77 Commits

Author SHA1 Message Date
citronneur
1c4b42544c Update: Finish mcs connect 2020-05-27 22:27:24 +02:00
citronneur
9550734743 Fix: bad string length 2020-04-21 22:13:20 +02:00
citronneur
73b97d6929 Update: Start MCS layer 2020-04-21 22:02:35 +02:00
citronneur
018c59fe42 Update: refactor cssp code 2020-04-20 22:51:32 +02:00
citronneur
9cac72a8d2 Update: TPKT + x224 asyncio complient 2020-04-17 15:52:37 +02:00
unknown
3f56b25f46 Start python3 2020-04-14 21:56:46 +02:00
Sylvain Peyrefitte
cef16a9f64 Merge pull request #108 from cudeso/master
Add logging for rdphoneypot
2020-04-10 21:12:15 +02:00
Koen Van Impe
9aea135fd9 Add logging 2020-04-10 21:05:18 +02:00
Sylvain Peyrefitte
4109b7a6fe Merge pull request #67 from Mutchako/patch-1
Update setup.py
2018-08-23 22:20:14 +02:00
Sylvain Peyrefitte
d3b0ae5e90 Merge pull request #80 from speidy/pointerex
onPointerEvent: handle 4, 5 mouse buttons based on INPUT_EVENT_MOUSEX event
2018-08-23 22:18:20 +02:00
Idan Freiberg
a1f9afa87a onPointerEvent: handle 4, 5 mouse buttons based on INPUT_EVENT_MOUSEX event 2018-08-06 07:52:32 +03:00
Sylvain Peyrefitte
b8ff4136b6 Merge pull request #78 from speidy/mitm
rdpy-rdpmitm: pep8 fixes, argument parsing improvements
2018-08-05 16:23:28 +02:00
Sylvain Peyrefitte
ce04150790 Merge pull request #77 from speidy/pointerex
data: add support for INPUT_EVENT_MOUSEX event
2018-08-05 16:23:03 +02:00
speidy
bcec1aad25 rdpy-rdpmitm: use argparse for argument parsing 2018-08-05 17:07:29 +03:00
speidy
c18a4c4101 rdpy-rdpmitm: apply pep8 fixes 2018-08-05 15:58:07 +03:00
speidy
3e37899ae4 data: add support for INPUT_EVENT_MOUSEX event 2018-08-05 14:28:23 +03:00
Mutchako
cd6e14e7ef Update setup.py
Syntax error on install_requires. Unnecessary comma on last statement.
2018-02-12 09:48:17 -05:00
Sylvain Peyrefitte
629d2160af Merge pull request #33 from ChrisTruncer/bin_
Small change to reference object attribute
2015-06-01 11:40:37 +02:00
Christopher Truncer
e23def3179 Small changeto reference object attribute 2015-05-28 12:08:55 -04:00
speyrefitte
a23ae25a1f fix issue on unhandle upadte 2015-05-21 10:29:32 +02:00
speyrefitte
763ed2e3ee fix major bug on update handle 2015-05-20 17:51:43 +02:00
speyrefitte
11d66a4818 add onSessionReady event -> user session is ready 2015-05-19 17:53:15 +02:00
speyrefitte
9b99365f80 fix bug on lwin key activation 2015-05-19 16:42:22 +02:00
speyrefitte
bd7c708bf3 update version 2015-05-04 11:49:37 +02:00
speyrefitte
1deb2d69ea bug fixing 2015-05-04 11:47:25 +02:00
speyrefitte
0a5a1fd12c add keylogger on rss player and file format 2015-04-29 12:13:24 +02:00
speyrefitte
c97b451ce3 Merge branch 'hotfix' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:32:50 +02:00
speyrefitte
80f989a804 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:32:44 +02:00
speyrefitte
c6e100f9a6 Merge branch 'master' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:31:05 +02:00
Sylvain Peyrefitte
15df00ec20 Merge pull request #24 from citronneur/master
Update readme with pypi version
2015-03-25 14:48:18 +01:00
Sylvain Peyrefitte
342349cf41 Merge pull request #23 from citronneur/master
Just add pypi package
2015-03-25 14:47:14 +01:00
Sylvain Peyrefitte
d6043106e3 Update README.md 2015-03-25 12:15:57 +01:00
speyrefitte
bd7e73a6e7 change log format 2015-03-20 17:54:48 +01:00
Sylvain Peyrefitte
fc1685e652 Merge pull request #21 from ojosdegris/patch-1
Added OS X install example
2015-03-18 10:47:01 +01:00
vittore
4320824aae Fixed example to follow existing style. 2015-03-13 12:11:41 -04:00
vittore
5a438174b9 Added OS X install example 2015-03-13 12:07:22 -04:00
speyrefitte
bb9483e7e1 update setup.py 2015-03-13 10:38:26 +01:00
speyrefitte
bd362263f7 Correct bug from cssp security layer 2015-03-13 10:17:13 +01:00
citronneur
20de5f6f82 Add tests for cssp ntlm authentication protocol 2015-03-09 22:31:08 +01:00
citronneur
95052a323f remove pycrypto dependancies, bug fixing 2015-03-08 21:04:26 +01:00
speyrefitte
0abf18d130 NLA Security Layer is AVAILABLE 2015-03-06 18:19:42 +01:00
Sylvain Peyrefitte
b57b3d7398 Update ntlm.py 2015-03-05 22:53:13 +01:00
citronneur
0695825d98 cssp protocol proxy 2015-03-05 22:28:33 +01:00
speyrefitte
8fb4893b6f almost finish ntlmv2... 2015-03-05 18:26:36 +01:00
citronneur
30a16fbb7a add some usefull methods 2015-03-03 21:57:40 +01:00
speyrefitte
98494d0e73 try to understand key in ntlm 2015-03-03 18:27:45 +01:00
speyrefitte
a7058f1c54 not handle correctly the ntlmv2 auth message 2015-03-02 18:37:18 +01:00
citronneur
1e2f284e97 Parse chanllenge response from server 2015-02-24 22:35:16 +01:00
citronneur
8cd789480f Fix immediatly automata 2015-02-22 14:04:09 +01:00
citronneur
e9a93d117b automata modification for NLA 2015-02-21 14:29:17 +01:00
citronneur
3fe16130d8 NTLM pbs ... 2015-02-21 10:07:19 +01:00
citronneur
31b0920a87 Some changes + NTLM challenge message 2015-02-20 22:13:50 +01:00
speyrefitte
36c05faa11 NTLM Negotiate message embended in cssp request 2015-02-20 10:13:24 +01:00
citronneur
1c3119cffd ASN.1 Tag correcr 2015-02-19 23:05:21 +01:00
speyrefitte
d6bb21565d start spneg 2015-02-19 18:26:50 +01:00
citronneur
d6428430eb Start NTLM support 2015-02-18 22:01:58 +01:00
citronneur
a4f4d71929 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-02-18 21:10:18 +01:00
citronneur
9e211c0199 Merge branch 'master' of https://github.com/citronneur/rdpy into dev 2015-02-18 21:10:11 +01:00
Sylvain Peyrefitte
5bd78cc012 Merge pull request #18 from manuteleco/master
Fix typos and tabs in README
2015-02-17 23:10:36 +01:00
Manuel Rodríguez Guimeráns
e139a2c7eb Replace tabs with spaces in code snippets. 2015-02-17 16:14:06 +01:00
Manuel Rodríguez Guimeráns
349a8a7227 Fix typos in documentation. 2015-02-17 16:12:58 +01:00
speyrefitte
35514a2849 Fix build issue 2015-02-17 13:45:22 +01:00
citronneur
222ee76c91 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-02-16 22:29:42 +01:00
citronneur
30c3611bb9 add credssp grammar 2015-02-16 22:29:12 +01:00
citronneur
6c93ca17b0 release 1.2.2 2015-02-16 11:35:01 +01:00
speyrefitte
a01eb57cef Add support for salted mac generation, fix issue 17 2015-02-10 11:39:26 +01:00
speyrefitte
9e50c2292d fix certuficate signing 2015-02-09 15:43:19 +01:00
speyrefitte
82d7798255 fix issue 16 2015-02-06 18:07:02 +01:00
speyrefitte
e9db7d720f fix issue 16 2015-02-06 15:23:44 +01:00
speyrefitte
1d5b15a310 fix minor bug to work with rdesktop but finally there is a bug in rdesktop 2015-02-06 14:35:41 +01:00
speyrefitte
a8ddaa77ff fix issue 14 on xp sp3 + server side effect for honeypot 2015-02-05 16:06:06 +01:00
citronneur
4c56f55266 fix issue 14 2015-02-04 21:47:32 +01:00
citronneur
1039e014c1 fix issue 14 2015-02-04 21:04:58 +01:00
speyrefitte
84ac320e82 add multiple file for honeypot 2015-01-29 17:56:21 +01:00
speyrefitte
02dfe8f46e fix issue 13 2015-01-23 17:13:14 +01:00
speyrefitte
8b159e668f merge jaredhaight branch 2015-01-19 11:23:46 +01:00
Jared Haight
1b1dfa06c8 Fixed some typos 2015-01-16 22:40:31 -05:00
50 changed files with 3272 additions and 2862 deletions

2
.gitignore vendored
View File

@@ -7,3 +7,5 @@ README.md~
dist/*
build/*
rdpy.egg-info/*
*.pyd
.idea

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 service_identity rsa
- pip install qt4reactor pyopenssl twisted service_identity rsa pyasn1
install:
- python setup.py install

290
README.md
View File

@@ -1,25 +1,25 @@
# RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=dev)](https://travis-ci.org/citronneur/rdpy)
# RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=dev)](https://travis-ci.org/citronneur/rdpy) [![PyPI version](https://badge.fury.io/py/rdpy.png)](http://badge.fury.io/py/rdpy)
Remote Desktop Protocol in twisted PYthon.
Remote Desktop Protocol in twisted python.
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server Side). RDPY is built over the event driven network engine Twisted.
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (client and server side). RDPY is built over the event driven network engine Twisted. RDPY support standard RDP security layer, RDP over SSL and NLA authentication (through ntlmv2 authentication protocol).
RDPY provide RDP and VNC binaries :
RDPY provides the following RDP and VNC binaries :
* RDP Man In The Middle proxy which record session
* RDP Honeypot
* RDP screen shooter
* RDP screenshoter
* RDP client
* VNC client
* VNC screen shooter
* VNC screenshoter
* RSS Player
## Build
RDPY is fully implemented in python, except the bitmap uncompression algorithm which is implemented in C for performance purposes.
RDPY is fully implemented in python, except the bitmap decompression algorithm which is implemented in C for performance purposes.
### Depends
### Dependencies
Depends are only needed for pyqt4 binaries :
Dependencies are only needed for pyqt4 binaries :
* rdpy-rdpclient
* rdpy-rdpscreenshot
* rdpy-vncclient
@@ -28,11 +28,17 @@ Depends are only needed for pyqt4 binaries :
#### Linux
Exemple from Debian based system :
Example for Debian based systems :
```
sudo apt-get install python-qt4
```
#### OS X
Example for OS X to install PyQt with homebrew
```
$ brew install qt sip pyqt
```
#### Windows
x86 | x86_64
@@ -44,7 +50,7 @@ x86 | x86_64
```
$ git clone https://github.com/citronneur/rdpy.git rdpy
$ pip install twisted pyopenssl qt4reactor service_identity rsa
$ pip install twisted pyopenssl qt4reactor service_identity rsa pyasn1
$ python rdpy/setup.py install
```
@@ -53,7 +59,7 @@ Or use PIP:
$ pip install rdpy
```
For virtualenv, you need to link qt4 library to it:
For virtualenv, you will need to link the qt4 library to it:
```
$ 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/
@@ -65,13 +71,13 @@ RDPY comes with some very useful binaries. These binaries are linux and windows
### rdpy-rdpclient
rdpy-rdpclient is a simple RDP Qt4 client .
rdpy-rdpclient is a simple RDP Qt4 client.
```
$ 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.
You can use rdpy-rdpclient in a Recorder Session Scenario, used in rdpy-rdphoneypot.
### rdpy-vncclient
@@ -83,7 +89,7 @@ $ rdpy-vncclient.py [-p password] XXX.XXX.XXX.XXX[:5900]
### rdpy-rdpscreenshot
rdpy-rdpscreenshot save login screen in file.
rdpy-rdpscreenshot saves login screen in file.
```
$ rdpy-rdpscreenshot.py [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX.XXX[:3389]
@@ -91,7 +97,7 @@ $ rdpy-rdpscreenshot.py [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX
### rdpy-vncscreenshot
rdpy-vncscreenshot save first screen update in file.
rdpy-vncscreenshot saves the first screen update in file.
```
$ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900]
@@ -100,24 +106,25 @@ $ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:590
### rdpy-rdpmitm
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.
Record Session Scenario into rss file which can be replayed by rdpy-rssplayer.
```
$ 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]
```
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.
Output directory is used to save the 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 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.
rdpy-rdphoneypot is an RDP honey Pot. Use Recorded Session Scenario to replay scenario through RDP Protocol.
```
$ rdpy-rdphoneypot.py [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] rss_file_path
$ rdpy-rdphoneypot.py [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] rss_file_path_1 ... rss_file_path_N
```
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. If one of both parameters are omitted, the server use standard RDP as security layer.
You can specify more than one files to match more common screen size.
### rdpy-rssplayer
@@ -129,113 +136,118 @@ $ rdpy-rssplayer.py rss_file_path
## RDPY Qt Widget
RDPY can also be used as Qt widget throw rdpy.ui.qt4.QRemoteDesktop class. It can be embedded in your own Qt application. qt4reactor must be used in your app for Twisted and Qt to work together. For more details, see sources of rdpy-rdpclient.
RDPY can also be used as Qt widget through rdpy.ui.qt4.QRemoteDesktop class. It can be embedded in your own Qt application. qt4reactor must be used in your app for Twisted and Qt to work together. For more details, see sources of rdpy-rdpclient.
## RDPY library
In a nutshell the RDPY can be used as a protocol library with a twisted engine.
In a nutshell RDPY can be used as a protocol library with a twisted engine.
### Simple RDP Client
```python
from rdpy.protocol.rdp import rdp
from rdpy.core import rdp
class MyRDPFactory(rdp.ClientFactory):
def clientConnectionLost(self, connector, reason):
def clientConnectionLost(self, connector, reason):
reactor.stop()
def clientConnectionFailed(self, connector, reason):
reactor.stop()
def buildObserver(self, controller, addr):
class MyObserver(rdp.RDPClientObserver)
def onReady(self):
class MyObserver(rdp.RDPClientObserver):
def onReady(self):
"""
@summary: Call when stack is ready
"""
#send 'r' key
self._controller.sendKeyEventUnicode(ord(unicode("r".toUtf8(), encoding="UTF-8")), True)
#mouse move and click at pixel 200x200
self._controller.sendPointerEvent(200, 200, 1, true)
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@summary: Notify bitmap update
@param destLeft: xmin position
@param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding
@param destBottom: ymax position because RDP can send bitmap with padding
@param width: width of bitmap
@param height: height of bitmap
@param bitsPerPixel: number of bit per pixel
@param isCompress: use RLE compression
@param data: bitmap data
"""
def onSessionReady(self):
"""
@summary: Call when stack is ready
"""
#send 'r' key
self._controller.sendKeyEventUnicode(ord(unicode("r".toUtf8(), encoding="UTF-8")), True)
#mouse move and click at pixel 200x200
self._controller.sendPointerEvent(200, 200, 1, true)
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@summary: Notify bitmap update
@param destLeft: xmin position
@param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding
@param destBottom: ymax position because RDP can send bitmap with padding
@param width: width of bitmap
@param height: height of bitmap
@param bitsPerPixel: number of bit per pixel
@param isCompress: use RLE compression
@param data: bitmap data
"""
def onClose(self):
"""
@summary: Call when stack is close
@summary: Windows session is ready
"""
return MyObserver(controller)
def onClose(self):
"""
@summary: Call when stack is close
"""
return MyObserver(controller)
from twisted.internet import reactor
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory())
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389, MyRDPFactory())
reactor.run()
```
### Simple RDP Server
```python
from rdpy.protocol.rdp import rdp
from rdpy.core import rdp
class MyRDPFactory(rdp.ServerFactory):
def buildObserver(self, controller, addr):
class MyObserver(rdp.RDPServerObserver)
def onReady(self):
"""
@summary: Call when server is ready
to send and receive messages
"""
def onKeyEventScancode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in scan code format
@param code: scan code of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventScancode
"""
def onKeyEventUnicode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in unicode format
@param code: unicode of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventUnicode
"""
def onPointerEvent(self, x, y, button, isPressed):
"""
@summary: Event call on mouse event
@param x: x position
@param y: y position
@param button: 1, 2 or 3 button
@param isPressed: True if mouse button is pressed
@see: rdp.RDPServerObserver.onPointerEvent
"""
def onClose(self):
"""
@summary: Call when human client close connection
@see: rdp.RDPServerObserver.onClose
"""
return MyObserver(controller)
class MyObserver(rdp.RDPServerObserver):
def onReady(self):
"""
@summary: Call when server is ready
to send and receive messages
"""
def onKeyEventScancode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in scan code format
@param code: scan code of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventScancode
"""
def onKeyEventUnicode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in unicode format
@param code: unicode of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventUnicode
"""
def onPointerEvent(self, x, y, button, isPressed):
"""
@summary: Event call on mouse event
@param x: x position
@param y: y position
@param button: 1, 2, 3, 4 or 5 button
@param isPressed: True if mouse button is pressed
@see: rdp.RDPServerObserver.onPointerEvent
"""
def onClose(self):
"""
@summary: Call when human client close connection
@see: rdp.RDPServerObserver.onClose
"""
return MyObserver(controller)
from twisted.internet import reactor
reactor.listenTCP(3389, MyRDPFactory())
@@ -244,55 +256,55 @@ reactor.run()
### Simple VNC Client
```python
from rdpy.protocol.rfb import rdp
from rdpy.protocol.rfb import rfb
class MyRFBFactory(rfb.ClientFactory):
def clientConnectionLost(self, connector, reason):
def clientConnectionLost(self, connector, reason):
reactor.stop()
def clientConnectionFailed(self, connector, reason):
reactor.stop()
def buildObserver(self, controller, addr):
class MyObserver(rfb.RFBClientObserver)
def onReady(self):
"""
@summary: Event when network stack is ready to receive or send event
"""
class MyObserver(rfb.RFBClientObserver):
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
"""
@summary: Implement RFBClientObserver interface
@param width: width of new image
@param height: height of new image
@param x: x position of new image
@param y: y position of new image
@param pixelFormat: pixefFormat structure in rfb.message.PixelFormat
@param encoding: encoding type rfb.message.Encoding
@param data: image data in accordance with pixel format and encoding
"""
def onCutText(self, text):
"""
@summary: event when server send cut text event
@param text: text received
"""
def onBell(self):
"""
@summary: event when server send biiip
"""
def onClose(self):
"""
@summary: Call when stack is close
"""
def onReady(self):
"""
@summary: Event when network stack is ready to receive or send event
"""
return MyObserver(controller)
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
"""
@summary: Implement RFBClientObserver interface
@param width: width of new image
@param height: height of new image
@param x: x position of new image
@param y: y position of new image
@param pixelFormat: pixefFormat structure in rfb.message.PixelFormat
@param encoding: encoding type rfb.message.Encoding
@param data: image data in accordance with pixel format and encoding
"""
def onCutText(self, text):
"""
@summary: event when server send cut text event
@param text: text received
"""
def onBell(self):
"""
@summary: event when server send biiip
"""
def onClose(self):
"""
@summary: Call when stack is close
"""
return MyObserver(controller)
from twisted.internet import reactor
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRFBFactory())
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389, MyRFBFactory())
reactor.run()
```

View File

@@ -21,264 +21,27 @@
example of use rdpy as rdp client
"""
import sys, os, getopt, socket
import sys
import asyncio
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
from rdpy.core import tpkt, x224
from rdpy.core.nla import ntlm
from rdpy.core.t125 import mcs
from rdpy.model.message import UInt8
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, security, recodedPath):
"""
@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
self._username = username
self._passwod = password
self._domain = domain
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):
"""
@summary: Build RFB observer
We use a RDPClientQt as RDP observer
@param controller: build factory and needed by observer
@param addr: destination address
@return: RDPClientQt
"""
#create client observer
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 = self._client.getWidget()
self._w.setWindowTitle('rdpy-rdpclient')
if self._fullscreen:
self._w.showFullScreen()
else:
self._w.show()
controller.setUsername(self._username)
controller.setPassword(self._passwod)
controller.setDomain(self._domain)
controller.setKeyboardLayout(self._keyboardLayout)
controller.setHostname(socket.gethostname())
if self._optimized:
controller.setPerformanceSession()
controller.setSecurityLevel(self._security)
return self._client
def clientConnectionLost(self, connector, reason):
"""
@summary: Connection lost event
@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()
def clientConnectionFailed(self, connector, reason):
"""
@summary: Connection failed event
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
@param reason: str use to advertise reason of lost connection
"""
QtGui.QMessageBox.warning(self._w, "Warning", "Connection failed : %s"%reason)
reactor.stop()
app.exit()
def autoDetectKeyboardLayout():
"""
@summary: try to auto detect keyboard layout
"""
try:
if os.name == 'posix':
from subprocess import check_output
result = check_output(["setxkbmap", "-print"])
if "azerty" in result:
return "fr"
elif os.name == 'nt':
import win32api, win32con, win32process
from ctypes import windll
w = windll.user32.GetForegroundWindow()
tid = windll.user32.GetWindowThreadProcessId(w, 0)
result = windll.user32.GetKeyboardLayout(tid)
log.info(result)
if result == 0x40c040c:
return "fr"
except Exception as e:
log.info("failed to auto detect keyboard layout " + str(e))
pass
return "en"
def help():
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__':
#default script argument
username = ""
password = ""
domain = ""
width = 1024
height = 800
fullscreen = False
optimized = False
recodedPath = None
keyboardLayout = autoDetectKeyboardLayout()
try:
opts, args = getopt.getopt(sys.argv[1:], "hfou:p:d:w:l:k:r:")
except getopt.GetoptError:
help()
for opt, arg in opts:
if opt == "-h":
help()
sys.exit()
elif opt == "-u":
username = arg
elif opt == "-p":
password = arg
elif opt == "-d":
domain = arg
elif opt == "-w":
width = int(arg)
elif opt == "-l":
height = int(arg)
elif opt == "-f":
fullscreen = True
elif opt == "-o":
optimized = True
elif opt == "-k":
keyboardLayout = arg
elif opt == "-r":
recodedPath = arg
if ':' in args[0]:
ip, port = args[0].split(':')
else:
ip, port = args[0], "3389"
#create application
app = QtGui.QApplication(sys.argv)
#add qt4 reactor
import qt4reactor
qt4reactor.install()
if fullscreen:
width = QtGui.QDesktopWidget().screenGeometry().width()
height = QtGui.QDesktopWidget().screenGeometry().height()
log.info("keyboard layout set to %s"%keyboardLayout)
from twisted.internet import reactor
reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain, fullscreen, keyboardLayout, optimized, "nego", recodedPath))
reactor.runReturn()
app.exec_()
#sys.exit(app.exec_())
async def tcp_echo_client(message):
reader, writer = await asyncio.open_connection(
'127.0.0.1', 33389)
x224_layer = await x224.connect(tpkt.Tpkt(reader, writer), ntlm.NTLMv2("", "sylvain", "sylvain"))
mcs_layer = mcs.Client(x224_layer)
await mcs_layer.connect()
await asyncio.sleep(10)
print("foooooooooooooooooooo")
asyncio.run(tcp_echo_client('Hello World!'))

View File

@@ -22,22 +22,24 @@
RDP Honey pot use Rss scenario file to simulate RDP server
"""
import sys, os, getopt, time
import sys, getopt, datetime
from rdpy.core import log, error, rss
from rdpy.protocol.rdp import rdp
from rdpy.core import log, rss
from rdpy.core import rdp
from twisted.internet import reactor
log._LOG_LEVEL = log.Level.INFO
class HoneyPotServer(rdp.RDPServerObserver):
def __init__(self, controller, rssFile):
def __init__(self, controller, rssFileSizeList):
"""
@param controller: {RDPServerController}
@param rssFileSizeList: {Tuple} Tuple(Tuple(width, height), rssFilePath)
"""
rdp.RDPServerObserver.__init__(self, controller)
self._rssFile = rssFile
self._rssFileSizeList = rssFileSizeList
self._dx, self._dy = 0, 0
self._rssFile = None
def onReady(self):
"""
@@ -47,20 +49,23 @@ class HoneyPotServer(rdp.RDPServerObserver):
restart a connection sequence
@see: rdp.RDPServerObserver.onReady
"""
if self._rssFile is None:
#compute which RSS file to keep
width, height = self._controller.getScreen()
size = width * height
rssFilePath = sorted(self._rssFileSizeList, key = lambda x: abs(x[0][0] * x[0][1] - size))[0][1]
log.info("%s --- select file (%s, %s) -> %s"%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'),width, height, rssFilePath))
self._rssFile = rss.createReader(rssFilePath)
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));
log.info("""%s --- Credentials: domain: %s username: %s password: %s hostname: %s"""%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), domain, username, password, hostname));
self.start()
def onClose(self):
""" HoneyPot """
def onKeyEventScancode(self, code, isPressed):
def onKeyEventScancode(self, code, isPressed, isExtended):
""" HoneyPot """
def onKeyEventUnicode(self, code, isPressed):
@@ -89,7 +94,7 @@ class HoneyPotServer(rdp.RDPServerObserver):
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
self._dx, self._dy = (max(0, serverSize[0] - clientSize[0]) / 2), max(0, (serverSize[1] - clientSize[1]) / 2)
#restart connection sequence
return
@@ -100,14 +105,14 @@ class HoneyPotServerFactory(rdp.ServerFactory):
"""
@summary: Factory on listening events
"""
def __init__(self, rssFilePath, privateKeyFilePath, certificateFilePath):
def __init__(self, rssFileSizeList, privateKeyFilePath, certificateFilePath):
"""
@param rssFilePath: Recorded Session Scenario File path
@param rssFileSizeList: {Tuple} Tuple(Tuple(width, height), rssFilePath)
@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
self._rssFileSizeList = rssFileSizeList
def buildObserver(self, controller, addr):
"""
@@ -115,33 +120,51 @@ class HoneyPotServerFactory(rdp.ServerFactory):
@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))
log.info("%s --- Connection from %s:%s"%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), addr.host, addr.port))
return HoneyPotServer(controller, self._rssFileSizeList)
def readSize(filePath):
"""
@summary: read size event in rss file
@param filePath: path of rss file
"""
r = rss.createReader(filePath)
while True:
e = r.nextEvent()
if e is None:
return None
elif e.type.value == rss.EventType.SCREEN:
return e.event.width.value, e.event.height.value
def help():
"""
@summary: Print help in console
"""
print """
Usage: rdpy-rdphoneypot.py rss_filepath
Usage: rdpy-rdphoneypot.py
[-L logfile]
[-l listen_port default 3389]
[-k private_key_file_path (mandatory for SSL)]
[-c certificate_file_path (mandatory for SSL)]
rss_filepath(1..n)
"""
if __name__ == '__main__':
listen = "3389"
privateKeyFilePath = None
certificateFilePath = None
rssFileSizeList = []
try:
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:")
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:L:")
except getopt.GetoptError:
help()
for opt, arg in opts:
if opt == "-h":
help()
sys.exit()
elif opt == "-L":
log._LOG_FILE = arg
elif opt == "-l":
listen = arg
elif opt == "-k":
@@ -149,5 +172,13 @@ if __name__ == '__main__':
elif opt == "-c":
certificateFilePath = arg
reactor.listenTCP(int(listen), HoneyPotServerFactory(args[0], privateKeyFilePath, certificateFilePath))
reactor.run()
#build size map
log.info("%s --- Start rdphoneypot"%datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
log.info("%s --- Build size map"%datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
for arg in args:
size = readSize(arg)
rssFileSizeList.append((size, arg))
log.info("%s --- (%s, %s) -> %s"%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), size[0], size[1], arg))
reactor.listenTCP(int(listen), HoneyPotServerFactory(rssFileSizeList, privateKeyFilePath, certificateFilePath))
reactor.run()

View File

@@ -29,18 +29,22 @@ Client RDP -> | ProxyServer | ProxyClient | -> Server RDP
-----------------
"""
import sys, os, getopt, time
import os
import argparse
import time
from rdpy.core import log, error, rss
from rdpy.protocol.rdp import rdp
from rdpy.core import log, rss
from rdpy.core 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}
@@ -52,14 +56,14 @@ class ProxyServer(rdp.RDPServerObserver):
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
@@ -69,40 +73,44 @@ class ProxyServer(rdp.RDPServerObserver):
@see: rdp.RDPServerObserver.onReady
"""
if self._client is None:
#try a connection
# try a connection
domain, username, password = self._controller.getCredentials()
self._rss.credentials(username, password, domain, self._controller.getHostname())
self._rss.credentials(username, password,
domain, self._controller.getHostname())
width, height = self._controller.getScreen()
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))
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
# end scenario
self._rss.close()
#close network stack
# close network stack
if self._client is None:
return
self._client._controller.close()
def onKeyEventScancode(self, code, isPressed):
def onKeyEventScancode(self, code, isPressed, isExtended):
"""
@summary: Event call when a keyboard event is catch in scan code format
@param code: {int} scan code of key
@param isPressed: {bool} True if key is down
@param code: {integer} scan code of key
@param isPressed: {boolean} True if key is down
@param isExtended: {boolean} True if a special key
@see: rdp.RDPServerObserver.onKeyEventScancode
"""
if self._client is None:
return
self._client._controller.sendKeyEventScancode(code, isPressed)
self._client._controller.sendKeyEventScancode(
code, isPressed, isExtended)
self._rss.keyScancode(code, isPressed)
def onKeyEventUnicode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in unicode format
@@ -113,24 +121,27 @@ class ProxyServer(rdp.RDPServerObserver):
if self._client is None:
return
self._client._controller.sendKeyEventUnicode(code, isPressed)
self._rss.keyUnicode(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 button: {int} 1, 2, 3, 4 or 5 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)}
@@ -138,13 +149,14 @@ class ProxyServerFactory(rdp.ServerFactory):
@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)
rdp.ServerFactory.__init__(
self, 16, privateKeyFilePath, certificateFilePath)
self._target = target
self._ouputDir = ouputDir
self._clientSecurity = clientSecurity
#use produce unique file by connection
# use produce unique file by connection
self._uniqueId = 0
def buildObserver(self, controller, addr):
"""
@param controller: {rdp.RDPServerController}
@@ -152,12 +164,14 @@ class ProxyServerFactory(rdp.ServerFactory):
@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))))
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}
@@ -165,7 +179,7 @@ class ProxyClient(rdp.RDPClientObserver):
"""
rdp.RDPClientObserver.__init__(self, controller)
self._server = server
def onReady(self):
"""
@summary: Event use to signal that RDP stack is ready
@@ -173,18 +187,26 @@ class ProxyClient(rdp.RDPClientObserver):
@see: rdp.RDPClientObserver.onReady
"""
self._server.setClient(self)
#maybe color depth change
self._server._controller.setColorDepth(self._controller.getColorDepth())
# maybe color depth change
self._server._controller.setColorDepth(
self._controller.getColorDepth())
def onSessionReady(self):
"""
@summary: Windows session is ready
@see: rdp.RDPClientObserver.onSessionReady
"""
pass
def onClose(self):
"""
@summary: Event inform that stack is close
@see: rdp.RDPClientObserver.onClose
"""
#end scenario
# 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
@@ -199,13 +221,17 @@ class ProxyClient(rdp.RDPClientObserver):
@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)
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}
@@ -223,7 +249,7 @@ class ProxyClientFactory(rdp.ClientFactory):
self._username = username
self._password = password
self._security = security
def buildObserver(self, controller, addr):
"""
@summary: Build observer
@@ -232,64 +258,65 @@ class ProxyClientFactory(rdp.ClientFactory):
@see: rdp.ClientFactory.buildObserver
@return: ProxyClient
"""
#set screen resolution
# set screen resolution
controller.setScreen(self._width, self._height)
#set credential
# 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"):
def parseIpPort(interface, defaultPort="3389"):
if ':' in interface:
return interface.split(':')
s = interface.split(':')
return s[0], int(s[1])
else:
return interface, defaultPort
return interface, int(defaultPort)
def isDirectory(outputDirectory):
if outputDirectory is None or not os.path.dirname(outputDirectory):
log.error("{} is an invalid output directory or directory doesn't exist".format(
outputDirectory))
return outputDirectory
def mapSecurityLayer(layer):
return {
"rdp": rdp.SecurityLevel.RDP_LEVEL_RDP,
"tls": rdp.SecurityLevel.RDP_LEVEL_SSL,
"nla": rdp.SecurityLevel.RDP_LEVEL_NLA
}[layer]
if __name__ == '__main__':
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()
p = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
p.add_argument('-l', '--listen', type=parseIpPort, default="0.0.0.0:3389",
help="<addr>[:<port>] to bind the server")
p.add_argument('-t', '--target', type=parseIpPort, required=True,
help="<addr>[:<port>] of the target you want to connect to via proxy")
p.add_argument('-o', '--output', type=isDirectory,
help="output directory", required=True)
p.add_argument('-s', '--sec', choices=["rdp", "tls", "nla"],
default="rdp", help="set protocol security layer")
ssl = p.add_argument_group()
ssl.add_argument('-c', '--certificate', help="certificate for TLS connections")
ssl.add_argument('-k', '--key', help="private key of the given certificate for TLS connections")
args = p.parse_args()
if args.certificate and args.key and not args.sec == "nla":
args.sec = "tls"
log.info("running server on {addr}, using {sec} security layer, proxying to {target}".format(
addr=args.listen, sec=args.sec.upper(), target=args.target))
reactor.listenTCP(args.listen[1], ProxyServerFactory(
args.target, args.output, args.key, args.certificate, mapSecurityLayer(args.sec)),
interface=args.listen[0])
reactor.run()

View File

@@ -23,24 +23,26 @@ example of use rdpy
take screenshot of login page
"""
import sys, os, getopt
import getopt
import sys
from PyQt4 import QtCore, QtGui
from rdpy.protocol.rdp import rdp
from PyQt4 import QtGui
from rdpy.core import rdp
from rdpy.ui.qt4 import RDPBitmapToQtImage
import rdpy.core.log as log
from rdpy.core.error import RDPSecurityNegoFail
from twisted.internet import task
#set log level
# set log level
log._LOG_LEVEL = log.Level.INFO
class RDPScreenShotFactory(rdp.ClientFactory):
"""
@summary: Factory for screenshot exemple
"""
__INSTANCE__ = 0
__STATE__ = []
def __init__(self, reactor, app, width, height, path, timeout):
"""
@param reactor: twisted reactor
@@ -56,8 +58,9 @@ class RDPScreenShotFactory(rdp.ClientFactory):
self._height = height
self._path = path
self._timeout = timeout
self._security = "ssl"
#NLA server can't be screenshooting
self._security = rdp.SecurityLevel.RDP_LEVEL_SSL
def clientConnectionLost(self, connector, reason):
"""
@summary: Connection lost event
@@ -66,17 +69,17 @@ class RDPScreenShotFactory(rdp.ClientFactory):
"""
if reason.type == RDPSecurityNegoFail and self._security != "rdp":
log.info("due to RDPSecurityNegoFail try standard security layer")
self._security = "rdp"
self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
connector.connect()
return
log.info("connection lost : %s"%reason)
log.info("connection lost : %s" % reason)
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
RDPScreenShotFactory.__INSTANCE__ -= 1
if(RDPScreenShotFactory.__INSTANCE__ == 0):
self._reactor.stop()
self._app.exit()
def clientConnectionFailed(self, connector, reason):
"""
@summary: Connection failed event
@@ -89,8 +92,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
if(RDPScreenShotFactory.__INSTANCE__ == 0):
self._reactor.stop()
self._app.exit()
def buildObserver(self, controller, addr):
"""
@summary: build ScreenShot observer
@@ -116,39 +118,46 @@ class RDPScreenShotFactory(rdp.ClientFactory):
self._timeout = timeout
self._startTimeout = False
self._reactor = reactor
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@summary: callback use when bitmap is received
"""
image = RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data);
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data);
with QtGui.QPainter(self._buffer) as qp:
#draw image
# draw image
qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1)
if not self._startTimeout:
self._startTimeout = False
self._reactor.callLater(self._timeout, self.checkUpdate)
def onReady(self):
"""
@summary: callback use when RDP stack is connected (just before received bitmap)
"""
log.info("connected %s"%addr)
log.info("connected %s" % addr)
def onSessionReady(self):
"""
@summary: Windows session is ready
@see: rdp.RDPClientObserver.onSessionReady
"""
pass
def onClose(self):
"""
@summary: callback use when RDP stack is closed
"""
log.info("save screenshot into %s"%self._path)
log.info("save screenshot into %s" % self._path)
self._buffer.save(self._path)
def checkUpdate(self):
self._controller.close();
controller.setScreen(width, height);
controller.setScreen(self._width, self._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
@@ -160,39 +169,40 @@ def main(width, height, path, timeout, hosts):
"""
#create application
app = QtGui.QApplication(sys.argv)
#add qt4 reactor
import qt4reactor
qt4reactor.install()
from twisted.internet import reactor
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.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]"
print "\t-w: width of screen default value is 1024"
print "\t-l: height of screen default value is 800"
print "\t-o: file path of screenshot default(/tmp/rdpy-rdpscreenshot.jpg)"
print "\t-t: timeout of connection without any updating order (default is 2s)"
if __name__ == '__main__':
#default script argument
# default script argument
width = 1024
height = 800
path = "/tmp/"
timeout = 5.0
try:
opts, args = getopt.getopt(sys.argv[1:], "hw:l:o:t:")
except getopt.GetoptError:
@@ -209,5 +219,5 @@ if __name__ == '__main__':
path = arg
elif opt == "-t":
timeout = float(arg)
main(width, height, path, timeout, args)
main(width, height, path, timeout, args)

View File

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

View File

@@ -938,10 +938,19 @@ static PyMethodDef rle_methods[] =
{"bitmap_decompress", bitmap_decompress_wrapper, METH_VARARGS, "decompress bitmap from microsoft rle algorithm."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef rle =
{
PyModuleDef_HEAD_INIT,
"rle", /* name of module */
"", /* module documentation, may be NULL */
-1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */
rle_methods
};
PyMODINIT_FUNC
initrle(void)
PyInit_rle(void)
{
(void) Py_InitModule("rle", rle_methods);
(void) PyModule_Create(&rle);
}

View File

@@ -22,10 +22,11 @@
@see: http://msdn.microsoft.com/en-us/library/cc241880.aspx
"""
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.model.type import CompositeType, CallableValue, UInt8, UInt16Le, UInt32Le, Buffer, sizeof, FactoryType, ArrayType, Stream
from rdpy.model.error import InvalidExpectedDataException
import rdpy.model.log as log
from rdpy.core import sec
from rdpy.core.t125 import gcc
from rdpy.security import rc4
from rdpy.security import rsa_wrapper as rsa
@@ -97,11 +98,11 @@ class LicenseBinaryBlob(CompositeType):
@summary: Blob use by license manager to exchange security data
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
"""
def __init__(self, blobType = BinaryBlobType.BB_ANY_BLOB):
CompositeType.__init__(self)
def __init__(self, blobType = BinaryBlobType.BB_ANY_BLOB, optional = False):
CompositeType.__init__(self, optional = optional)
self.wBlobType = UInt16Le(blobType, constant = True if blobType != BinaryBlobType.BB_ANY_BLOB else False)
self.wBlobLen = UInt16Le(lambda:sizeof(self.blobData))
self.blobData = String(readLen = self.wBlobLen)
self.blobData = Buffer(readLen = self.wBlobLen)
class LicensingErrorMessage(CompositeType):
"""
@@ -110,11 +111,11 @@ class LicensingErrorMessage(CompositeType):
"""
_MESSAGE_TYPE_ = MessageType.ERROR_ALERT
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.dwErrorCode = UInt32Le()
self.dwStateTransition = UInt32Le()
self.blob = LicenseBinaryBlob(BinaryBlobType.BB_ERROR_BLOB)
self.blob = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
class ProductInformation(CompositeType):
"""
@@ -126,10 +127,10 @@ class ProductInformation(CompositeType):
self.dwVersion = UInt32Le()
self.cbCompanyName = UInt32Le(lambda:sizeof(self.pbCompanyName))
#may contain "Microsoft Corporation" from server microsoft
self.pbCompanyName = String("Microsoft Corporation", readLen = self.cbCompanyName, unicode = True)
self.pbCompanyName = Buffer("Microsoft Corporation", readLen = self.cbCompanyName, unicode = True)
self.cbProductId = UInt32Le(lambda:sizeof(self.pbProductId))
#may contain "A02" from microsoft license server
self.pbProductId = String("A02", readLen = self.cbProductId, unicode = True)
self.pbProductId = Buffer("A02", readLen = self.cbProductId, unicode = True)
class Scope(CompositeType):
@@ -159,9 +160,9 @@ class ServerLicenseRequest(CompositeType):
"""
_MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST
def __init__(self):
CompositeType.__init__(self)
self.serverRandom = String("\x00" * 32, readLen = UInt8(32))
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.serverRandom = Buffer("\x00" * 32, readLen = CallableValue(32))
self.productInfo = ProductInformation()
self.keyExchangeList = LicenseBinaryBlob(BinaryBlobType.BB_KEY_EXCHG_ALG_BLOB)
self.serverCertificate = LicenseBinaryBlob(BinaryBlobType.BB_CERTIFICATE_BLOB)
@@ -175,14 +176,14 @@ class ClientNewLicenseRequest(CompositeType):
"""
_MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
#RSA and must be only RSA
self.preferredKeyExchangeAlg = UInt32Le(0x00000001, constant = True)
#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, readLen = UInt8(32))
self.clientRandom = Buffer("\x00" * 32, readLen = CallableValue(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)
@@ -194,11 +195,11 @@ class ServerPlatformChallenge(CompositeType):
"""
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.connectFlags = UInt32Le()
self.encryptedPlatformChallenge = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
self.MACData = String(readLen = UInt8(16))
self.MACData = Buffer(readLen = CallableValue(16))
class ClientPLatformChallengeResponse(CompositeType):
"""
@@ -207,11 +208,11 @@ class ClientPLatformChallengeResponse(CompositeType):
"""
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE_RESPONSE
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.encryptedPlatformChallengeResponse = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
self.encryptedHWID = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
self.MACData = String(readLen = UInt8(16))
self.MACData = Buffer(readLen = CallableValue(16))
class LicPacket(CompositeType):
"""
@@ -231,9 +232,9 @@ class LicPacket(CompositeType):
"""
for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest, ServerPlatformChallenge, ClientPLatformChallengeResponse]:
if self.bMsgtype.value == c._MESSAGE_TYPE_:
return c()
return c(readLen = self.wMsgSize - 4)
log.debug("unknown license message : %s"%self.bMsgtype.value)
return String()
return Buffer(readLen =self.wMsgSize - 4)
if message is None:
message = FactoryType(LicensingMessageFactory)
@@ -271,7 +272,7 @@ class LicenseManager(object):
@return true when license automata is finish
"""
licPacket = LicPacket()
s.readType(licPacket)
s.read_type(licPacket)
#end of automata
if licPacket.bMsgtype.value == MessageType.ERROR_ALERT and licPacket.licensingMessage.dwErrorCode.value == ErrorCode.STATUS_VALID_CLIENT and licPacket.licensingMessage.dwStateTransition.value == StateTransition.ST_NO_TRANSITION:
@@ -302,9 +303,12 @@ class LicenseManager(object):
"""
#get server information
serverRandom = licenseRequest.serverRandom.value
s = Stream(licenseRequest.serverCertificate.blobData.value)
serverCertificate = gcc.ServerCertificate()
s.readType(serverCertificate)
if self._transport.getGCCServerSettings().SC_SECURITY.serverCertificate._is_readed:
serverCertificate = self._transport.getGCCServerSettings().SC_SECURITY.serverCertificate
else:
s = Stream(licenseRequest.serverCertificate.blobData.value)
serverCertificate = gcc.ServerCertificate()
s.read_type(serverCertificate)
#generate crypto values
clientRandom = rsa.random(256)
@@ -336,7 +340,7 @@ class LicenseManager(object):
#generate hwid
s = Stream()
s.writeType((UInt32Le(2), String(self._hostname + self._username + "\x00" * 16)))
s.write_type((UInt32Le(2), Buffer(self._hostname + self._username + "\x00" * 16)))
hwid = s.getvalue()[:20]
message = ClientPLatformChallengeResponse()

206
rdpy/core/nla/cssp.py Normal file
View File

@@ -0,0 +1,206 @@
#
# 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/>.
#
"""
@summary: Credential Security Support Provider
@see: https://msdn.microsoft.com/en-us/library/cc226764.aspx
"""
from pyasn1.type import namedtype, univ, tag
import pyasn1.codec.der.encoder as der_encoder
import pyasn1.codec.der.decoder as der_decoder
from rdpy.core.nla import sspi
from rdpy.model.message import Stream
from rdpy.model import error
from rdpy.security.x509 import X509Certificate
class NegoToken(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('negoToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
)
class NegoData(univ.SequenceOf):
"""
@summary: contain spnego ntlm of kerberos data
@see: https://msdn.microsoft.com/en-us/library/cc226781.aspx
"""
componentType = NegoToken()
class TSRequest(univ.Sequence):
"""
@summary: main structure
@see: https://msdn.microsoft.com/en-us/library/cc226780.aspx
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('version', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.OptionalNamedType('negoTokens', NegoData().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
namedtype.OptionalNamedType('authInfo', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
namedtype.OptionalNamedType('pubKeyAuth', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
namedtype.OptionalNamedType('errorCode', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
)
class TSCredentials(univ.Sequence):
"""
@summary: contain user information
@see: https://msdn.microsoft.com/en-us/library/cc226782.aspx
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('credType', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.NamedType('credentials', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)
class TSPasswordCreds(univ.Sequence):
"""
@summary: contain username and password
@see: https://msdn.microsoft.com/en-us/library/cc226783.aspx
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('domainName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.NamedType('userName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
namedtype.NamedType('password', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
)
class TSCspDataDetail(univ.Sequence):
"""
@summary: smart card credentials
@see: https://msdn.microsoft.com/en-us/library/cc226785.aspx
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('keySpec', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.OptionalNamedType('cardName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
namedtype.OptionalNamedType('readerName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
namedtype.OptionalNamedType('containerName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
namedtype.OptionalNamedType('cspName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
)
class TSSmartCardCreds(univ.Sequence):
"""
@summary: smart card credentials
@see: https://msdn.microsoft.com/en-us/library/cc226784.aspx
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('pin', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.NamedType('cspData', TSCspDataDetail().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
namedtype.OptionalNamedType('userHint', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
namedtype.OptionalNamedType('domainHint', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
)
def encode_der_trequest(nego_types=[], auth_info=None, pub_key_auth=None):
"""
@summary: create TSRequest from list of Type
@param nego_types: {list(Type)}
@param auth_info: {str} authentication info TSCredentials encrypted with authentication protocol
@param pub_key_auth: {str} public key encrypted with authentication protocol
@return: {str} TRequest der encoded
"""
negoData = NegoData().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))
#fill nego data tokens
i = 0
for negoType in nego_types:
s = Stream()
s.write_type(negoType)
negoToken = NegoToken()
negoToken.setComponentByPosition(0, s.getvalue())
negoData.setComponentByPosition(i, negoToken)
i += 1
request = TSRequest()
request.setComponentByName("version", univ.Integer(2).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
if i > 0:
request.setComponentByName("negoTokens", negoData)
if not auth_info is None:
request.setComponentByName("authInfo", univ.OctetString(auth_info).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
if not pub_key_auth is None:
request.setComponentByName("pubKeyAuth", univ.OctetString(pub_key_auth).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
return der_encoder.encode(request)
def decode_der_trequest(s):
"""
@summary: Decode the stream as
@param s: {str}
"""
return der_decoder.decode(s, asn1Spec=TSRequest())[0]
def getNegoTokens(tRequest):
negoData = tRequest.getComponentByName("negoTokens")
return [Stream(negoData.getComponentByPosition(i).getComponentByPosition(0).asOctets()) for i in range(len(negoData))]
def encode_der_tcredentials(domain, username, password):
passwordCred = TSPasswordCreds()
passwordCred.setComponentByName("domainName", domain)
passwordCred.setComponentByName("userName", username)
passwordCred.setComponentByName("password", password)
credentials = TSCredentials()
credentials.setComponentByName("credType", 1)
credentials.setComponentByName("credentials", der_encoder.encode(passwordCred))
return der_encoder.encode(credentials)
async def connect(reader, writer, authentication_protocol: sspi.IAuthenticationProtocol):
"""
CSSP connection sequence
"""
# send negotiate message
writer.write(encode_der_trequest(nego_types=[authentication_protocol.getNegotiateMessage()]))
await writer.drain()
trequest = decode_der_trequest(await reader.read(1500))
message, interface = authentication_protocol.getAuthenticateMessage(getNegoTokens(trequest)[0])
# get binary certificate
# There is no other way to get certificate that have net been validated
peer_certificate = writer.transport._ssl_protocol._sslpipe.ssl_object._sslobj.getpeercert(True)
x509_certificate = der_decoder.decode(peer_certificate, asn1Spec=X509Certificate())[0]
public_key = x509_certificate.getComponentByName("tbsCertificate").getComponentByName("subjectPublicKeyInfo").getComponentByName("subjectPublicKey").asOctets()
# send back public key encrypted with NTLM
writer.write(encode_der_trequest(nego_types=[message], pub_key_auth=interface.GSS_WrapEx(public_key)))
await writer.drain()
# now check the increment
# sever must respond with the same key incremented to one
trequest = decode_der_trequest(await reader.read(1500))
public_key_inc = interface.GSS_UnWrapEx(trequest.getComponentByName("pubKeyAuth").asOctets())
if int.from_bytes(public_key, "little") + 1 != int.from_bytes(public_key_inc, "little"):
raise error.InvalidExpectedDataException("CSSP : Invalid public key increment")
domain, user, password = authentication_protocol.getEncodedCredentials()
writer.write(encode_der_trequest(auth_info=interface.GSS_WrapEx(encode_der_tcredentials(domain, user, password))))
await writer.drain()

652
rdpy/core/nla/ntlm.py Normal file
View File

@@ -0,0 +1,652 @@
#
# 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/>.
#
"""
@summary: NTLM Authentication
@see: https://msdn.microsoft.com/en-us/library/cc236621.aspx
"""
import hashlib, hmac, struct, datetime
from rdpy.core.nla import sspi
import rdpy.security.pyDes as pyDes
import rdpy.security.rc4 as rc4
from rdpy.security.rsa_wrapper import random
from rdpy.model.message import CompositeType, Buffer, UInt8, UInt16Le, UInt24Le, UInt32Le, sizeof, Stream
from rdpy.model import filetimes, error
class MajorVersion(object):
"""
@see: https://msdn.microsoft.com/en-us/library/cc236654.aspx
@see: https://msdn.microsoft.com/en-us/library/a211d894-21bc-4b8b-86ba-b83d0c167b00#id29
"""
WINDOWS_MAJOR_VERSION_5 = 0x05
WINDOWS_MAJOR_VERSION_6 = 0x06
class MinorVersion(object):
"""
@see: https://msdn.microsoft.com/en-us/library/cc236654.aspx
@see: https://msdn.microsoft.com/en-us/library/a211d894-21bc-4b8b-86ba-b83d0c167b00#id30
"""
WINDOWS_MINOR_VERSION_0 = 0x00
WINDOWS_MINOR_VERSION_1 = 0x01
WINDOWS_MINOR_VERSION_2 = 0x02
WINDOWS_MINOR_VERSION_3 = 0x03
class NTLMRevision(object):
"""
@see: https://msdn.microsoft.com/en-us/library/cc236654.aspx
"""
NTLMSSP_REVISION_W2K3 = 0x0F
class Negotiate(object):
"""
@see: https://msdn.microsoft.com/en-us/library/cc236650.aspx
"""
NTLMSSP_NEGOTIATE_56 = 0x80000000
NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000
NTLMSSP_NEGOTIATE_128 = 0x20000000
NTLMSSP_NEGOTIATE_VERSION = 0x02000000
NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000
NTLMSSP_REQUEST_NON_NT_SESSION_KEY = 0x00400000
NTLMSSP_NEGOTIATE_IDENTIFY = 0x00100000
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000
NTLMSSP_TARGET_TYPE_SERVER = 0x00020000
NTLMSSP_TARGET_TYPE_DOMAIN = 0x00010000
NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000
NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000
NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000
NTLMSSP_NEGOTIATE_NTLM = 0x00000200
NTLMSSP_NEGOTIATE_LM_KEY = 0x00000080
NTLMSSP_NEGOTIATE_DATAGRAM = 0x00000040
NTLMSSP_NEGOTIATE_SEAL = 0x00000020
NTLMSSP_NEGOTIATE_SIGN = 0x00000010
NTLMSSP_REQUEST_TARGET = 0x00000004
NTLM_NEGOTIATE_OEM = 0x00000002
NTLMSSP_NEGOTIATE_UNICODE = 0x00000001
class AvId(object):
"""
@see: https://msdn.microsoft.com/en-us/library/cc236646.aspx
"""
MsvAvEOL = 0x0000
MsvAvNbComputerName = 0x0001
MsvAvNbDomainName = 0x0002
MsvAvDnsComputerName = 0x0003
MsvAvDnsDomainName = 0x0004
MsvAvDnsTreeName = 0x0005
MsvAvFlags = 0x0006
MsvAvTimestamp = 0x0007
MsvAvSingleHost = 0x0008
MsvAvTargetName = 0x0009
MsvChannelBindings = 0x000A
def getPayLoadField(message, length, bufferOffset):
if length == 0:
return None
offset = sizeof(message) - sizeof(message.Payload)
start = bufferOffset - offset
end = start + length
return message.Payload.value[start:end]
class Version(CompositeType):
"""
@summary: Version structure as describe in NTLM spec
@see: https://msdn.microsoft.com/en-us/library/cc236654.aspx
"""
def __init__(self, conditional):
CompositeType.__init__(self, conditional = conditional)
self.ProductMajorVersion = UInt8(MajorVersion.WINDOWS_MAJOR_VERSION_6)
self.ProductMinorVersion = UInt8(MinorVersion.WINDOWS_MINOR_VERSION_0)
self.ProductBuild = UInt16Le(6002)
self.Reserved = UInt24Le()
self.NTLMRevisionCurrent = UInt8(NTLMRevision.NTLMSSP_REVISION_W2K3)
class AvPair(CompositeType):
"""
@see: https://msdn.microsoft.com/en-us/library/cc236646.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.AvId = UInt16Le()
self.AvLen = UInt16Le(lambda: sizeof(self.Value))
self.Value = Buffer(read_len=lambda: self.AvLen.value)
class MessageSignatureEx(CompositeType):
"""
@summary: Signature for message
@see: https://msdn.microsoft.com/en-us/library/cc422952.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.Version = UInt32Le(0x00000001, constant = True)
self.Checksum = Buffer(read_len=lambda: 8)
self.SeqNum = UInt32Le()
class NegotiateMessage(CompositeType):
"""
@summary: Message send from client to server to negotiate capability of NTLM Authentication
@see: https://msdn.microsoft.com/en-us/library/cc236641.aspx
"""
def __init__(self):
super().__init__()
self.Signature = Buffer(b"NTLMSSP\x00", read_len=lambda: 8, constant=True)
self.MessageType = UInt32Le(0x00000001, constant=True)
self.NegotiateFlags = UInt32Le()
self.DomainNameLen = UInt16Le()
self.DomainNameMaxLen = UInt16Le(lambda:self.DomainNameLen.value)
self.DomainNameBufferOffset = UInt32Le()
self.WorkstationLen = UInt16Le()
self.WorkstationMaxLen = UInt16Le(lambda:self.WorkstationLen.value)
self.WorkstationBufferOffset = UInt32Le()
self.Version = Version(conditional = lambda:(self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION))
self.Payload = Buffer()
class ChallengeMessage(CompositeType):
"""
@summary: Message send from server to client contains server challenge
@see: https://msdn.microsoft.com/en-us/library/cc236642.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.Signature = Buffer(b"NTLMSSP\x00", read_len=lambda: 8, constant=True)
self.MessageType = UInt32Le(0x00000002, constant=True)
self.TargetNameLen = UInt16Le()
self.TargetNameMaxLen = UInt16Le(lambda:self.TargetNameLen.value)
self.TargetNameBufferOffset = UInt32Le()
self.NegotiateFlags = UInt32Le()
self.ServerChallenge = Buffer(read_len=lambda: 8)
self.Reserved = Buffer(b"\x00" * 8, read_len=lambda: 8)
self.TargetInfoLen = UInt16Le()
self.TargetInfoMaxLen = UInt16Le(lambda: self.TargetInfoLen.value)
self.TargetInfoBufferOffset = UInt32Le()
self.Version = Version(conditional=lambda: (self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION))
self.Payload = Buffer()
def getTargetName(self):
return getPayLoadField(self, self.TargetNameLen.value, self.TargetNameBufferOffset.value)
def getTargetInfo(self):
return getPayLoadField(self, self.TargetInfoLen.value, self.TargetInfoBufferOffset.value)
def getTargetInfoAsAvPairArray(self):
"""
@summary: Parse Target info field to retrieve array of AvPair
@return: {map(AvId, str)}
"""
result = {}
s = Stream(self.getTargetInfo())
while(True):
avPair = AvPair()
s.read_type(avPair)
if avPair.AvId.value == AvId.MsvAvEOL:
return result
result[avPair.AvId.value] = avPair.Value.value
class AuthenticateMessage(CompositeType):
"""
@summary: Last message in ntlm authentication
@see: https://msdn.microsoft.com/en-us/library/cc236643.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.Signature = Buffer(b"NTLMSSP\x00", read_len=lambda: 8, constant = True)
self.MessageType = UInt32Le(0x00000003, constant=True)
self.LmChallengeResponseLen = UInt16Le()
self.LmChallengeResponseMaxLen = UInt16Le(lambda:self.LmChallengeResponseLen.value)
self.LmChallengeResponseBufferOffset = UInt32Le()
self.NtChallengeResponseLen = UInt16Le()
self.NtChallengeResponseMaxLen = UInt16Le(lambda:self.NtChallengeResponseLen.value)
self.NtChallengeResponseBufferOffset = UInt32Le()
self.DomainNameLen = UInt16Le()
self.DomainNameMaxLen = UInt16Le(lambda:self.DomainNameLen.value)
self.DomainNameBufferOffset = UInt32Le()
self.UserNameLen = UInt16Le()
self.UserNameMaxLen = UInt16Le(lambda:self.UserNameLen.value)
self.UserNameBufferOffset = UInt32Le()
self.WorkstationLen = UInt16Le()
self.WorkstationMaxLen = UInt16Le(lambda:self.WorkstationLen.value)
self.WorkstationBufferOffset = UInt32Le()
self.EncryptedRandomSessionLen = UInt16Le()
self.EncryptedRandomSessionMaxLen = UInt16Le(lambda:self.EncryptedRandomSessionLen.value)
self.EncryptedRandomSessionBufferOffset = UInt32Le()
self.NegotiateFlags = UInt32Le()
self.Version = Version(conditional = lambda:(self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION))
self.MIC = Buffer(b"\x00" * 16, read_len=lambda: 16)
self.Payload = Buffer()
def getUserName(self):
return getPayLoadField(self, self.UserNameLen.value, self.UserNameBufferOffset.value)
def getDomainName(self):
return getPayLoadField(self, self.DomainNameLen.value, self.DomainNameBufferOffset.value)
def getLmChallengeResponse(self):
return getPayLoadField(self, self.LmChallengeResponseLen.value, self.LmChallengeResponseBufferOffset.value)
def getNtChallengeResponse(self):
return getPayLoadField(self, self.NtChallengeResponseLen.value, self.NtChallengeResponseBufferOffset.value)
def getEncryptedRandomSession(self):
return getPayLoadField(self, self.EncryptedRandomSessionLen.value, self.EncryptedRandomSessionBufferOffset.value)
def createAuthenticationMessage(NegFlag, domain, user, NtChallengeResponse, LmChallengeResponse, EncryptedRandomSessionKey, Workstation):
"""
@summary: Create an Authenticate Message
@param domain: {str} domain microsoft
@param user: {str} user microsoft
@param NtChallengeResponse: {str} Challenge response
@param LmChallengeResponse: {str} domain microsoft
@param EncryptedRandomSessionKey: {str} EncryptedRandomSessionKey
"""
message = AuthenticateMessage()
message.NegotiateFlags.value = NegFlag
#fill message
offset = sizeof(message)
message.DomainNameLen.value = len(domain)
message.DomainNameBufferOffset.value = offset
message.Payload.value += domain
offset += len(domain)
message.UserNameLen.value = len(user)
message.UserNameBufferOffset.value = offset
message.Payload.value += user
offset += len(user)
message.WorkstationLen.value = len(Workstation)
message.WorkstationBufferOffset.value = offset
message.Payload.value += Workstation
offset += len(Workstation)
message.LmChallengeResponseLen.value = len(LmChallengeResponse)
message.LmChallengeResponseBufferOffset.value = offset
message.Payload.value += LmChallengeResponse
offset += len(LmChallengeResponse)
message.NtChallengeResponseLen.value = len(NtChallengeResponse)
message.NtChallengeResponseBufferOffset.value = offset
message.Payload.value += NtChallengeResponse
offset += len(NtChallengeResponse)
message.EncryptedRandomSessionLen.value = len(EncryptedRandomSessionKey)
message.EncryptedRandomSessionBufferOffset.value = offset
message.Payload.value += EncryptedRandomSessionKey
offset += len(EncryptedRandomSessionKey)
return message
def expandDesKey(key):
"""
@summary: Expand the key from a 7-byte password key into a 8-byte DES key
"""
s = chr(((ord(key[0]) >> 1) & 0x7f) << 1)
s = s + chr(((ord(key[0]) & 0x01) << 6 | ((ord(key[1]) >> 2) & 0x3f)) << 1)
s = s + chr(((ord(key[1]) & 0x03) << 5 | ((ord(key[2]) >> 3) & 0x1f)) << 1)
s = s + chr(((ord(key[2]) & 0x07) << 4 | ((ord(key[3]) >> 4) & 0x0f)) << 1)
s = s + chr(((ord(key[3]) & 0x0f) << 3 | ((ord(key[4]) >> 5) & 0x07)) << 1)
s = s + chr(((ord(key[4]) & 0x1f) << 2 | ((ord(key[5]) >> 6) & 0x03)) << 1)
s = s + chr(((ord(key[5]) & 0x3f) << 1 | ((ord(key[6]) >> 7) & 0x01)) << 1)
s = s + chr((ord(key[6]) & 0x7f) << 1)
return s
def CurrentFileTimes():
"""
@summary: Current File times in 64 bits
@return : {str[8]}
"""
return struct.pack("Q", filetimes.dt_to_filetime(datetime.datetime.now()))
def DES(key, data):
"""
@summary: DES use in microsoft specification
@param key: {str} Des key on 56 bits or 7 bytes
@param data: {str} data to encrypt
"""
return pyDes.des(expandDesKey(key)).encrypt(data)
def DESL(key, data):
"""
@summary: an krosoft security function (triple des = des + des + des ;-))
@param key: {str} Des key
@param data: {str} encrypted data
"""
return DES(key[0:7], data) + DES(key[7:14], data) + DES(key[14:16] + "\x00" * 5, data)
def UNICODE(s):
"""
@param s: source
@return: {str} encoded in unicode
"""
return s.encode('utf-16le')
def MD4(s):
"""
@summary: compute the md4 sum
@param s: {str} input data
@return: {str} MD4(s)
"""
return hashlib.new('md4', s).digest()
def MD5(s):
"""
@summary: compute the md5 sum
@param s: {str} input data
@return: {str} MD5(s)
"""
return hashlib.new('md5', s).digest()
def Z(m):
"""
@summary: fill m zero in string
@param m: {int} size of string
@return: \x00 * m
"""
return b"\x00" * m
def RC4K(key, plaintext):
"""
@summary: Context free of rc4 encoding
@param key: {str} key
@param plaintext: {str} plaintext
@return {str} encrypted text
"""
return rc4.crypt(rc4.RC4Key(key), plaintext)
def KXKEYv2(SessionBaseKey, LmChallengeResponse, ServerChallenge):
"""
@summary: Key eXchange Key for NTLMv2
@param SessionBaseKey: {str} computed by NTLMv1Anthentication or NTLMv2Authenticate function
@param LmChallengeResponse : {str} computed by NTLMv1Anthentication or NTLMv2Authenticate function
@param ServerChallenge : {str} Server chanllenge come from ChallengeMessage
@see: https://msdn.microsoft.com/en-us/library/cc236710.aspx
"""
return SessionBaseKey
def SEALKEY(ExportedSessionKey, client):
if client:
return MD5(ExportedSessionKey + b"session key to client-to-server sealing key magic constant\0")
else:
return MD5(ExportedSessionKey + b"session key to server-to-client sealing key magic constant\0")
def SIGNKEY(ExportedSessionKey, client):
if client:
return MD5(ExportedSessionKey + b"session key to client-to-server signing key magic constant\0")
else:
return MD5(ExportedSessionKey + b"session key to server-to-client signing key magic constant\0")
def HMAC_MD5(key, data):
"""
@summary: classic HMAC algorithm with MD5 sum
@param key: {str} key
@param data: {str} data
"""
return hmac.new(key, data, hashlib.md5).digest()
def NTOWFv2(Passwd, User, UserDom):
"""
@summary: Version 2 of NTLM hash function
@param Passwd: {str} Password
@param User: {str} Username
@param UserDom: {str} microsoft domain
@see: https://msdn.microsoft.com/en-us/library/cc236700.aspx
"""
return HMAC_MD5(MD4(UNICODE(Passwd)), UNICODE(User.upper() + UserDom))
def LMOWFv2(Passwd, User, UserDom):
"""
@summary: Same as NTOWFv2
@param Passwd: {str} Password
@param User: {str} Username
@param UserDom: {str} microsoft domain
@see: https://msdn.microsoft.com/en-us/library/cc236700.aspx
"""
return NTOWFv2(Passwd, User, UserDom)
def ComputeResponsev2(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ClientChallenge, Time, ServerName):
"""
@summary: process NTLMv2 Authenticate hash
@param NegFlg: {int} Negotiation flags come from challenge message
@see: https://msdn.microsoft.com/en-us/library/cc236700.aspx
"""
Responserversion = b"\x01"
HiResponserversion = b"\x01"
temp = Responserversion + HiResponserversion + Z(6) + Time + ClientChallenge + Z(4) + ServerName
NTProofStr = HMAC_MD5(ResponseKeyNT, ServerChallenge + temp)
NtChallengeResponse = NTProofStr + temp
LmChallengeResponse = HMAC_MD5(ResponseKeyLM, ServerChallenge + ClientChallenge) + ClientChallenge
SessionBaseKey = HMAC_MD5(ResponseKeyNT, NTProofStr)
return NtChallengeResponse, LmChallengeResponse, SessionBaseKey
def MAC(handle, SigningKey, SeqNum, Message):
"""
@summary: generate signature for application message
@param handle: {rc4.RC4Key} handle on crypt
@param SigningKey: {str} Signing key
@param SeqNum: {int} Sequence number
@param Message: Message to sign
@see: https://msdn.microsoft.com/en-us/library/cc422952.aspx
"""
signature = MessageSignatureEx()
signature.SeqNum.value = SeqNum
#write the SeqNum
s = Stream()
s.write_type(signature.SeqNum)
signature.Checksum.value = rc4.crypt(handle, HMAC_MD5(SigningKey, s.getvalue() + Message)[:8])
return signature
def MIC(ExportedSessionKey, negotiateMessage, challengeMessage, authenticateMessage):
"""
@summary: Compute MIC signature
@param negotiateMessage: {NegotiateMessage}
@param challengeMessage: {ChallengeMessage}
@param authenticateMessage: {AuthenticateMessage}
@return: {str} signature
@see: https://msdn.microsoft.com/en-us/library/cc236676.aspx
"""
s = Stream()
s.write_type((negotiateMessage, challengeMessage, authenticateMessage))
return HMAC_MD5(ExportedSessionKey, s.getvalue())
class NTLMv2(sspi.IAuthenticationProtocol):
"""
@summary: Handle NTLMv2 Authentication
"""
def __init__(self, domain: str, user: str, password: str):
self._domain = domain
self._user = user
self._password = password
self._enableUnicode = False
#https://msdn.microsoft.com/en-us/library/cc236700.aspx
self._ResponseKeyNT = NTOWFv2(password, user, domain)
self._ResponseKeyLM = LMOWFv2(password, user, domain)
#For MIC computation
self._negotiateMessage = None
self._challengeMessage = None
self._authenticateMessage = None
def getNegotiateMessage(self):
"""
@summary: generate first handshake messgae
"""
self._negotiateMessage = NegotiateMessage()
self._negotiateMessage.NegotiateFlags.value = (Negotiate.NTLMSSP_NEGOTIATE_KEY_EXCH |
Negotiate.NTLMSSP_NEGOTIATE_128 |
Negotiate.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY |
Negotiate.NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
Negotiate.NTLMSSP_NEGOTIATE_NTLM |
Negotiate.NTLMSSP_NEGOTIATE_SEAL |
Negotiate.NTLMSSP_NEGOTIATE_SIGN |
Negotiate.NTLMSSP_REQUEST_TARGET |
Negotiate.NTLMSSP_NEGOTIATE_UNICODE)
return self._negotiateMessage
def getAuthenticateMessage(self, s):
"""
@summary: Client last handshake message
@param s: {Stream} challenge message stream
@return: {(AuthenticateMessage, NTLMv2SecurityInterface)} Last handshake message and security interface use to encrypt
@see: https://msdn.microsoft.com/en-us/library/cc236676.aspx
"""
self._challengeMessage = ChallengeMessage()
s.read_type(self._challengeMessage)
ServerChallenge = self._challengeMessage.ServerChallenge.value
ClientChallenge = random(64)
computeMIC = False
ServerName = self._challengeMessage.getTargetInfo()
infos = self._challengeMessage.getTargetInfoAsAvPairArray()
if AvId.MsvAvTimestamp in infos.keys():
Timestamp = infos[AvId.MsvAvTimestamp]
computeMIC = True
else:
Timestamp = CurrentFileTimes()
NtChallengeResponse, LmChallengeResponse, SessionBaseKey = ComputeResponsev2(self._ResponseKeyNT, self._ResponseKeyLM, ServerChallenge, ClientChallenge, Timestamp, ServerName)
KeyExchangeKey = KXKEYv2(SessionBaseKey, LmChallengeResponse, ServerChallenge)
ExportedSessionKey = random(128)
EncryptedRandomSessionKey = RC4K(KeyExchangeKey, ExportedSessionKey)
domain, user = self._domain, self._user
if self._challengeMessage.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_UNICODE:
self._enableUnicode = True
domain, user = UNICODE(domain), UNICODE(user)
self._authenticateMessage = createAuthenticationMessage(self._challengeMessage.NegotiateFlags.value, domain, user, NtChallengeResponse, LmChallengeResponse, EncryptedRandomSessionKey, b"")
if computeMIC:
self._authenticateMessage.MIC.value = MIC(ExportedSessionKey, self._negotiateMessage, self._challengeMessage, self._authenticateMessage)
else:
self._authenticateMessage.MIC._conditional = lambda:False
ClientSigningKey = SIGNKEY(ExportedSessionKey, True)
ServerSigningKey = SIGNKEY(ExportedSessionKey, False)
ClientSealingKey = SEALKEY(ExportedSessionKey, True)
ServerSealingKey = SEALKEY(ExportedSessionKey, False)
interface = NTLMv2SecurityInterface(rc4.RC4Key(ClientSealingKey), rc4.RC4Key(ServerSealingKey), ClientSigningKey, ServerSigningKey)
return self._authenticateMessage, interface
def getEncodedCredentials(self):
"""
@summary: return encoded credentials accorded with authentication protocol nego
@return: (domain, username, password)
"""
if self._enableUnicode:
return UNICODE(self._domain), UNICODE(self._user), UNICODE(self._password)
else:
return self._domain, self._user, self._password
class NTLMv2SecurityInterface(sspi.IGenericSecurityService):
"""
@summary: Generic Security Service for NTLM session
"""
def __init__(self, encryptHandle, decryptHandle, signingKey, verifyKey):
"""
@param encryptHandle: {rc4.RC4Key} rc4 keystream for encrypt phase
@param decryptHandle: {rc4.RC4Key} rc4 keystream for decrypt phase
@param signingKey: {str} signingKey
@param verifyKey: {str} verifyKey
"""
self._encryptHandle = encryptHandle
self._decryptHandle = decryptHandle
self._signingKey = signingKey
self._verifyKey = verifyKey
self._seqNum = 0
def GSS_WrapEx(self, data):
"""
@summary: Encrypt function for NTLMv2 security service
@param data: data to encrypt
@return: {str} encrypted data
"""
encryptedData = rc4.crypt(self._encryptHandle, data)
signature = MAC(self._encryptHandle, self._signingKey, self._seqNum, data)
self._seqNum += 1
s = Stream()
s.write_type(signature)
return s.getvalue() + encryptedData
def GSS_UnWrapEx(self, data):
"""
@summary: decrypt data with key exchange in Authentication protocol
@param data: {str}
"""
signature, message = Stream(data).read_type((MessageSignatureEx(), Buffer()))
#decrypt message
plaintextMessage = rc4.crypt(self._decryptHandle, message.value)
checksum = rc4.crypt(self._decryptHandle, signature.Checksum.value)
# recompute checksum
t = Stream()
t.write_type(signature.SeqNum)
verify = HMAC_MD5(self._verifyKey, t.getvalue() + plaintextMessage)[:8]
if verify != checksum:
raise error.InvalidExpectedDataException("NTLMv2SecurityInterface : Invalid checksum")
return plaintextMessage

69
rdpy/core/nla/sspi.py Normal file
View File

@@ -0,0 +1,69 @@
#
# 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/>.
#
"""
@summary: security service provider interface (Microsoft)
"""
from rdpy.model.error import CallPureVirtualFuntion
class IAuthenticationProtocol(object):
"""
@summary: generic class for authentication Protocol (ex: ntlmv2, SPNEGO or kerberos)
"""
def getNegotiateMessage(self):
"""
@summary: Client first handshake message for authentication protocol
@return: {object} first handshake message
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getNegotiateMessage", "IAuthenticationProtocol"))
def getAuthenticateMessage(self, s):
"""
@summary: Client last handshake message
@param s: {Stream} challenge message stream
@return: {(object, IGenericSecurityService)} Last handshake message and interface for application
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getAuthenticateMessage", "IAuthenticationProtocol"))
def getEncodedCredentials(self):
"""
@summary: return encoded credentials accorded with authentication protocol nego
@return: (domain, username, password)
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getEncodedCredentials", "IAuthenticationProtocol"))
class IGenericSecurityService(object):
"""
@summary: use by application from authentification protocol
@see: http://www.rfc-editor.org/rfc/rfc2743.txt
"""
def GSS_WrapEx(self, data):
"""
@summary: encrypt data with key exchange in Authentication protocol
@param data: {str}
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "GSS_WrapEx", "IGenericSecurityService"))
def GSS_UnWrapEx(self, data):
"""
@summary: decrypt data with key exchange in Authentication protocol
@param data: {str}
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "GSS_UnWrapEx", "IGenericSecurityService"))

View File

@@ -16,16 +16,17 @@
# 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.core.error import InvalidExpectedDataException
import rdpy.core.log as log
from rdpy.model.error import InvalidExpectedDataException
import rdpy.model.log as log
"""
Definition of structure use for capabilities nego
Use in PDU layer
"""
from rdpy.core.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
from rdpy.model.type import CompositeType, CallableValue, Buffer, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
class CapsType(object):
"""
@summary: Different type of capabilities
@@ -240,7 +241,7 @@ class Capability(CompositeType):
return c(readLen = self.lengthCapability - 4)
log.debug("unknown Capability type : %s"%hex(self.capabilitySetType.value))
#read entire packet
return String(readLen = self.lengthCapability - 4)
return Buffer(readLen =self.lengthCapability - 4)
if capability is None:
capability = FactoryType(CapabilityFactory)
@@ -308,7 +309,7 @@ class OrderCapability(CompositeType):
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.terminalDescriptor = String("\x00" * 16, readLen = UInt8(16))
self.terminalDescriptor = Buffer("\x00" * 16, readLen = CallableValue(16))
self.pad4octetsA = UInt32Le(0)
self.desktopSaveXGranularity = UInt16Le(1)
self.desktopSaveYGranularity = UInt16Le(20)
@@ -316,7 +317,7 @@ class OrderCapability(CompositeType):
self.maximumOrderLevel = UInt16Le(1)
self.numberFonts = UInt16Le()
self.orderFlags = UInt16Le(OrderFlag.NEGOTIATEORDERSUPPORT)
self.orderSupport = ArrayType(UInt8, init = [UInt8(0) for _ in range (0, 32)], readLen = UInt8(32))
self.orderSupport = ArrayType(UInt8, init = [UInt8(0) for _ in range (0, 32)], readLen = CallableValue(32))
self.textFlags = UInt16Le()
self.orderSupportExFlags = UInt16Le()
self.pad4octetsB = UInt32Le()
@@ -388,7 +389,7 @@ class InputCapability(CompositeType):
#same value as gcc.ClientCoreSettings.keyboardFnKeys
self.keyboardFunctionKey = UInt32Le()
#same value as gcc.ClientCoreSettingrrs.imeFileName
self.imeFileName = String("\x00" * 64, readLen = UInt8(64))
self.imeFileName = Buffer("\x00" * 64, readLen = CallableValue(64))
class BrushCapability(CompositeType):
"""
@@ -412,7 +413,7 @@ class GlyphCapability(CompositeType):
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.glyphCache = ArrayType(CacheEntry, init = [CacheEntry() for _ in range(0,10)], readLen = UInt8(10))
self.glyphCache = ArrayType(CacheEntry, init = [CacheEntry() for _ in range(0,10)], readLen = CallableValue(10))
self.fragCache = UInt32Le()
#all fonts are sent with bitmap format (very expensive)
self.glyphSupportLevel = UInt16Le(GlyphSupport.GLYPH_SUPPORT_NONE)

View File

@@ -22,10 +22,10 @@ Implement the main graphic layer
In this layer are managed all mains bitmap update orders end user inputs
"""
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
from rdpy.model.type import CompositeType, CallableValue, Buffer, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
from rdpy.model.error import InvalidExpectedDataException
import rdpy.model.log as log
from rdpy.core.pdu import caps, order
class PDUType(object):
"""
@@ -160,7 +160,16 @@ class PointerFlag(object):
PTRFLAGS_BUTTON1 = 0x1000
PTRFLAGS_BUTTON2 = 0x2000
PTRFLAGS_BUTTON3 = 0x4000
class PointerExFlag(object):
"""
@summary: Use in Pointer event
@see: https://msdn.microsoft.com/en-us/library/cc240587.aspx
"""
PTRXFLAGS_DOWN = 0x8000
PTRXFLAGS_BUTTON1 = 0x0001
PTRXFLAGS_BUTTON2 = 0x0002
class KeyboardFlag(object):
"""
@summary: Use in scan code key event
@@ -202,6 +211,16 @@ class Display(object):
SUPPRESS_DISPLAY_UPDATES = 0x00
ALLOW_DISPLAY_UPDATES = 0x01
class ToogleFlag(object):
"""
@summary: Use to known state of keyboard
@see: https://msdn.microsoft.com/en-us/library/cc240588.aspx
"""
TS_SYNC_SCROLL_LOCK = 0x00000001
TS_SYNC_NUM_LOCK = 0x00000002
TS_SYNC_CAPS_LOCK = 0x00000004
TS_SYNC_KANA_LOCK = 0x00000008
class ErrorInfo(object):
"""
@summary: Error code use in Error info PDU
@@ -413,8 +432,6 @@ class ErrorInfo(object):
ERRINFO_VCDATATOOLONG : "The size of a received Virtual Channel PDU (section 2.2.6.1) exceeds the chunking size specified in the Virtual Channel Capability Set (section 2.2.7.1.10).",
}
class ShareControlHeader(CompositeType):
"""
@summary: PDU share control header
@@ -429,7 +446,8 @@ class ShareControlHeader(CompositeType):
#share control header
self.totalLength = UInt16Le(totalLength)
self.pduType = UInt16Le(pduType)
self.PDUSource = UInt16Le(userId)
#for xp sp3 and deactiveallpdu PDUSource may not be present
self.PDUSource = UInt16Le(userId, optional = True)
class ShareDataHeader(CompositeType):
"""
@@ -460,10 +478,10 @@ class PDU(CompositeType):
"""
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
return c()
return c(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
log.debug("unknown PDU type : %s"%hex(self.shareControlHeader.pduType.value))
#read entire packet
return String()
return Buffer(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
if pduMessage is None:
pduMessage = FactoryType(PDUMessageFactory)
@@ -480,12 +498,12 @@ class DemandActivePDU(CompositeType):
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.shareId = UInt32Le()
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets)))
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
self.sourceDescriptor = Buffer("rdpy", readLen = self.lengthSourceDescriptor)
self.numberCapabilities = UInt16Le(lambda:len(self.capabilitySets._array))
self.pad2Octets = UInt16Le()
self.capabilitySets = ArrayType(caps.Capability, readLen = self.numberCapabilities)
@@ -499,13 +517,13 @@ class ConfirmActivePDU(CompositeType):
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.shareId = UInt32Le()
self.originatorId = UInt16Le(0x03EA, constant = True)
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets)))
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
self.sourceDescriptor = Buffer("rdpy", readLen = self.lengthSourceDescriptor)
self.numberCapabilities = UInt16Le(lambda:len(self.capabilitySets._array))
self.pad2Octets = UInt16Le()
self.capabilitySets = ArrayType(caps.Capability, readLen = self.numberCapabilities)
@@ -518,11 +536,13 @@ class DeactiveAllPDU(CompositeType):
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DEACTIVATEALLPDU
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
#in old version this packet is empty i don't know
#and not specified
CompositeType.__init__(self, optional = True, readLen = readLen)
self.shareId = UInt32Le()
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
self.sourceDescriptor = Buffer("rdpy", readLen = self.lengthSourceDescriptor)
class DataPDU(CompositeType):
"""
@@ -531,19 +551,19 @@ class DataPDU(CompositeType):
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
def __init__(self, pduData = None, shareId = 0):
CompositeType.__init__(self)
def __init__(self, pduData = None, shareId = 0, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), lambda:self.pduData.__class__._PDUTYPE2_, shareId)
def PDUDataFactory():
"""
@summary: Create object in accordance self.shareDataHeader.pduType2 value
"""
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU]:
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU, SaveSessionInfoPDU]:
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
return c()
return c(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value))
return String()
return Buffer(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
if pduData is None:
pduData = FactoryType(PDUDataFactory)
@@ -652,8 +672,8 @@ class PersistentListPDU(CompositeType):
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST
def __init__(self, userId = 0, shareId = 0):
CompositeType.__init__(self)
def __init__(self, userId = 0, shareId = 0, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.numEntriesCache0 = UInt16Le()
self.numEntriesCache1 = UInt16Le()
self.numEntriesCache2 = UInt16Le()
@@ -667,7 +687,7 @@ class PersistentListPDU(CompositeType):
self.bitMask = UInt8()
self.pad2 = UInt8()
self.pad3 = UInt16Le()
self.entries = ArrayType(PersistentListEntry, readLen = UInt16Le(lambda:(self.numEntriesCache0 + self.numEntriesCache1 + self.numEntriesCache2 + self.numEntriesCache3 + self.numEntriesCache4)))
self.entries = ArrayType(PersistentListEntry, readLen = CallableValue(lambda:(self.numEntriesCache0 + self.numEntriesCache1 + self.numEntriesCache2 + self.numEntriesCache3 + self.numEntriesCache4)))
class ClientInputEventPDU(CompositeType):
"""
@@ -676,8 +696,8 @@ class ClientInputEventPDU(CompositeType):
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.numEvents = UInt16Le(lambda:len(self.slowPathInputEvents._array))
self.pad2Octets = UInt16Le()
self.slowPathInputEvents = ArrayType(SlowPathInputEvent, readLen = self.numEvents)
@@ -688,8 +708,8 @@ class ShutdownRequestPDU(CompositeType):
client -> server
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
class ShutdownDeniedPDU(CompositeType):
"""
@@ -697,8 +717,8 @@ class ShutdownDeniedPDU(CompositeType):
server -> client
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
class InclusiveRectangle(CompositeType):
"""
@@ -758,9 +778,9 @@ class UpdateDataPDU(CompositeType):
"""
for c in [BitmapUpdateDataPDU]:
if self.updateType.value == c._UPDATE_TYPE_:
return c()
return c(readLen = CallableValue(readLen.value - 2))
log.debug("unknown PDU update data type : %s"%hex(self.updateType.value))
return String()
return Buffer(readLen = CallableValue(readLen.value - 2))
if updateData is None:
updateData = FactoryType(UpdateDataFactory, conditional = lambda:(self.updateType.value != UpdateType.UPDATETYPE_SYNCHRONIZE))
@@ -768,7 +788,19 @@ class UpdateDataPDU(CompositeType):
raise InvalidExpectedDataException("Try to send an invalid data update PDU")
self.updateData = updateData
class SaveSessionInfoPDU(CompositeType):
"""
@see: https://msdn.microsoft.com/en-us/library/cc240636.aspx
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SAVE_SESSION_INFO
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.infoType = UInt32Le()
#TODO parse info data
self.infoData = Buffer()
class FastPathUpdatePDU(CompositeType):
"""
@summary: Fast path update PDU packet
@@ -786,9 +818,9 @@ class FastPathUpdatePDU(CompositeType):
"""
for c in [FastPathBitmapUpdateDataPDU]:
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
return c()
return c(readLen = self.size)
log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf))
return String()
return Buffer(readLen = self.size)
if updateData is None:
updateData = FactoryType(UpdateDataFactory)
@@ -818,8 +850,8 @@ class OrderUpdateDataPDU(CompositeType):
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
@todo: not implemented yet but need it
"""
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.pad2OctetsA = UInt16Le()
self.numberOrders = UInt16Le(lambda:len(self.orderData._array))
self.pad2OctetsB = UInt16Le()
@@ -870,7 +902,7 @@ class BitmapData(CompositeType):
self.flags = UInt16Le()
self.bitmapLength = UInt16Le(lambda:(sizeof(self.bitmapComprHdr) + sizeof(self.bitmapDataStream)))
self.bitmapComprHdr = BitmapCompressedDataHeader(bodySize = lambda:sizeof(self.bitmapDataStream), scanWidth = lambda:self.width.value, uncompressedSize = lambda:(self.width.value * self.height.value * self.bitsPerPixel.value), conditional = lambda:((self.flags.value & BitmapFlag.BITMAP_COMPRESSION) and not (self.flags.value & BitmapFlag.NO_BITMAP_COMPRESSION_HDR)))
self.bitmapDataStream = String(bitmapDataStream, readLen = UInt16Le(lambda:(self.bitmapLength.value if (not self.flags.value & BitmapFlag.BITMAP_COMPRESSION or self.flags.value & BitmapFlag.NO_BITMAP_COMPRESSION_HDR) else self.bitmapComprHdr.cbCompMainBodySize.value)))
self.bitmapDataStream = Buffer(bitmapDataStream, readLen = CallableValue(lambda:(self.bitmapLength.value if (not self.flags.value & BitmapFlag.BITMAP_COMPRESSION or self.flags.value & BitmapFlag.NO_BITMAP_COMPRESSION_HDR) else self.bitmapComprHdr.cbCompMainBodySize.value)))
class FastPathBitmapUpdateDataPDU(CompositeType):
"""
@@ -879,8 +911,8 @@ class FastPathBitmapUpdateDataPDU(CompositeType):
"""
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.header = UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True)
self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array))
self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles)
@@ -896,11 +928,10 @@ class SlowPathInputEvent(CompositeType):
self.messageType = UInt16Le(lambda:self.slowPathInputData.__class__._INPUT_MESSAGE_TYPE_)
def SlowPathInputDataFactory():
for c in [PointerEvent, ScancodeKeyEvent, UnicodeKeyEvent]:
for c in [PointerEvent, PointerExEvent, ScancodeKeyEvent, UnicodeKeyEvent, SynchronizeEvent]:
if self.messageType.value == c._INPUT_MESSAGE_TYPE_:
return c()
log.debug("unknown slow path input : %s"%hex(self.messageType.value))
return String()
raise InvalidExpectedDataException("unknown slow path input : %s"%hex(self.messageType.value))
if messageData is None:
messageData = FactoryType(SlowPathInputDataFactory)
@@ -908,7 +939,19 @@ class SlowPathInputEvent(CompositeType):
raise InvalidExpectedDataException("try to send an invalid Slow Path Input Event")
self.slowPathInputData = messageData
class SynchronizeEvent(CompositeType):
"""
@summary: Synchronize keyboard
@see: https://msdn.microsoft.com/en-us/library/cc240588.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SYNC
def __init__(self):
CompositeType.__init__(self)
self.pad2Octets = UInt16Le()
self.toggleFlags = UInt32Le()
class PointerEvent(CompositeType):
"""
@summary: Event use to communicate mouse position
@@ -922,6 +965,19 @@ class PointerEvent(CompositeType):
self.xPos = UInt16Le()
self.yPos = UInt16Le()
class PointerExEvent(CompositeType):
"""
@summary: Event use to communicate mouse position
@see: http://msdn.microsoft.com/en-us/library/cc240587.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_MOUSEX
def __init__(self):
CompositeType.__init__(self)
self.pointerFlags = UInt16Le()
self.xPos = UInt16Le()
self.yPos = UInt16Le()
class ScancodeKeyEvent(CompositeType):
"""
@summary: Event use to communicate keyboard informations

View File

@@ -23,11 +23,13 @@ Implement the main graphic layer
In this layer are managed all mains bitmap update orders end user inputs
"""
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 data, caps
from rdpy.model.layer import LayerAutomata
from rdpy.model.error import CallPureVirtualFuntion
from rdpy.model.type import ArrayType
import rdpy.model.log as log
from rdpy.core import tpkt
from rdpy.core.pdu import data, caps
class PDUClientListener(object):
"""
@@ -39,6 +41,13 @@ class PDUClientListener(object):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener"))
def onSessionReady(self):
"""
@summary: Event call when Windows session is ready
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "PDUClientListener"))
def onUpdate(self, rectangles):
"""
@summary: call when a bitmap data is received from update PDU
@@ -167,7 +176,7 @@ class Client(PDULayer):
@param s: Stream
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DEMANDACTIVEPDU:
#not a blocking error because in deactive reactive sequence
@@ -179,6 +188,9 @@ class Client(PDULayer):
for cap in pdu.pduMessage.capabilitySets._array:
self._serverCapabilities[cap.capabilitySetType] = cap
#secure checksum cap here maybe protocol (another) design error
self._transport._enableSecureCheckSum = bool(self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags & caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM)
self.sendConfirmActivePDU()
#send synchronize
@@ -192,7 +204,7 @@ class Client(PDULayer):
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_SYNCHRONIZE:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -208,7 +220,7 @@ class Client(PDULayer):
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_COOPERATE:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -224,7 +236,7 @@ class Client(PDULayer):
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_GRANTED_CONTROL:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -240,7 +252,7 @@ class Client(PDULayer):
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_FONTMAP:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -256,15 +268,16 @@ class Client(PDULayer):
@summary: Main receive function after connection sequence
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
self.readDataPDU(pdu.pduMessage)
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
#use in deactivation-reactivation sequence
#next state is either a capabilities re exchange or disconnection
#http://msdn.microsoft.com/en-us/library/cc240454.aspx
self.setNextState(self.recvDemandActivePDU)
pdus = ArrayType(data.PDU)
s.read_type(pdus)
for pdu in pdus:
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
self.readDataPDU(pdu.pduMessage)
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
#use in deactivation-reactivation sequence
#next state is either a capabilities re exchange or disconnection
#http://msdn.microsoft.com/en-us/library/cc240454.aspx
self.setNextState(self.recvDemandActivePDU)
def recvFastPath(self, secFlag, fastPathS):
"""
@@ -273,25 +286,32 @@ class Client(PDULayer):
@param fastPathS: {Stream} that contain fast path data
@param secFlag: {SecFlags}
"""
fastPathPDU = data.FastPathUpdatePDU()
fastPathS.readType(fastPathPDU)
if fastPathPDU.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
self._listener.onUpdate(fastPathPDU.updateData.rectangles._array)
updates = ArrayType(data.FastPathUpdatePDU)
fastPathS.read_type(updates)
for update in updates:
if update.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
self._listener.onUpdate(update.updateData.rectangles._array)
def readDataPDU(self, dataPDU):
"""
@summary: read a data PDU object
@param dataPDU: DataPDU object
"""
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
#ignore 0 error code because is not an error code
if dataPDU.pduData.errorInfo.value == 0:
return
errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value)
if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo):
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
log.error("INFO PDU : %s"%errorMessage)
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_DENIED:
#may be an event to ask to user
self._transport.close()
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SAVE_SESSION_INFO:
#handle session event
self._listener.onSessionReady()
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_UPDATE:
self.readUpdateDataPDU(dataPDU.pduData)
@@ -312,7 +332,7 @@ class Client(PDULayer):
generalCapability = self._clientCapabilities[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.ENC_SALTED_CHECKSUM
if not self._fastPathSender is None:
generalCapability.extraFlags.value |= caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED
@@ -399,7 +419,7 @@ class Server(PDULayer):
@param s: Stream
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_CONFIRMACTIVEPDU:
#not a blocking error because in deactive reactive sequence
@@ -413,6 +433,9 @@ class Server(PDULayer):
#find use full flag
self._clientFastPathSupported = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED)
#secure checksum cap here maybe protocol (another) design error
self._transport._enableSecureCheckSum = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags & caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM)
self.setNextState(self.recvClientSynchronizePDU)
def recvClientSynchronizePDU(self, s):
@@ -422,7 +445,7 @@ class Server(PDULayer):
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_SYNCHRONIZE:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -437,7 +460,7 @@ class Server(PDULayer):
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_COOPERATE:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -452,7 +475,7 @@ class Server(PDULayer):
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_REQUEST_CONTROL:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -468,7 +491,7 @@ class Server(PDULayer):
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_FONTLIST:
#not a blocking error because in deactive reactive sequence
#input can be send but ignored
@@ -487,7 +510,7 @@ class Server(PDULayer):
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
self.readDataPDU(pdu.pduMessage)
@@ -525,7 +548,7 @@ class Server(PDULayer):
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 | caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED | caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM
inputCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability
inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX

View File

@@ -21,9 +21,9 @@
GDI order structure
"""
from rdpy.core import log
from rdpy.core.error import InvalidExpectedDataException
from rdpy.core.type import CompositeType, UInt8, String, FactoryType, SInt8, SInt16Le
from rdpy.model import log
from rdpy.model.error import InvalidExpectedDataException
from rdpy.model.type import CompositeType, UInt8, Buffer, FactoryType, SInt8, SInt16Le
class ControlFlag(object):
"""
@@ -100,7 +100,7 @@ class PrimaryDrawingOrder(CompositeType):
return c(self.controlFlags)
log.debug("unknown Order type : %s"%hex(self.orderType.value))
#read entire packet
return String()
return Buffer()
if order is None:
order = FactoryType(OrderFactory)

View File

@@ -21,15 +21,30 @@
Use to manage RDP stack in twisted
"""
from rdpy.core import layer
from rdpy.core.error import CallPureVirtualFuntion, InvalidValue
import pdu.layer
import pdu.data
import pdu.caps
import rdpy.core.log as log
import tpkt, x224, mcs, gcc, sec
from rdpy.model import layer
from rdpy.model.error import CallPureVirtualFuntion, InvalidValue
from rdpy.core.pdu.layer import PDUClientListener, PDUServerListener
from rdpy.core.pdu import data
from rdpy.core.pdu import caps
from rdpy.core.pdu import layer as pdu
import rdpy.model.log as log
import rdpy.core.tpkt as tpkt
import rdpy.core.x224 as x224
import rdpy.core.sec as sec
from rdpy.core.t125 import mcs, gcc
from rdpy.core.nla import cssp, ntlm
class RDPClientController(pdu.layer.PDUClientListener):
class SecurityLevel(object):
"""
@summary: RDP security level
"""
RDP_LEVEL_RDP = 0
RDP_LEVEL_SSL = 1
RDP_LEVEL_NLA = 2
class RDPClientController(PDUClientListener):
"""
Manage RDP stack as client
"""
@@ -37,7 +52,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
#list of observer
self._clientObserver = []
#PDU layer
self._pduLayer = pdu.layer.Client(self)
self._pduLayer = pdu.Client(self)
#secure layer
self._secLayer = sec.Client(self._pduLayer)
#multi channel service
@@ -57,7 +72,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
@return: return Protocol layer for twisted
In case of RDP TPKT is the Raw layer
"""
return self._tpktLayer
return cssp.CSSP(self._tpktLayer, ntlm.NTLMv2(self._secLayer._info.domain.value, self._secLayer._info.userName.value, self._secLayer._info.password.value))
def getColorDepth(self):
"""
@@ -84,13 +99,13 @@ class RDPClientController(pdu.layer.PDUClientListener):
@param height: height in pixel of screen
"""
#set screen definition in MCS layer
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).desktopHeight.value = height
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).desktopWidth.value = width
self._mcsLayer._clientSettings.get_block(gcc.MessageType.CS_CORE).desktopHeight.value = height
self._mcsLayer._clientSettings.get_block(gcc.MessageType.CS_CORE).desktopWidth.value = width
def setUsername(self, username):
"""
@summary: Set the username for session
@param username: username of session
@param username: {string} username of session
"""
#username in PDU info packet
self._secLayer._info.userName.value = username
@@ -99,7 +114,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setPassword(self, password):
"""
@summary: Set password for session
@param password: password of session
@param password: {string} password of session
"""
self.setAutologon()
self._secLayer._info.password.value = password
@@ -107,7 +122,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setDomain(self, domain):
"""
@summary: Set the windows domain of session
@param domain: domain of session
@param domain: {string} domain of session
"""
self._secLayer._info.domain.value = domain
@@ -117,6 +132,13 @@ class RDPClientController(pdu.layer.PDUClientListener):
"""
self._secLayer._info.flag |= sec.InfoFlag.INFO_AUTOLOGON
def setAlternateShell(self, appName):
"""
@summary: set application name of app which start at the begining of session
@param appName: {string} application name
"""
self._secLayer._info.alternateShell.value = appName
def setKeyboardLayout(self, layout):
"""
@summary: keyboard layout
@@ -137,12 +159,14 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setSecurityLevel(self, level):
"""
@summary: Request basic security
@param level: {str} (ssl | rdp)
@param level: {SecurityLevel}
"""
if level == "rdp":
if level == SecurityLevel.RDP_LEVEL_RDP:
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP
elif level == "ssl":
elif level == SecurityLevel.RDP_LEVEL_SSL:
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_SSL
elif level == SecurityLevel.RDP_LEVEL_NLA:
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_SSL | x224.Protocols.PROTOCOL_HYBRID
def addClientObserver(self, observer):
"""
@@ -180,6 +204,15 @@ class RDPClientController(pdu.layer.PDUClientListener):
for observer in self._clientObserver:
observer.onReady()
def onSessionReady(self):
"""
@summary: Call when Windows session is ready (connected)
"""
self._isReady = True
#signal all listener
for observer in self._clientObserver:
observer.onSessionReady()
def onClose(self):
"""
@summary: Event call when RDP stack is closed
@@ -200,24 +233,35 @@ class RDPClientController(pdu.layer.PDUClientListener):
return
try:
event = pdu.data.PointerEvent()
if isPressed:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN
if button == 1:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON1
elif button == 2:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON2
elif button == 3:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON3
if button == 4 or button == 5:
event = pdu.data.PointerExEvent()
if isPressed:
event.pointerFlags.value |= pdu.data.PointerExFlag.PTRXFLAGS_DOWN
if button == 4:
event.pointerFlags.value |= pdu.data.PointerExFlag.PTRXFLAGS_BUTTON1
elif button == 5:
event.pointerFlags.value |= pdu.data.PointerExFlag.PTRXFLAGS_BUTTON2
else:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_MOVE
event = pdu.data.PointerEvent()
if isPressed:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN
if button == 1:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON1
elif button == 2:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON2
elif button == 3:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON3
else:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_MOVE
#position
# position
event.xPos.value = x
event.yPos.value = y
#send proper event
# send proper event
self._pduLayer.sendInputEvents([event])
except InvalidValue:
@@ -257,11 +301,12 @@ class RDPClientController(pdu.layer.PDUClientListener):
except InvalidValue:
log.info("try send wheel event with incorrect position")
def sendKeyEventScancode(self, code, isPressed):
def sendKeyEventScancode(self, code, isPressed, extended = False):
"""
@summary: Send a scan code to RDP stack
@param code: scan code
@param isPressed: True if key is pressed and false if it's released
@param extended: {boolean} extended scancode like ctr or win button
"""
if not self._isReady:
return
@@ -269,11 +314,12 @@ class RDPClientController(pdu.layer.PDUClientListener):
try:
event = pdu.data.ScancodeKeyEvent()
event.keyCode.value = code
if isPressed:
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_DOWN
else:
if not isPressed:
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE
if extended:
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED
#send event
self._pduLayer.sendInputEvents([event])
@@ -324,7 +370,8 @@ class RDPClientController(pdu.layer.PDUClientListener):
"""
self._pduLayer.close()
class RDPServerController(pdu.layer.PDUServerListener):
class RDPServerController(PDUServerListener):
"""
@summary: Controller use in server side mode
"""
@@ -347,6 +394,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
self._x224Layer = x224.Server(self._mcsLayer, privateKeyFileName, certificateFileName, False)
#transport packet (protocol layer)
self._tpktLayer = tpkt.TPKT(self._x224Layer)
#fastpath stack
self._pduLayer.initFastPath(self._secLayer)
self._secLayer.initFastPath(self._tpktLayer)
@@ -465,11 +513,11 @@ class RDPServerController(pdu.layer.PDUServerListener):
for event in slowPathInputEvents:
#scan code
if event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_SCANCODE:
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE), bool(event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED))
#unicode
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_UNICODE:
observer.onKeyEventUnicode(event.slowPathInputData.unicode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
#mouse event
#mouse events
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSE:
isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_DOWN
button = 0
@@ -480,6 +528,15 @@ class RDPServerController(pdu.layer.PDUServerListener):
elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_BUTTON3:
button = 3
observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed)
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSEX:
isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_DOWN
button = 0
if event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_BUTTON1:
button = 4
elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_BUTTON2:
button = 5
observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed)
def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@@ -507,8 +564,9 @@ class ClientFactory(layer.RawLayerClientFactory):
@summary: Factory of Client RDP protocol
@param reason: twisted reason
"""
def connectionLost(self, tpktLayer, reason):
def connectionLost(self, csspLayer, reason):
#retrieve controller
tpktLayer = csspLayer._layer
x224Layer = tpktLayer._presentation
mcsLayer = x224Layer._presentation
secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
@@ -533,49 +591,50 @@ class ClientFactory(layer.RawLayerClientFactory):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildObserver", "ClientFactory"))
class ServerFactory(layer.RawLayerServerFactory):
"""
@summary: Factory of Server RDP protocol
"""
def __init__(self, colorDepth, privateKeyFileName = None, certificateFileName = None):
"""
@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
def connectionLost(self, tpktLayer, reason):
"""
@param reason: twisted reason
"""
#retrieve controller
x224Layer = tpktLayer._presentation
mcsLayer = x224Layer._presentation
secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
pduLayer = secLayer._presentation
controller = pduLayer._listener
controller.onClose()
def buildRawLayer(self, addr):
"""
@summary: Function call from twisted and build rdp protocol stack
@param addr: destination address
"""
controller = RDPServerController(self._colorDepth, self._privateKeyFileName, self._certificateFileName)
self.buildObserver(controller, addr)
return controller.getProtocol()
def buildObserver(self, controller, addr):
"""
@summary: Build observer use for connection
@param controller: RDP stack controller
@param addr: destination address
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildObserver", "ServerFactory"))
# class ServerFactory(RawLayerServerFactory):
# """
# @summary: Factory of Server RDP protocol
# """
# def __init__(self, colorDepth, privateKeyFileName = None, certificateFileName = None):
# """
# @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
#
# def connectionLost(self, tpktLayer, reason):
# """
# @param reason: twisted reason
# """
# #retrieve controller
# x224Layer = tpktLayer._presentation
# mcsLayer = x224Layer._presentation
# secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
# pduLayer = secLayer._presentation
# controller = pduLayer._listener
# controller.onClose()
#
# def buildRawLayer(self, addr):
# """
# @summary: Function call from twisted and build rdp protocol stack
# @param addr: destination address
# """
# controller = RDPServerController(self._colorDepth, self._privateKeyFileName, self._certificateFileName)
# self.buildObserver(controller, addr)
# return controller.getProtocol()
#
# def buildObserver(self, controller, addr):
# """
# @summary: Build observer use for connection
# @param controller: RDP stack controller
# @param addr: destination address
# """
# raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildObserver", "ServerFactory"))
#
class RDPClientObserver(object):
"""
@summary: Class use to inform all RDP event handle by RDPY
@@ -593,6 +652,12 @@ class RDPClientObserver(object):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver"))
def onSessionReady(self):
"""
@summary: Windows session is ready
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "RDPClientObserver"))
def onClose(self):
"""
@summary: Stack is closes
@@ -638,11 +703,12 @@ class RDPServerObserver(object):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
def onKeyEventScancode(self, code, isPressed):
def onKeyEventScancode(self, code, isPressed, isExtended):
"""
@summary: Event call when a keyboard event is catch in scan code format
@param code: scan code of key
@param isPressed: True if key is down
@param code: {integer} scan code of key
@param isPressed: {boolean} True if key is down
@param isExtended: {boolean} True if a special key
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventScanCode", "RDPServerObserver"))
@@ -659,7 +725,7 @@ class RDPServerObserver(object):
@summary: Event call on mouse event
@param x: x position
@param y: y position
@param button: 1, 2 or 3 button
@param button: 1, 2, 3, 4 or 5 button
@param isPressed: True if mouse button is pressed
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onPointerEvent", "RDPServerObserver"))

View File

@@ -21,12 +21,14 @@
RDP Standard security layer
"""
import sha, md5
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 hashlib import sha1 as sha
from hashlib import md5
from rdpy.core import tpkt, lic
from rdpy.core.t125 import gcc, mcs
from rdpy.model.type import CompositeType, CallableValue, Stream, UInt32Le, UInt16Le, Buffer, sizeof, UInt8
from rdpy.model.layer import LayerAutomata, IStreamSender
from rdpy.model.error import InvalidExpectedDataException
from rdpy.model import log
from rdpy.security import rc4
import rdpy.security.rsa_wrapper as rsa
@@ -164,12 +166,12 @@ def macData(macSaltKey, data):
md5Digest = md5.new()
#encode length
s = Stream()
s.writeType(UInt32Le(len(data)))
dataLength = Stream()
dataLength.write_type(UInt32Le(len(data)))
sha1Digest.update(macSaltKey)
sha1Digest.update("\x36" * 40)
sha1Digest.update(s.getvalue())
sha1Digest.update(dataLength.getvalue())
sha1Digest.update(data)
sha1Sig = sha1Digest.digest()
@@ -180,6 +182,38 @@ def macData(macSaltKey, data):
return md5Digest.digest()
def macSaltedData(macSaltKey, data, encryptionCount):
"""
@see: https://msdn.microsoft.com/en-us/library/cc240789.aspx
@param macSaltKey: {str} mac key
@param data: {str} data to sign
@param encryptionCount: nb encrypted packet
@return: {str} signature
"""
sha1Digest = sha.new()
md5Digest = md5.new()
#encode length
dataLengthS = Stream()
dataLengthS.write_type(UInt32Le(len(data)))
encryptionCountS = Stream()
encryptionCountS.write_type(UInt32Le(encryptionCount))
sha1Digest.update(macSaltKey)
sha1Digest.update("\x36" * 40)
sha1Digest.update(dataLengthS.getvalue())
sha1Digest.update(data)
sha1Digest.update(encryptionCountS.getvalue())
sha1Sig = sha1Digest.digest()
md5Digest.update(macSaltKey)
md5Digest.update("\x5c" * 48)
md5Digest.update(sha1Sig)
return md5Digest.digest()
def tempKey(initialKey, currentKey):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240792.aspx
@@ -276,8 +310,8 @@ class ClientSecurityExchangePDU(CompositeType):
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))
self.encryptedClientRandom = Buffer(readLen = CallableValue(lambda:(self.length.value - 8)))
self.padding = Buffer("\x00" * 8, readLen = CallableValue(8))
class RDPInfo(CompositeType):
"""
@@ -290,20 +324,20 @@ class RDPInfo(CompositeType):
#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.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL | InfoFlag.INFO_ENABLEWINDOWSKEY)
self.cbDomain = UInt16Le(lambda:sizeof(self.domain) - 2)
self.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)
self.domain = Buffer(readLen = CallableValue(lambda: self.cbDomain.value + 2), unicode = True)
self.userName = Buffer(readLen = CallableValue(lambda: self.cbUserName.value + 2), unicode = True)
self.password = Buffer(readLen = CallableValue(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)
self.alternateShell = Buffer(readLen = CallableValue(lambda: self.cbAlternateShell.value + 2), unicode = True)
#working directory for session
self.workingDir = String(readLen = UInt16Le(lambda:self.cbWorkingDir.value + 2), unicode = True)
self.workingDir = Buffer(readLen = CallableValue(lambda: self.cbWorkingDir.value + 2), unicode = True)
self.extendedInfo = RDPExtendedInfo(conditional = extendedInfoConditional)
class RDPExtendedInfo(CompositeType):
@@ -314,11 +348,11 @@ class RDPExtendedInfo(CompositeType):
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.clientAddress = Buffer(readLen = self.cbClientAddress, unicode = True)
self.cbClientDir = UInt16Le(lambda:sizeof(self.clientDir))
self.clientDir = String(readLen = self.cbClientDir, unicode = True)
self.clientDir = Buffer(readLen = self.cbClientDir, unicode = True)
#TODO make tiomezone
self.clientTimeZone = String("\x00" * 172)
self.clientTimeZone = Buffer("\x00" * 172)
self.clientSessionId = UInt32Le()
self.performanceFlags = UInt32Le()
@@ -342,6 +376,9 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
#True if classic encryption is enable
self._enableEncryption = False
#Enable Secure Mac generation
self._enableSecureCheckSum = False
#initialise decrypt and encrypt keys
self._macKey = None
self._initialDecrytKey = None
@@ -358,10 +395,11 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
self._encryptRc4 = None
def readEncryptedPayload(self, s):
def readEncryptedPayload(self, s, saltedMacGeneration):
"""
@summary: decrypt basic RDP security payload
@param s: {Stream} encrypted stream
@param saltedMacGeneration: {bool} use salted mac generation
@return: {Stream} decrypted
"""
#if update is needed
@@ -372,24 +410,28 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
self._decryptRc4 = rc4.RC4Key(self._currentDecrytKey)
self._nbDecryptedPacket = 0
signature = String(readLen = UInt8(8))
encryptedPayload = String()
s.readType((signature, encryptedPayload))
signature = Buffer(readLen = CallableValue(8))
encryptedPayload = Buffer()
s.read_type((signature, encryptedPayload))
decrypted = rc4.crypt(self._decryptRc4, encryptedPayload.value)
#ckeck signature
if macData(self._macKey, decrypted)[:8] != signature.value:
raise InvalidExpectedDataException("Bad packet signature")
if not saltedMacGeneration and macData(self._macKey, decrypted)[:8] != signature.value:
raise InvalidExpectedDataException("bad signature")
if saltedMacGeneration and macSaltedData(self._macKey, decrypted, self._nbDecryptedPacket)[:8] != signature.value:
raise InvalidExpectedDataException("bad signature")
#count
self._nbDecryptedPacket += 1
return Stream(decrypted)
def writeEncryptedPayload(self, data):
def writeEncryptedPayload(self, data, saltedMacGeneration):
"""
@summary: sign and crypt data
@param s: {Stream} raw stream
@param data: {Type} raw stream
@param saltedMacGeneration: {bool} use salted mac generation
@return: {Tuple} (signature, encryptedData)
"""
if self._nbEncryptedPacket == 4096:
@@ -400,9 +442,14 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
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())))
s.write_type(data)
if saltedMacGeneration:
return (Buffer(macSaltedData(self._macKey, s.getvalue(), self._nbEncryptedPacket - 1)[:8]), Buffer(rc4.crypt(self._encryptRc4, s.getvalue())))
else:
return (Buffer(macData(self._macKey, s.getvalue())[:8]), Buffer(rc4.crypt(self._encryptRc4, s.getvalue())))
def recv(self, data):
"""
@@ -416,10 +463,10 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
securityFlag = UInt16Le()
securityFlagHi = UInt16Le()
data.readType((securityFlag, securityFlagHi))
data.read_type((securityFlag, securityFlagHi))
if securityFlag.value & SecurityFlag.SEC_ENCRYPT:
data = self.readEncryptedPayload(data)
data = self.readEncryptedPayload(data, securityFlag.value & SecurityFlag.SEC_SECURE_CHECKSUM)
self._presentation.recv(data)
@@ -433,7 +480,12 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
self._transport.send(data)
return
self.sendFlagged(SecurityFlag.SEC_ENCRYPT, data)
flag = SecurityFlag.SEC_ENCRYPT
if self._enableSecureCheckSum:
flag |= SecurityFlag.SEC_SECURE_CHECKSUM
self.sendFlagged(flag, data)
def sendFlagged(self, flag, data):
"""
@@ -444,7 +496,7 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
@param data: {Type | Tuple}
"""
if flag & SecurityFlag.SEC_ENCRYPT:
data = self.writeEncryptedPayload(data)
data = self.writeEncryptedPayload(data, flag & SecurityFlag.SEC_SECURE_CHECKSUM)
self._transport.send((UInt16Le(flag), UInt16Le(), data))
def recvFastPath(self, secFlag, fastPathS):
@@ -454,7 +506,7 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
@param fastPathS: {Stream}
"""
if self._enableEncryption and secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED:
fastPathS = self.readEncryptedPayload(fastPathS)
fastPathS = self.readEncryptedPayload(fastPathS, secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_SECURE_CHECKSUM)
self._fastPathPresentation.recvFastPath(secFlag, fastPathS)
@@ -472,7 +524,11 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
"""
if self._enableEncryption:
secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED
fastPathS = self.writeEncryptedPayload(fastPathS)
if self._enableSecureCheckSum:
secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_SECURE_CHECKSUM
fastPathS = self.writeEncryptedPayload(fastPathS, self._enableSecureCheckSum)
self._fastPathTransport.sendFastPath(secFlag, fastPathS)
@@ -575,7 +631,7 @@ class Client(SecLayer):
#packet preambule
securityFlag = UInt16Le()
securityFlagHi = UInt16Le()
s.readType((securityFlag, securityFlagHi))
s.read_type((securityFlag, securityFlagHi))
if not (securityFlag.value & SecurityFlag.SEC_LICENSE_PKT):
raise InvalidExpectedDataException("waiting license packet")
@@ -624,13 +680,13 @@ class Server(SecLayer):
#packet preambule
securityFlag = UInt16Le()
securityFlagHi = UInt16Le()
s.readType((securityFlag, securityFlagHi))
s.read_type((securityFlag, securityFlagHi))
if not (securityFlag.value & SecurityFlag.SEC_EXCHANGE_PKT):
raise InvalidExpectedDataException("waiting client random")
message = ClientSecurityExchangePDU()
s.readType(message)
s.read_type(message)
clientRandom = rsa.decrypt(message.encryptedClientRandom.value[::-1], self._rsaPrivateKey)[::-1]
self._macKey, self._initialEncryptKey, self._initialDecrytKey = generateKeys( clientRandom,
@@ -655,15 +711,15 @@ class Server(SecLayer):
"""
securityFlag = UInt16Le()
securityFlagHi = UInt16Le()
s.readType((securityFlag, securityFlagHi))
s.read_type((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 = self.readEncryptedPayload(s, securityFlag.value & SecurityFlag.SEC_SECURE_CHECKSUM)
s.readType(self._info)
s.read_type(self._info)
#next state send error license
self.sendLicensingErrorMessage()
#reinit state

View File

@@ -22,22 +22,25 @@ Basic Encoding Rules use in RDP.
ASN.1 standard
"""
from rdpy.core.type import UInt8, UInt16Be, UInt32Be, String
from rdpy.core.error import InvalidExpectedDataException, InvalidSize
from rdpy.model.message import UInt8, UInt16Be, UInt32Be, Buffer
from rdpy.model.error import InvalidExpectedDataException, InvalidSize
class BerPc(object):
class BerPc:
BER_PC_MASK = 0x20
BER_PRIMITIVE = 0x00
BER_CONSTRUCT = 0x20
class Class(object):
class Class:
BER_CLASS_MASK = 0xC0
BER_CLASS_UNIV = 0x00
BER_CLASS_APPL = 0x40
BER_CLASS_CTXT = 0x80
BER_CLASS_PRIV = 0xC0
class Tag(object):
class Tag:
BER_TAG_MASK = 0x1F
BER_TAG_BOOLEAN = 0x01
BER_TAG_INTEGER = 0x02
@@ -48,6 +51,7 @@ class Tag(object):
BER_TAG_SEQUENCE = 0x10
BER_TAG_SEQUENCE_OF = 0x10
def berPC(pc):
"""
@summary: Return BER_CONSTRUCT if true
@@ -59,7 +63,8 @@ def berPC(pc):
return BerPc.BER_CONSTRUCT
else:
return BerPc.BER_PRIMITIVE
def readLength(s):
"""
@summary: Read length of BER structure
@@ -69,7 +74,7 @@ def readLength(s):
"""
size = None
length = UInt8()
s.readType(length)
s.read_type(length)
byte = length.value
if byte & 0x80:
byte &= ~0x80
@@ -79,11 +84,12 @@ def readLength(s):
size = UInt16Be()
else:
raise InvalidExpectedDataException("BER length may be 1 or 2")
s.readType(size)
s.read_type(size)
else:
size = length
return size.value
def writeLength(size):
"""
@summary: Return structure length as expected in BER specification
@@ -94,7 +100,8 @@ def writeLength(size):
return (UInt8(0x82), UInt16Be(size))
else:
return UInt8(size)
def readUniversalTag(s, tag, pc):
"""
@summary: Read tag of BER packet
@@ -103,9 +110,10 @@ def readUniversalTag(s, tag, pc):
@return: true if tag is correctly read
"""
byte = UInt8()
s.readType(byte)
s.read_type(byte)
return byte.value == ((Class.BER_CLASS_UNIV | berPC(pc)) | (Tag.BER_TAG_MASK & tag))
def writeUniversalTag(tag, pc):
"""
@summary: Return universal tag byte
@@ -115,6 +123,7 @@ def writeUniversalTag(tag, pc):
"""
return UInt8((Class.BER_CLASS_UNIV | berPC(pc)) | (Tag.BER_TAG_MASK & tag))
def readApplicationTag(s, tag):
"""
@summary: Read application tag
@@ -123,11 +132,11 @@ def readApplicationTag(s, tag):
@return: length of application packet
"""
byte = UInt8()
s.readType(byte)
s.read_type(byte)
if tag.value > 30:
if byte.value != ((Class.BER_CLASS_APPL | BerPc.BER_CONSTRUCT) | Tag.BER_TAG_MASK):
raise InvalidExpectedDataException()
s.readType(byte)
s.read_type(byte)
if byte.value != tag.value:
raise InvalidExpectedDataException("bad tag")
else:
@@ -136,6 +145,7 @@ def readApplicationTag(s, tag):
return readLength(s)
def writeApplicationTag(tag, size):
"""
@summary: Return structure that represent BER application tag
@@ -159,7 +169,7 @@ def readBoolean(s):
if size != 1:
raise InvalidExpectedDataException("bad boolean size")
b = UInt8()
s.readType(b)
s.read_type(b)
return bool(b.value)
def writeBoolean(b):
@@ -186,21 +196,21 @@ def readInteger(s):
if size == 1:
integer = UInt8()
s.readType(integer)
s.read_type(integer)
return integer.value
elif size == 2:
integer = UInt16Be()
s.readType(integer)
s.read_type(integer)
return integer.value
elif size == 3:
integer1 = UInt8()
integer2 = UInt16Be()
s.readType(integer1)
s.readType(integer2)
s.read_type(integer1)
s.read_type(integer2)
return integer2.value + (integer1.value << 16)
elif size == 4:
integer = UInt32Be()
s.readType(integer)
s.read_type(integer)
return integer.value
else:
raise InvalidExpectedDataException("Wrong integer size")
@@ -235,7 +245,7 @@ def writeOctetstring(value):
@param value: string
@return: BER octet string block
"""
return (writeUniversalTag(Tag.BER_TAG_OCTET_STRING, False), writeLength(len(value)), String(value))
return (writeUniversalTag(Tag.BER_TAG_OCTET_STRING, False), writeLength(len(value)), Buffer(value))
def readEnumerated(s):
"""
@@ -248,7 +258,7 @@ def readEnumerated(s):
if readLength(s) != 1:
raise InvalidSize("enumerate size is wrong")
enumer = UInt8()
s.readType(enumer)
s.read_type(enumer)
return enumer.value
def writeEnumerated(enumerated):

View File

@@ -22,18 +22,18 @@ Implement GCC structure use in RDP protocol
http://msdn.microsoft.com/en-us/library/cc240508.aspx
"""
import md5
from rdpy.core.type import UInt8, UInt16Le, UInt32Le, CompositeType, String, Stream, sizeof, FactoryType, ArrayType
import per, mcs
from rdpy.core.error import InvalidExpectedDataException
from rdpy.core import log
from hashlib import md5
from rdpy.model.message import UInt8, UInt16Le, UInt32Le, CompositeType, Buffer, Stream, sizeof, FactoryType, ArrayType
from rdpy.core.t125 import per, mcs
from rdpy.model.error import InvalidExpectedDataException
from rdpy.model import log
from rdpy.security import x509
import rdpy.security.rsa_wrapper as rsa
t124_02_98_oid = ( 0, 0, 20, 124, 0, 1 )
h221_cs_key = "Duca";
h221_sc_key = "McDn";
h221_cs_key = b"Duca";
h221_sc_key = b"McDn";
class MessageType(object):
"""
@@ -208,43 +208,47 @@ class CertificateType(object):
"""
CERT_CHAIN_VERSION_1 = 0x00000001
CERT_CHAIN_VERSION_2 = 0x00000002
class DataBlock(CompositeType):
"""
@summary: Block settings
"""
def __init__(self, dataBlock = None):
CompositeType.__init__(self)
self.type = UInt16Le(lambda:self.dataBlock.__class__._TYPE_)
self.length = UInt16Le(lambda:sizeof(self))
def __init__(self, data_block=None):
super().__init__()
self.type = UInt16Le(lambda:data_block._TYPE_)
self.length = UInt16Le(lambda: sizeof(self))
def DataBlockFactory():
def factory():
"""
@summary: build settings in accordance of type self.type.value
"""
for c in [ClientCoreData, ClientSecurityData, ClientNetworkData, ServerCoreData, ServerNetworkData, ServerSecurityData]:
gcc_type = [
ClientCoreData, ClientSecurityData, ClientNetworkData,
ServerCoreData, ServerNetworkData, ServerSecurityData
]
for c in gcc_type:
if self.type.value == c._TYPE_:
return c(readLen = self.length - 4)
return c(read_len=lambda: (self.length.value - 4))
log.debug("unknown GCC block type : %s"%hex(self.type.value))
#read entire packet
return String(readLen = self.length - 4)
# read entire packet
return Buffer(read_len=lambda: (self.length.value - 4))
if dataBlock is None:
dataBlock = FactoryType(DataBlockFactory)
elif not "_TYPE_" in dataBlock.__class__.__dict__:
if data_block is None:
data_block = FactoryType(factory)
elif "_TYPE_" not in data_block.__class__.__dict__:
raise InvalidExpectedDataException("Try to send an invalid GCC blocks")
self.dataBlock = dataBlock
self.dataBlock = data_block
class ClientCoreData(CompositeType):
"""
@summary: Class that represent core setting of client
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
_TYPE_ = MessageType.CS_CORE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
def __init__(self, read_len=None):
super().__init__(read_len=read_len)
self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS)
self.desktopWidth = UInt16Le(1280)
self.desktopHeight = UInt16Le(800)
@@ -252,69 +256,68 @@ class ClientCoreData(CompositeType):
self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL)
self.kbdLayout = UInt32Le(KeyboardLayout.US)
self.clientBuild = UInt32Le(3790)
self.clientName = String("rdpy" + "\x00"*11, readLen = UInt8(32), unicode = True)
self.clientName = Buffer(("rdpy" + "\x00" * 12).encode("utf-16le"), read_len=lambda: 32)
self.keyboardType = UInt32Le(KeyboardType.IBM_101_102_KEYS)
self.keyboardSubType = UInt32Le(0)
self.keyboardFnKeys = UInt32Le(12)
self.imeFileName = String("\x00"*64, readLen = UInt8(64))
self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP)
self.clientProductId = UInt16Le(1)
self.serialNumber = UInt32Le(0)
self.highColorDepth = UInt16Le(HighColor.HIGH_COLOR_24BPP)
self.supportedColorDepths = UInt16Le(Support.RNS_UD_15BPP_SUPPORT | Support.RNS_UD_16BPP_SUPPORT | Support.RNS_UD_24BPP_SUPPORT | Support.RNS_UD_32BPP_SUPPORT)
self.earlyCapabilityFlags = UInt16Le(CapabilityFlags.RNS_UD_CS_SUPPORT_ERRINFO_PDU)
self.clientDigProductId = String("\x00"*64, readLen = UInt8(64))
self.connectionType = UInt8()
self.pad1octet = UInt8()
self.serverSelectedProtocol = UInt32Le()
self.imeFileName = Buffer(b"\x00" * 64, read_len=lambda: 64, optional=True)
self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP, optional=True)
self.clientProductId = UInt16Le(1, optional=True)
self.serialNumber = UInt32Le(0, optional=True)
self.highColorDepth = UInt16Le(HighColor.HIGH_COLOR_24BPP, optional=True)
self.supportedColorDepths = UInt16Le(Support.RNS_UD_15BPP_SUPPORT | Support.RNS_UD_16BPP_SUPPORT | Support.RNS_UD_24BPP_SUPPORT | Support.RNS_UD_32BPP_SUPPORT, optional=True)
self.earlyCapabilityFlags = UInt16Le(CapabilityFlags.RNS_UD_CS_SUPPORT_ERRINFO_PDU, optional=True)
self.clientDigProductId = Buffer(b"\x00" * 64, read_len=lambda: 64, optional=True)
self.connectionType = UInt8(optional=True)
self.pad1octet = UInt8(optional=True)
self.serverSelectedProtocol = UInt32Le(optional=True)
class ServerCoreData(CompositeType):
"""
@summary: Server side core settings structure
@see: http://msdn.microsoft.com/en-us/library/cc240517.aspx
"""
_TYPE_ = MessageType.SC_CORE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
def __init__(self, read_len=None):
super().__init__(read_len=read_len)
self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS)
self.clientRequestedProtocol = UInt32Le()
self.clientRequestedProtocol = UInt32Le(optional=True)
self.earlyCapabilityFlags = UInt32Le(optional=True)
class ClientSecurityData(CompositeType):
"""
@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)
def __init__(self, read_len=None):
super().__init__(read_len=read_len)
self.encryptionMethods = UInt32Le(EncryptionMethod.ENCRYPTION_FLAG_40BIT | EncryptionMethod.ENCRYPTION_FLAG_56BIT | EncryptionMethod.ENCRYPTION_FLAG_128BIT)
self.extEncryptionMethods = UInt32Le()
class ServerSecurityData(CompositeType):
"""
@summary: Server security settings
@see: http://msdn.microsoft.com/en-us/library/cc240518.aspx
"""
_TYPE_ = MessageType.SC_SECURITY
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
def __init__(self, read_len=None):
super().__init__(read_len=read_len)
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))
self.serverRandomLen = UInt32Le(0x00000020, constant=True, conditional=lambda: not(self.encryptionMethod.value == 0 and self.encryptionLevel.value == 0))
self.serverCertLen = UInt32Le(lambda: sizeof(self.serverCertificate), conditional=lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel.value == 0))
self.serverRandom = Buffer(read_len=lambda: self.serverRandomLen.value, conditional=lambda: not(self.encryptionMethod.value == 0 and self.encryptionLevel.value == 0))
self.serverCertificate = ServerCertificate(read_len=lambda: self.serverCertLen.value, conditional=lambda: not(self.encryptionMethod.value == 0 and self.encryptionLevel.value == 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)
def __init__(self, certData = None, read_len = None, conditional = lambda:True):
CompositeType.__init__(self, read_len=read_len, conditional = conditional)
self.dwVersion = UInt32Le(lambda:(self.certData.__class__._TYPE_))
def CertificateFactory():
@@ -353,9 +356,9 @@ class ProprietaryServerCertificate(CompositeType):
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))
self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) + sizeof(self.padding)))
self.SignatureBlob = Buffer(readLen = CallableValue(lambda:(self.wSignatureBlobLen.value - sizeof(self.padding))))
self.padding = Buffer(b"\x00" * 8, readLen = CallableValue(8))
def getPublicKey(self):
"""
@@ -370,17 +373,17 @@ class ProprietaryServerCertificate(CompositeType):
@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)
s.write_type(UInt32Le(self.__class__._TYPE_))
s.write_type(self.dwSigAlgId)
s.write_type(self.dwKeyAlgId)
s.write_type(self.wPublicKeyBlobType)
s.write_type(self.wPublicKeyBlobLen)
s.write_type(self.PublicKeyBlob)
md5Digest = md5.new()
md5Digest.update(s.getvalue())
return md5Digest.digest() + "\x00" + "\xff" * 46 + "\x01"
return md5Digest.digest() + "\x00" + "\xff" * 45 + "\x01"
def sign(self):
"""
@@ -404,7 +407,7 @@ class CertBlob(CompositeType):
def __init__(self):
CompositeType.__init__(self)
self.cbCert = UInt32Le(lambda:sizeof(self.abCert))
self.abCert = String(readLen = self.cbCert)
self.abCert = Buffer(readLen = self.cbCert)
class X509CertificateChain(CompositeType):
"""
@@ -417,7 +420,7 @@ class X509CertificateChain(CompositeType):
CompositeType.__init__(self)
self.NumCertBlobs = UInt32Le()
self.CertBlobArray = ArrayType(CertBlob, readLen = self.NumCertBlobs)
self.padding = String(readLen = UInt8(lambda:(8 + 4 * self.NumCertBlobs.value)))
self.padding = Buffer(readLen = CallableValue(lambda:(8 + 4 * self.NumCertBlobs.value)))
def getPublicKey(self):
"""
@@ -446,93 +449,76 @@ class RSAPublicKey(CompositeType):
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))
self.modulus = Buffer(readLen = CallableValue(lambda:(self.keylen.value - 8)))
self.padding = Buffer(b"\x00" * 8, readLen = CallableValue(8))
class ChannelDef(CompositeType):
"""
Channels structure share between client and server
@see: http://msdn.microsoft.com/en-us/library/cc240513.aspx
"""
def __init__(self, name = "", options = 0):
CompositeType.__init__(self)
#name of channel
self.name = String(name[0:8] + "\x00" * (8 - len(name)), readLen = UInt8(8))
#unknown
def __init__(self, name=b""):
super().__init__()
# name of channel
self.name = Buffer(name[0:8] + b"\x00" * (8 - len(name)), read_len=lambda: 8)
# unknown
self.options = UInt32Le()
class ClientNetworkData(CompositeType):
"""
GCC client network block
All channels asked by client are listed here
@see: http://msdn.microsoft.com/en-us/library/cc240512.aspx
"""
_TYPE_ = MessageType.CS_NET
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.channelCount = UInt32Le(lambda:len(self.channelDefArray._array))
self.channelDefArray = ArrayType(ChannelDef, readLen = self.channelCount)
def __init__(self, read_len=None):
CompositeType.__init__(self, read_len=read_len)
self.channelCount = UInt32Le(lambda: len(self.channelDefArray))
self.channelDefArray = ArrayType(ChannelDef, read_len=lambda: self.channelCount.value)
class ServerNetworkData(CompositeType):
"""
GCC server network block
All channels asked by client are listed here
@see: All channels asked by client are listed here
"""
_TYPE_ = MessageType.SC_NET
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
def __init__(self, read_len=None):
super().__init__(read_len=read_len)
self.MCSChannelId = UInt16Le(mcs.Channel.MCS_GLOBAL_CHANNEL)
self.channelCount = UInt16Le(lambda:len(self.channelIdArray._array))
self.channelIdArray = ArrayType(UInt16Le, readLen = self.channelCount)
self.pad = UInt16Le(conditional = lambda:((self.channelCount.value % 2) == 1))
self.channelCount = UInt16Le(lambda: len(self.channelIdArray))
self.channelIdArray = ArrayType(UInt16Le, read_len=lambda: self.channelCount.value)
self.pad = UInt16Le(conditional=lambda: ((self.channelCount.value % 2) == 1))
class Settings(CompositeType):
"""
Class which group all clients settings supported by RDPY
"""
def __init__(self, init = [], readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.settings = ArrayType(DataBlock, [DataBlock(i) for i in init])
def __init__(self, init=None, read_len=None):
super().__init__(read_len=read_len)
self.settings = ArrayType(DataBlock, [DataBlock(i) for i in init or []])
def getBlock(self, messageType):
def get_block(self, message_type):
"""
@param messageType: type of block
@return: specific block of type messageType
"""
for i in self.settings._array:
if i.type.value == messageType:
if i.type.value == message_type:
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():
def client_settings():
"""
Build settings for client
@return: Settings
"""
return Settings([ClientCoreData(), ClientNetworkData(), ClientSecurityData()])
def serverSettings():
"""
Build settings for server
@summary: Build settings for server
@return Settings
"""
return Settings([ServerCoreData(), ServerSecurityData(), ServerNetworkData()])
def readConferenceCreateRequest(s):
"""
Read a response from client
@summary: Read a response from client
GCC create request
@param s: Stream
@param client settings (Settings)
@@ -553,13 +539,13 @@ def readConferenceCreateRequest(s):
per.readOctetStream(s, h221_cs_key, 4)
length = per.readLength(s)
clientSettings = Settings(readLen = UInt32Le(length))
s.readType(clientSettings)
clientSettings = Settings(readLen = CallableValue(length))
s.read_type(clientSettings)
return clientSettings
def readConferenceCreateResponse(s):
"""
Read response from server
@summary: Read response from server
and return server settings read from this response
@param s: Stream
@return: ServerSettings
@@ -577,36 +563,36 @@ def readConferenceCreateResponse(s):
raise InvalidExpectedDataException("cannot read h221_sc_key")
length = per.readLength(s)
serverSettings = Settings(readLen = UInt32Le(length))
s.readType(serverSettings)
return serverSettings
server_settings = Settings(read_len=lambda: length)
s.read_type(server_settings)
return server_settings
def writeConferenceCreateRequest(userData):
"""
Write conference create request structure
@summary: Write conference create request structure
@param userData: Settings for client
@return: GCC packet
"""
userDataStream = Stream()
userDataStream.writeType(userData)
userDataStream.write_type(userData)
return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
per.writeLength(len(userDataStream.getvalue()) + 14), per.writeChoice(0),
per.writeSelection(0x08), per.writeNumericString("1", 1), per.writePadding(1),
per.writeSelection(0x08), per.writeNumericString(b"1", 1), per.writePadding(1),
per.writeNumberOfSet(1), per.writeChoice(0xc0),
per.writeOctetStream(h221_cs_key, 4), per.writeOctetStream(userDataStream.getvalue()))
def writeConferenceCreateResponse(serverData):
"""
Write a conference create response packet
@summary: Write a conference create response packet
@param serverData: Settings for server
@return: gcc packet
"""
serverDataStream = Stream()
serverDataStream.writeType(serverData)
serverDataStream.write_type(serverData)
return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
per.writeLength(len(serverDataStream.getvalue()) + 14), per.writeChoice(0x14),
per.writeInteger16(0x79F3, 1001), per.writeInteger(1), per.writeEnumerates(16),
per.writeInteger16(0x79F3, 1001), per.writeInteger(1), per.writeEnumerates(0),
per.writeNumberOfSet(1), per.writeChoice(0xc0),
per.writeOctetStream(h221_sc_key, 4), per.writeOctetStream(serverDataStream.getvalue()))

View File

@@ -24,25 +24,28 @@ 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.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.core.log as log
from typing import Tuple
import ber, gcc, per
from rdpy.core import x224
from rdpy.model.layer import LayerAutomata, IStreamSender, Layer
from rdpy.model.message import sizeof, Stream, UInt8, UInt16Le, Buffer
from rdpy.model.error import InvalidExpectedDataException, InvalidValue, InvalidSize, CallPureVirtualFuntion
from rdpy.core.t125.ber import writeLength
import rdpy.model.log as log
from rdpy.core.t125 import ber, gcc, per
import rdpy.security.rsa_wrapper as rsa
class Message(object):
class Message:
"""
@summary: Message type
"""
MCS_TYPE_CONNECT_INITIAL = 0x65
MCS_TYPE_CONNECT_RESPONSE = 0x66
class DomainMCSPDU:
"""
@summary: Domain MCS PDU header
"""
ERECT_DOMAIN_REQUEST = 1
DISCONNECT_PROVIDER_ULTIMATUM = 8
@@ -53,44 +56,182 @@ class DomainMCSPDU:
SEND_DATA_REQUEST = 25
SEND_DATA_INDICATION = 26
class Channel:
"""
@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"))
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"))
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"))
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"))
raise CallPureVirtualFuntion(
"%s:%s defined by interface %s" % (self.__class__, "getGCCServerSettings", "IGCCConfig"))
def write_domain_params(max_channels: int, max_users: int, max_tokens: int, max_pdu_size: int) -> tuple:
"""
"""
domain_param = (ber.writeInteger(max_channels), ber.writeInteger(max_users), ber.writeInteger(max_tokens),
ber.writeInteger(1), ber.writeInteger(0), ber.writeInteger(1),
ber.writeInteger(max_pdu_size), ber.writeInteger(2))
return ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True), writeLength(sizeof(domain_param)), domain_param
def read_domain_params(stream: Stream) -> Tuple[int, int, int, int]:
"""
"""
if not ber.readUniversalTag(stream, ber.Tag.BER_TAG_SEQUENCE, True):
raise InvalidValue("bad BER tags")
ber.readLength(stream) # length
max_channels = ber.readInteger(stream)
max_users = ber.readInteger(stream)
max_tokens = ber.readInteger(stream)
ber.readInteger(stream)
ber.readInteger(stream)
ber.readInteger(stream)
max_pdu_size = ber.readInteger(stream)
ber.readInteger(stream)
return max_channels, max_users, max_tokens, max_pdu_size
def mcs_pdu_header(mcs_pdu: UInt8, options=0) -> UInt8:
return (mcs_pdu << 2) | options
def check_mcs_pdu_header(opcode: UInt8, mcs_pdu):
return (opcode >> 2) == mcs_pdu
def erect_domain_request() -> Tuple:
return mcs_pdu_header(UInt8(DomainMCSPDU.ERECT_DOMAIN_REQUEST)), per.writeInteger(0), per.writeInteger(0)
def read_attach_confirm(data: Stream) -> int:
"""
"""
opcode = data.read_type(UInt8())
if not check_mcs_pdu_header(opcode, DomainMCSPDU.ATTACH_USER_CONFIRM):
raise InvalidExpectedDataException("Invalid MCS PDU : ATTACH_USER_CONFIRM expected")
if per.readEnumerates(data) != 0:
raise InvalidExpectedDataException("Server reject user")
return per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
def attach_user_request() -> UInt8:
return mcs_pdu_header(UInt8(DomainMCSPDU.ATTACH_USER_REQUEST))
def channel_join_request(user_id: int, channel_id: int):
return (mcs_pdu_header(UInt8(DomainMCSPDU.CHANNEL_JOIN_REQUEST)),
per.writeInteger16(user_id, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channel_id))
def channel_join_confirm(user_id: int, channel_id: int, data: Stream) -> bool:
"""
"""
opcode = data.read_type(UInt8())
if not check_mcs_pdu_header(opcode.value, DomainMCSPDU.CHANNEL_JOIN_CONFIRM):
raise InvalidExpectedDataException("Invalid MCS PDU : CHANNEL_JOIN_CONFIRM expected")
confirm = per.readEnumerates(data)
if user_id != per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE):
raise InvalidExpectedDataException("Invalid MCS User Id")
if channel_id != per.readInteger16(data):
raise InvalidExpectedDataException("Invalid MCS channel id")
return confirm == 0
class Client:
def __init__(self, x224_layer: x224.X224):
self.x224 = x224_layer
self.channel_ids = {}
async def write_connect_initial(self):
"""
"""
settings = gcc.client_settings()
settings.get_block(gcc.MessageType.CS_CORE).serverSelectedProtocol.value = self.x224.get_selected_protocol()
cc_req = gcc.writeConferenceCreateRequest(settings)
cc_req_stream = Stream()
cc_req_stream.write_type(cc_req)
tmp = (ber.writeOctetstring(b"\x01"), ber.writeOctetstring(b"\x01"), ber.writeBoolean(True),
write_domain_params(34, 2, 0, 0xffff),
write_domain_params(1, 1, 1, 0x420),
write_domain_params(0xffff, 0xfc17, 0xffff, 0xffff),
ber.writeOctetstring(cc_req_stream.getvalue()))
await self.x224.write((ber.writeApplicationTag(Message.MCS_TYPE_CONNECT_INITIAL, sizeof(tmp)), tmp))
async def read_connect_response(self):
payload = await self.x224.read()
ber.readApplicationTag(payload, UInt8(Message.MCS_TYPE_CONNECT_RESPONSE))
ber.readEnumerated(payload)
ber.readInteger(payload)
read_domain_params(payload)
if not ber.readUniversalTag(payload, ber.Tag.BER_TAG_OCTET_STRING, False):
raise InvalidExpectedDataException("invalid expected BER tag")
gccRequestLength = ber.readLength(payload)
if payload.data_len() != gccRequestLength:
raise InvalidSize("bad size of GCC request")
gcc.readConferenceCreateResponse(payload)
async def connect(self):
await self.write_connect_initial()
await self.read_connect_response()
await self.x224.write(erect_domain_request())
await self.x224.write(attach_user_request())
user_id = read_attach_confirm(await self.x224.read())
self.channel_ids["global"] = 1003
self.channel_ids["user"] = user_id
# connect all channels
for channel_id in self.channel_ids.values():
await self.x224.write(channel_join_request(user_id, channel_id))
if not channel_join_confirm(user_id, channel_id, await self.x224.read()):
print("Server refused channel %s"%channel_id)
class MCSLayer(LayerAutomata):
"""
@@ -98,11 +239,13 @@ class MCSLayer(LayerAutomata):
the main layer of RDP protocol
is why he can do everything and more!
"""
class MCSProxySender(Layer, IStreamSender, IGCCConfig):
"""
@summary: Proxy use to set as transport layer for upper channel
use to abstract channel id for presentation layer
"""
def __init__(self, presentation, mcs, channelId):
"""
@param presentation: {Layer} presentation layer
@@ -112,7 +255,7 @@ class MCSLayer(LayerAutomata):
Layer.__init__(self, presentation)
self._mcs = mcs
self._channelId = channelId
def send(self, data):
"""
@summary: A send proxy function, use channel id and specific
@@ -120,43 +263,42 @@ class MCSLayer(LayerAutomata):
@param data: {type.Type | Tuple}
"""
self._mcs.send(self._channelId, data)
def close(self):
"""
@summary: Close wrapped layer
"""
self._mcs.close()
def getUserId(self):
"""
@return: {integer} mcs user id
@see: mcs.IGCCConfig
"""
return self._mcs._userId
def getChannelId(self):
"""
@return: {integer} return channel id of proxy
@see: mcs.IGCCConfig
"""
return self._channelId
def getGCCClientSettings(self):
"""
@return: {gcc.Settings} mcs layer gcc client settings
@see: mcs.IGCCConfig
"""
return self._mcs._clientSettings
def getGCCServerSettings(self):
"""
@return: {gcc.Settings} mcs layer gcc server settings
@see: mcs.IGCCConfig
"""
return self._mcs._serverSettings
def __init__(self, presentation, receiveOpcode, sendOpcode, virtualChannels = []):
def __init__(self, presentation, receiveOpcode, sendOpcode, virtualChannels=[]):
"""
@param presentation: {Layer} presentation layer
@param virtualChannels: {Array(Layer]} list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
@@ -166,82 +308,82 @@ class MCSLayer(LayerAutomata):
LayerAutomata.__init__(self, presentation)
self._clientSettings = gcc.clientSettings()
self._serverSettings = gcc.serverSettings()
#default user Id
# default user Id
self._userId = 1 + Channel.MCS_USERCHANNEL_BASE
#list of channel use in this layer and connection state
# list of channel use in this layer and connection state
self._channels = {Channel.MCS_GLOBAL_CHANNEL: presentation}
#virtual channels
# virtual channels
self._virtualChannels = virtualChannels
#send opcode
# send opcode
self._sendOpcode = sendOpcode
#receive opcode
# receive opcode
self._receiveOpcode = receiveOpcode
def close(self):
"""
@summary: Send disconnect provider ultimatum
"""
self._transport.send((UInt8(self.writeMCSPDUHeader(DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM, 1)),
per.writeEnumerates(0x80), String("\x00" * 6)))
per.writeEnumerates(0x80), Buffer(b"\x00" * 6)))
self._transport.close()
def allChannelConnected(self):
"""
@summary: All channels are connected to MCS layer
Send connect to upper channel
And prepare MCS layer to receive data
"""
#connection is done
# connection is done
self.setNextState(self.recvData)
#try connection on all requested channel
# try connection on all requested channel
for (channelId, layer) in self._channels.iteritems():
#use proxy for each channel
# use proxy for each channel
MCSLayer.MCSProxySender(layer, self, channelId).connect()
def send(self, channelId, data):
"""
@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),
per.writeInteger16(channelId),
UInt8(0x70),
self._transport.send((self.writeMCSPDUHeader(UInt8(self._sendOpcode)),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channelId),
UInt8(0x70),
per.writeLength(sizeof(data)), data))
def recvData(self, data):
"""
@summary: Main receive method
@param data: {Stream}
"""
opcode = UInt8()
data.readType(opcode)
data.read_type(opcode)
if self.readMCSPDUHeader(opcode.value, DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM):
log.info("MCS DISCONNECT_PROVIDER_ULTIMATUM")
self._transport.close()
return
#client case
# client case
elif not self.readMCSPDUHeader(opcode.value, self._receiveOpcode):
raise InvalidExpectedDataException("Invalid expected MCS opcode receive data")
#server user id
# server user id
per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
channelId = per.readInteger16(data)
per.readEnumerates(data)
per.readEnumerates(data)
per.readLength(data)
#channel id doesn't match a requested layer
# channel id doesn't match a requested layer
if not self._channels.has_key(channelId):
log.error("receive data for an unconnected layer")
return
self._channels[channelId].recv(data)
self._channels[channelId].recv(data)
def writeDomainParams(self, maxChannels, maxUsers, maxTokens, maxPduSize):
"""
@summary: Write a special domain parameter structure
@@ -256,8 +398,8 @@ class MCSLayer(LayerAutomata):
ber.writeInteger(1), ber.writeInteger(0), ber.writeInteger(1),
ber.writeInteger(maxPduSize), ber.writeInteger(2))
return (ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True), writeLength(sizeof(domainParam)), domainParam)
def writeMCSPDUHeader(self, mcsPdu, options = 0):
def writeMCSPDUHeader(self, mcsPdu, options=0):
"""
@summary: Write MCS PDU header
@param mcsPdu: {integer} PDU code
@@ -265,7 +407,7 @@ class MCSLayer(LayerAutomata):
@return: {integer}
"""
return (mcsPdu << 2) | options
def readMCSPDUHeader(self, opcode, mcsPdu):
"""
@summary: Read mcsPdu header and return options parameter
@@ -274,7 +416,7 @@ class MCSLayer(LayerAutomata):
@return: {boolean} true if opcode is correct
"""
return (opcode >> 2) == mcsPdu
def readDomainParams(self, s):
"""
@summary: Read domain parameters structure
@@ -283,7 +425,7 @@ class MCSLayer(LayerAutomata):
"""
if not ber.readUniversalTag(s, ber.Tag.BER_TAG_SEQUENCE, True):
raise InvalidValue("bad BER tags")
ber.readLength(s)#length
ber.readLength(s) # length
max_channels = ber.readInteger(s)
max_users = ber.readInteger(s)
max_tokens = ber.readInteger(s)
@@ -293,23 +435,26 @@ class MCSLayer(LayerAutomata):
max_pdu_size = ber.readInteger(s)
ber.readInteger(s)
return (max_channels, max_users, max_tokens, max_pdu_size)
class Client(MCSLayer):
class ClientOld(MCSLayer):
"""
@summary: Client automata of multiple channel service layer
"""
def __init__(self, presentation, virtualChannels = []):
def __init__(self, presentation, virtualChannels=[]):
"""
@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
MCSLayer.__init__(self, presentation, DomainMCSPDU.SEND_DATA_INDICATION, DomainMCSPDU.SEND_DATA_REQUEST,
virtualChannels)
# use to know state of static channel
self._isGlobalChannelRequested = False
self._isUserChannelRequested = False
#nb channel requested
# nb channel requested
self._nbChannelRequested = 0
def connect(self):
"""
@summary: Connect message in client automata case
@@ -317,13 +462,13 @@ class Client(MCSLayer):
Wait ConnectResponse
"""
self._clientSettings.CS_CORE.serverSelectedProtocol.value = self._transport._selectedProtocol
#ask for virtual channel
# ask for virtual channel
self._clientSettings.CS_NET.channelDefArray._array = [x for (x, _) in self._virtualChannels]
#send connect initial
# send connect initial
self.sendConnectInitial()
#next wait response
# next wait response
self.setNextState(self.recvConnectResponse)
def connectNextChannel(self):
"""
@summary: Send sendChannelJoinRequest message on next disconnect channel
@@ -331,27 +476,27 @@ class Client(MCSLayer):
Wait channel confirm
"""
self.setNextState(self.recvChannelJoinConfirm)
#global channel
# global channel
if not self._isGlobalChannelRequested:
self.sendChannelJoinRequest(Channel.MCS_GLOBAL_CHANNEL)
self._isGlobalChannelRequested = True
return
#user channel
# user channel
if not self._isUserChannelRequested:
self.sendChannelJoinRequest(self._userId)
self._isUserChannelRequested = True
return
#static virtual channel
if self._nbChannelRequested < self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelCount.value:
channelId = self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray[self._nbChannelRequested]
# static virtual channel
if self._nbChannelRequested < self._serverSettings.get_block(gcc.MessageType.SC_NET).channelCount.value:
channelId = self._serverSettings.get_block(gcc.MessageType.SC_NET).channelIdArray[self._nbChannelRequested]
self._nbChannelRequested += 1
self.sendChannelJoinRequest(channelId)
return
self.allChannelConnected()
def recvConnectResponse(self, data):
"""
@summary: Receive MCS connect response from server
@@ -367,17 +512,17 @@ class Client(MCSLayer):
if not ber.readUniversalTag(data, ber.Tag.BER_TAG_OCTET_STRING, False):
raise InvalidExpectedDataException("invalid expected BER tag")
gccRequestLength = ber.readLength(data)
if data.dataLen() != gccRequestLength:
if data.data_len() != gccRequestLength:
raise InvalidSize("bad size of GCC request")
self._serverSettings = gcc.readConferenceCreateResponse(data)
#send domain request
# send domain request
self.sendErectDomainRequest()
#send attach user request
# send attach user request
self.sendAttachUserRequest()
#now wait user confirm from server
# now wait user confirm from server
self.setNextState(self.recvAttachUserConfirm)
def recvAttachUserConfirm(self, data):
"""
@summary: Receive an attach user confirm
@@ -385,18 +530,18 @@ class Client(MCSLayer):
@param data: {Stream}
"""
opcode = UInt8()
data.readType(opcode)
data.read_type(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ATTACH_USER_CONFIRM):
raise InvalidExpectedDataException("Invalid MCS PDU : ATTACH_USER_CONFIRM expected")
if per.readEnumerates(data) != 0:
raise InvalidExpectedDataException("Server reject user")
self._userId = per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
self.connectNextChannel()
def recvChannelJoinConfirm(self, data):
"""
@summary: Receive a channel join confirm from server
@@ -404,30 +549,30 @@ class Client(MCSLayer):
@param data: {Stream}
"""
opcode = UInt8()
data.readType(opcode)
data.read_type(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.CHANNEL_JOIN_CONFIRM):
raise InvalidExpectedDataException("Invalid MCS PDU : CHANNEL_JOIN_CONFIRM expected")
confirm = per.readEnumerates(data)
userId = per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
if self._userId != userId:
raise InvalidExpectedDataException("Invalid MCS User Id")
channelId = per.readInteger16(data)
#must confirm global channel and user channel
# must confirm global channel and user channel
if (confirm != 0) and (channelId == Channel.MCS_GLOBAL_CHANNEL or channelId == self._userId):
raise InvalidExpectedDataException("Server must confirm static channel")
if confirm == 0:
serverNet = self._serverSettings.getBlock(gcc.MessageType.SC_NET)
serverNet = self._serverSettings.get_block(gcc.MessageType.SC_NET)
for i in range(0, serverNet.channelCount.value):
if channelId == serverNet.channelIdArray[i].value:
self._channels[channelId] = self._virtualChannels[i][1]
self._channels[channelId] = self._virtualChannels[i][1]
self.connectNextChannel()
def sendConnectInitial(self):
"""
@summary: Send connect initial packet
@@ -435,68 +580,70 @@ class Client(MCSLayer):
"""
ccReq = gcc.writeConferenceCreateRequest(self._clientSettings)
ccReqStream = Stream()
ccReqStream.writeType(ccReq)
ccReqStream.write_type(ccReq)
tmp = (ber.writeOctetstring("\x01"), ber.writeOctetstring("\x01"), ber.writeBoolean(True),
self.writeDomainParams(34, 2, 0, 0xffff),
self.writeDomainParams(1, 1, 1, 0x420),
self.writeDomainParams(0xffff, 0xfc17, 0xffff, 0xffff),
ber.writeOctetstring(ccReqStream.getvalue()))
self._transport.send((ber.writeApplicationTag(Message.MCS_TYPE_CONNECT_INITIAL, sizeof(tmp)), tmp))
def sendErectDomainRequest(self):
"""
@summary: Send a formated erect domain request for RDP connection
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ERECT_DOMAIN_REQUEST)),
per.writeInteger(0),
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ERECT_DOMAIN_REQUEST)),
per.writeInteger(0),
per.writeInteger(0)))
def sendAttachUserRequest(self):
"""
@summary: Send a formated attach user request for RDP connection
"""
self._transport.send(self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_REQUEST)))
def sendChannelJoinRequest(self, channelId):
"""
@summary: Send a formated Channel join request from client to server
client automata function
@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),
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.CHANNEL_JOIN_REQUEST)),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channelId)))
class Server(MCSLayer):
"""
@summary: Server automata of multiple channel service layer
"""
def __init__(self, presentation, virtualChannels = []):
def __init__(self, presentation, virtualChannels=[]):
"""
@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
MCSLayer.__init__(self, presentation, DomainMCSPDU.SEND_DATA_REQUEST, DomainMCSPDU.SEND_DATA_INDICATION,
virtualChannels)
# nb channel requested
self._nbChannelConfirmed = 0
def connect(self):
"""
@summary: Connect message for server automata
Wait Connect Initial
"""
#basic rdp security layer
# 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):
"""
@summary: Receive MCS connect initial from client
@@ -507,27 +654,28 @@ class Server(MCSLayer):
ber.readApplicationTag(data, UInt8(Message.MCS_TYPE_CONNECT_INITIAL))
ber.readOctetString(data)
ber.readOctetString(data)
if not ber.readBoolean(data):
raise InvalidExpectedDataException("invalid expected BER boolean tag")
self.readDomainParams(data)
self.readDomainParams(data)
self.readDomainParams(data)
self._clientSettings = gcc.readConferenceCreateRequest(Stream(ber.readOctetString(data)))
i = 1
for channelDef in self._clientSettings.getBlock(gcc.MessageType.CS_NET).channelDefArray._array:
self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray._array.append(UInt16Le(i + Channel.MCS_GLOBAL_CHANNEL))
#if channel can be handle by serve add it
for serverChannelDef, layer in self._virtualChannels:
if channelDef.name == serverChannelDef.name:
self._channels[i + Channel.MCS_GLOBAL_CHANNEL] = layer
i += 1
if not self._clientSettings.CS_NET is None:
i = 1
for channelDef in self._clientSettings.CS_NET.channelDefArray._array:
self._serverSettings.SC_NET.channelIdArray._array.append(UInt16Le(i + Channel.MCS_GLOBAL_CHANNEL))
# if channel can be handle by serve add it
for serverChannelDef, layer in self._virtualChannels:
if channelDef.name == serverChannelDef.name:
self._channels[i + Channel.MCS_GLOBAL_CHANNEL] = layer
i += 1
self.sendConnectResponse()
self.setNextState(self.recvErectDomainRequest)
def recvErectDomainRequest(self, data):
"""
@summary: Receive erect domain request
@@ -535,16 +683,16 @@ class Server(MCSLayer):
@param data: {Stream}
"""
opcode = UInt8()
data.readType(opcode)
data.read_type(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ERECT_DOMAIN_REQUEST):
raise InvalidExpectedDataException("Invalid MCS PDU : ERECT_DOMAIN_REQUEST expected")
per.readInteger(data)
per.readInteger(data)
self.setNextState(self.recvAttachUserRequest)
def recvAttachUserRequest(self, data):
"""
@summary: Receive Attach user request
@@ -553,14 +701,14 @@ class Server(MCSLayer):
@param data: {Stream}
"""
opcode = UInt8()
data.readType(opcode)
data.read_type(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ATTACH_USER_REQUEST):
raise InvalidExpectedDataException("Invalid MCS PDU : ATTACH_USER_REQUEST expected")
self.sendAttachUserConfirm()
self.setNextState(self.recvChannelJoinRequest)
def recvChannelJoinRequest(self, data):
"""
@summary: Receive for each client channel a request
@@ -569,51 +717,51 @@ class Server(MCSLayer):
"""
opcode = UInt8()
data.readType(opcode)
data.read_type(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.CHANNEL_JOIN_REQUEST):
raise InvalidExpectedDataException("Invalid MCS PDU : CHANNEL_JOIN_REQUEST expected")
userId = per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
if self._userId != userId:
raise InvalidExpectedDataException("Invalid MCS User Id")
channelId = per.readInteger16(data)
#actually algo support virtual channel but RDPY have no virtual channel
# actually algo support virtual channel but RDPY have no virtual channel
confirm = 0 if channelId in self._channels.keys() or channelId == self._userId else 1
self.sendChannelJoinConfirm(channelId, confirm)
self._nbChannelConfirmed += 1
if self._nbChannelConfirmed == self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelCount.value + 2:
if self._nbChannelConfirmed == self._serverSettings.get_block(gcc.MessageType.SC_NET).channelCount.value + 2:
self.allChannelConnected()
def sendConnectResponse(self):
"""
@summary: Send connect response
"""
ccReq = gcc.writeConferenceCreateResponse(self._serverSettings)
ccReqStream = Stream()
ccReqStream.writeType(ccReq)
tmp = (ber.writeEnumerated(0), ber.writeInteger(0), self.writeDomainParams(22, 3, 0, 0xfff8),
ccReqStream.write_type(ccReq)
tmp = (ber.writeEnumerated(0), ber.writeInteger(0), self.writeDomainParams(22, 3, 0, 0xfff8),
ber.writeOctetstring(ccReqStream.getvalue()))
self._transport.send((ber.writeApplicationTag(Message.MCS_TYPE_CONNECT_RESPONSE, sizeof(tmp)), tmp))
def sendAttachUserConfirm(self):
"""
@summary: Send attach user confirm
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_CONFIRM), 2),
per.writeEnumerates(0),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE)))
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_CONFIRM), 2),
per.writeEnumerates(0),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE)))
def sendChannelJoinConfirm(self, channelId, confirm):
"""
@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)),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channelId),
per.writeInteger16(channelId)))
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.CHANNEL_JOIN_CONFIRM), 2),
per.writeEnumerates(int(confirm)),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channelId),
per.writeInteger16(channelId)))

View File

@@ -21,8 +21,8 @@
Per encoded function
"""
from rdpy.core.type import UInt8, UInt16Be, UInt32Be, String
from rdpy.core.error import InvalidValue, InvalidExpectedDataException
from rdpy.model.message import UInt8, UInt16Be, UInt32Be, Buffer
from rdpy.model.error import InvalidValue, InvalidExpectedDataException
def readLength(s):
"""
@@ -31,12 +31,12 @@ def readLength(s):
@return: int python
"""
byte = UInt8()
s.readType(byte)
s.read_type(byte)
size = 0
if byte.value & 0x80:
byte.value &= ~0x80
size = byte.value << 8
s.readType(byte)
s.read_type(byte)
size += byte.value
else:
size = byte.value
@@ -60,7 +60,7 @@ def readChoice(s):
@return: int that represent choice
"""
choice = UInt8()
s.readType(choice)
s.read_type(choice)
return choice.value
def writeChoice(choice):
@@ -78,7 +78,7 @@ def readSelection(s):
@return: int that represent selection
"""
choice = UInt8()
s.readType(choice)
s.read_type(choice)
return choice.value
def writeSelection(selection):
@@ -96,7 +96,7 @@ def readNumberOfSet(s):
@return: int that represent numberOfSet
"""
choice = UInt8()
s.readType(choice)
s.read_type(choice)
return choice.value
def writeNumberOfSet(numberOfSet):
@@ -114,7 +114,7 @@ def readEnumerates(s):
@return: int that represent enumerate
"""
choice = UInt8()
s.readType(choice)
s.read_type(choice)
return choice.value
def writeEnumerates(enumer):
@@ -142,7 +142,7 @@ def readInteger(s):
result = UInt32Be()
else:
raise InvalidValue("invalid integer size %d"%size)
s.readType(result)
s.read_type(result)
return result.value
def writeInteger(value):
@@ -166,7 +166,7 @@ def readInteger16(s, minimum = 0):
@return: int or long python value
"""
result = UInt16Be()
s.readType(result)
s.read_type(result)
return result.value + minimum
def writeInteger16(value, minimum = 0):
@@ -190,16 +190,16 @@ def readObjectIdentifier(s, oid):
raise InvalidValue("size of stream oid is wrong %d != 5"%size)
a_oid = [0, 0, 0, 0, 0, 0]
t12 = UInt8()
s.readType(t12)
s.read_type(t12)
a_oid[0] = t12.value >> 4
a_oid[1] = t12.value & 0x0f
s.readType(t12)
s.read_type(t12)
a_oid[2] = t12.value
s.readType(t12)
s.read_type(t12)
a_oid[3] = t12.value
s.readType(t12)
s.read_type(t12)
a_oid[4] = t12.value
s.readType(t12)
s.read_type(t12)
a_oid[5] = t12.value
if list(oid) != a_oid:
@@ -223,12 +223,9 @@ def readNumericString(s, minValue):
length = (length + minValue + 1) / 2
s.read(length)
def writeNumericString(nStr, minValue):
"""
@summary: write string in per format
@param str: python string to write
@param min: min value
@return: String type that contain str encoded in per format
"""
length = len(nStr)
mlength = minValue
@@ -238,9 +235,9 @@ def writeNumericString(nStr, minValue):
result = []
for i in range(0, length, 2):
c1 = ord(nStr[i])
c1 = nStr[i]
if i + 1 < length:
c2 = ord(nStr[i + 1])
c2 = nStr[i + 1]
else:
c2 = 0x30
c1 = (c1 - 0x30) % 10
@@ -248,7 +245,7 @@ def writeNumericString(nStr, minValue):
result.append(UInt8((c1 << 4) | c2))
return (writeLength(mlength), tuple(result))
return writeLength(mlength), tuple(result)
def readPadding(s, length):
"""
@@ -264,7 +261,7 @@ def writePadding(length):
@param length: length of padding
@return: String with \x00 * length
"""
return String("\x00"*length)
return Buffer(b"\x00" * length)
def readOctetStream(s, octetStream, minValue = 0):
"""
@@ -276,11 +273,11 @@ def readOctetStream(s, octetStream, minValue = 0):
"""
size = readLength(s) + minValue
if size != len(octetStream):
raise InvalidValue("incompatible size %d != %d"(len(octetStream), size))
raise InvalidValue("incompatible size %d != %d"%(len(octetStream), size))
for i in range(0, size):
c = UInt8()
s.readType(c)
if ord(octetStream[i]) != c.value:
s.read_type(c)
if octetStream[i] != c.value:
return False
return True
@@ -300,6 +297,6 @@ def writeOctetStream(oStr, minValue = 0):
result = []
for i in range(0, length):
result.append(UInt8(ord(oStr[i])))
result.append(UInt8(oStr[i]))
return (writeLength(mlength), tuple(result))
return writeLength(mlength), tuple(result)

View File

@@ -22,115 +22,42 @@ Transport packet layer implementation
Use to build correct size packet and handle slow path and fast path mode
"""
from rdpy.core.layer import RawLayer
from rdpy.core.type import UInt8, UInt16Be, sizeof
from rdpy.core.error import CallPureVirtualFuntion
import asyncio
import ssl
class Action(object):
from rdpy.core.nla import cssp, sspi
from rdpy.model.layer import RawLayer
from rdpy.model.message import UInt8, UInt16Be, sizeof, Stream
class Action:
"""
@see: http://msdn.microsoft.com/en-us/library/cc240621.aspx
@see: http://msdn.microsoft.com/en-us/library/cc240589.aspx
"""
FASTPATH_ACTION_FASTPATH = 0x0
FASTPATH_ACTION_X224 = 0x3
class SecFlags(object):
class SecFlags:
"""
@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, secFlag, fastPathS):
"""
@summary: Call when fast path packet is received
@param secFlag: {SecFlags}
@param fastPathS: {Stream}
"""
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):
"""
@param fastPathSender : {IFastPathSender}
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "setFastPathSender", "IFastPathListener"))
class IFastPathSender(object):
"""
@summary: Fast path send capability
"""
def sendFastPath(self, secFlag, fastPathS):
"""
@summary: Send fastPathS Type as fast path packet
@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):
class Tpkt:
"""
@summary: TPKT layer in RDP protocol stack
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):
"""
@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
def connect(self):
"""
@summary: Call when transport layer connection
is made (inherit from RawLayer)
"""
#header is on two bytes
self.expect(2, self.readHeader)
#no connection automata on this layer
if not self._presentation is None:
self._presentation.connect()
def __init__(self, reader, writer):
self.reader = reader
self.writer = writer
def readHeader(self, data):
"""
@summary: Read header of TPKT packet
@@ -138,17 +65,17 @@ class TPKT(RawLayer, IFastPathSender):
"""
#first read packet version
version = UInt8()
data.readType(version)
data.read_type(version)
#classic packet
if version.value == Action.FASTPATH_ACTION_X224:
#padding
data.readType(UInt8())
data.read_type(UInt8())
#read end header
self.expect(2, self.readExtendedHeader)
else:
#is fast path packet
self._secFlag = ((version.value >> 6) & 0x3)
data.readType(self._lastShortLength)
data.read_type(self._lastShortLength)
if self._lastShortLength.value & 0x80:
#size is 1 byte more
self.expect(1, self.readExtendedFastPathHeader)
@@ -163,7 +90,7 @@ class TPKT(RawLayer, IFastPathSender):
"""
#next state is read data
size = UInt16Be()
data.readType(size)
data.read_type(size)
self.expect(size.value - 4, self.readData)
def readExtendedFastPathHeader(self, data):
@@ -172,7 +99,7 @@ class TPKT(RawLayer, IFastPathSender):
@param data: {Stream} from twisted layer
"""
leftPart = UInt8()
data.readType(leftPart)
data.read_type(leftPart)
self._lastShortLength.value &= ~0x80
packetSize = (self._lastShortLength.value << 8) + leftPart.value
#next state is fast patn data
@@ -195,16 +122,58 @@ class TPKT(RawLayer, IFastPathSender):
self._presentation.recv(data)
self.expect(2, self.readHeader)
def send(self, message):
async def write(self, message):
"""
@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))
s = Stream()
s.write_type((UInt8(Action.FASTPATH_ACTION_X224), UInt8(0), UInt16Be(sizeof(message) + 4), message))
self.writer.write(s.getvalue())
await self.writer.drain()
async def read(self):
"""
Read an entire payload from the reader stream
"""
header = Stream(await self.reader.readexactly(2))
action = UInt8()
header.read_type(action)
if action.value == Action.FASTPATH_ACTION_X224:
# read padding
header.read_type(UInt8())
size = UInt16Be()
Stream(await self.reader.readexactly(2)).read_type(size)
return Stream(await self.reader.readexactly(size.value - 4))
def sendFastPath(self, secFlag, fastPathS):
"""
@param fastPathS: type transform to stream and send as fastpath
@param fastPathS: {Type | Tuple} type transform to stream and send as fastpath
@param secFlag: {integer} Security flag for fastpath packet
"""
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH | ((secFlag & 0x3) << 6)), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS))
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH | ((secFlag & 0x3) << 6)), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS))
async def start_tls(self):
"""
Start TLS protocol
"""
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_ctx.check_hostname = False
ssl_ctx.verify_mode = ssl.VerifyMode.CERT_NONE
reader, writer = await asyncio.open_connection(sock=self.writer.transport._sock, ssl=ssl_ctx, server_hostname="")
return Tpkt(reader, writer)
async def start_nla(self, authentication_protocol: sspi.IAuthenticationProtocol):
"""
use to start NLA (NTLM over SSL) protocol
must be called after startTLS function
:ivar authentication_protocol: Authentication protocol use by CSSP to authenticate user
and transfert credentials
"""
tpkt = await self.start_tls()
await cssp.connect(tpkt.reader, tpkt.writer, authentication_protocol)
return tpkt

View File

@@ -23,13 +23,15 @@ Implement transport PDU layer
This layer have main goal to negociate SSL transport
RDP basic security is supported only on client side
"""
from rdpy.core import log
from rdpy.core import tpkt
from rdpy.core.nla import sspi
from rdpy.model import log
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
from rdpy.model.message import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, Buffer, Stream
from rdpy.model.error import InvalidExpectedDataException, RDPSecurityNegoFail
class MessageType(object):
class MessageType:
"""
@summary: Message type
"""
@@ -39,7 +41,8 @@ class MessageType(object):
X224_TPDU_DATA = 0xF0
X224_TPDU_ERROR = 0x70
class NegociationType(object):
class NegociationType:
"""
@summary: Negotiation header
"""
@@ -47,16 +50,19 @@ class NegociationType(object):
TYPE_RDP_NEG_RSP = 0x02
TYPE_RDP_NEG_FAILURE = 0x03
class Protocols(object):
class Protocols:
"""
@summary: Protocols available for x224 layer
@see: https://msdn.microsoft.com/en-us/library/cc240500.aspx
"""
PROTOCOL_RDP = 0x00000000
PROTOCOL_SSL = 0x00000001
PROTOCOL_HYBRID = 0x00000002
PROTOCOL_HYBRID_EX = 0x00000008
class NegotiationFailureCode(object):
class NegotiationFailureCode:
"""
@summary: Protocol negotiation failure code
"""
@@ -66,23 +72,25 @@ class NegotiationFailureCode(object):
INCONSISTENT_FLAGS = 0x00000004
HYBRID_REQUIRED_BY_SERVER = 0x00000005
SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 0x00000006
class ClientConnectionRequestPDU(CompositeType):
class ConnectionRequestPDU(CompositeType):
"""
@summary: Connection request
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240470.aspx
Connection Request PDU
Use to send protocol security level available for the client
:see: http://msdn.microsoft.com/en-us/library/cc240470.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.len = UInt8(lambda:sizeof(self) - 1)
self.code = UInt8(MessageType.X224_TPDU_CONNECTION_REQUEST, constant = True)
self.code = UInt8(MessageType.X224_TPDU_CONNECTION_REQUEST, constant=True)
self.padding = (UInt16Be(), UInt16Be(), UInt8())
self.cookie = String(until = "\x0d\x0a", conditional = lambda:(self.len._is_readed and self.len.value > 14))
#read if there is enough data
self.cookie = Buffer(until=b"\x0d\x0a", conditional=lambda: (self.len._is_readed and self.len.value > 14))
# read if there is enough data
self.protocolNeg = Negotiation(optional = True)
class ServerConnectionConfirm(CompositeType):
class ConnectionConfirmPDU(CompositeType):
"""
@summary: Server response
@see: http://msdn.microsoft.com/en-us/library/cc240506.aspx
@@ -94,7 +102,8 @@ class ServerConnectionConfirm(CompositeType):
self.padding = (UInt16Be(), UInt16Be(), UInt8())
#read if there is enough data
self.protocolNeg = Negotiation(optional = True)
class X224DataHeader(CompositeType):
"""
@summary: Header send when x224 exchange application data
@@ -104,7 +113,8 @@ class X224DataHeader(CompositeType):
self.header = UInt8(2)
self.messageType = UInt8(MessageType.X224_TPDU_DATA, constant = True)
self.separator = UInt8(0x80, constant = True)
class Negotiation(CompositeType):
"""
@summary: Negociate request message
@@ -121,104 +131,68 @@ class Negotiation(CompositeType):
self.selectedProtocol = UInt32Le(conditional = lambda: (self.code.value != NegociationType.TYPE_RDP_NEG_FAILURE))
self.failureCode = UInt32Le(conditional = lambda: (self.code.value == NegociationType.TYPE_RDP_NEG_FAILURE))
class X224Layer(LayerAutomata, IStreamSender):
class X224:
"""
@summary: x224 layer management
there is an connection automata
"""
def __init__(self, presentation):
def __init__(self, tpkt: tpkt.Tpkt, selected_protocol: int):
"""
@param presentation: upper layer, MCS layer in RDP case
"""
LayerAutomata.__init__(self, presentation)
#client requested selectedProtocol
self._requestedProtocol = Protocols.PROTOCOL_SSL
#server selected selectedProtocol
self._selectedProtocol = Protocols.PROTOCOL_SSL
self.tpkt = tpkt
self.selected_protocol = selected_protocol
def recvData(self, data):
async def read(self) -> Stream:
"""
@summary: Read data header from packet
And pass to presentation layer
@param data: Stream
"""
header = X224DataHeader()
data.readType(header)
self._presentation.recv(data)
payload = await self.tpkt.read()
payload.read_type(header)
return payload
def send(self, message):
async def write(self, message):
"""
@summary: Write message packet for TPDU layer
Add TPDU header
@param message: network.Type message
"""
self._transport.send((X224DataHeader(), message))
class Client(X224Layer):
"""
@summary: Client automata of TPDU layer
"""
def __init__(self, presentation):
"""
@param presentation: upper layer, MCS layer in RDP case
"""
X224Layer.__init__(self, presentation)
def connect(self):
"""
@summary: Connection request for client send a connection request packet
"""
self.sendConnectionRequest()
def sendConnectionRequest(self):
"""
@summary: Write connection request message
Next state is recvConnectionConfirm
@see: http://msdn.microsoft.com/en-us/library/cc240500.aspx
"""
message = ClientConnectionRequestPDU()
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_REQ
message.protocolNeg.selectedProtocol.value = self._requestedProtocol
self._transport.send(message)
self.setNextState(self.recvConnectionConfirm)
def recvConnectionConfirm(self, data):
"""
@summary: Receive connection confirm message
Next state is recvData
Call connect on presentation layer if all is good
@param data: Stream that contain connection confirm
@see: response -> http://msdn.microsoft.com/en-us/library/cc240506.aspx
@see: failure ->http://msdn.microsoft.com/en-us/library/cc240507.aspx
"""
message = ServerConnectionConfirm()
data.readType(message)
if message.protocolNeg.failureCode._is_readed:
raise RDPSecurityNegoFail("negotiation failure code %x"%message.protocolNeg.failureCode.value)
#check presence of negotiation response
if message.protocolNeg._is_readed:
self._selectedProtocol = message.protocolNeg.selectedProtocol.value
else:
self._selectedProtocol = Protocols.PROTOCOL_RDP
#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")
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)
#connection is done send to presentation
self._presentation.connect()
await self.tpkt.write((X224DataHeader(), message))
class Server(X224Layer):
def get_selected_protocol(self):
return self.selected_protocol
async def connect(tpkt: tpkt.Tpkt, authentication_protocol: sspi.IAuthenticationProtocol) -> X224:
"""
Negotiate the security level and generate a X224 configured layer
:ivar tpkt: this is the tpkt layer use to negotiate the security level
:ivar authentication_protocol: Authentication protocol is used by NLA authentication
Actually only NTLMv2 is available
:see: http://msdn.microsoft.com/en-us/library/cc240500.aspx
"""
request = ConnectionRequestPDU()
request.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_REQ
request.protocolNeg.selectedProtocol.value = Protocols.PROTOCOL_HYBRID | Protocols.PROTOCOL_SSL
await tpkt.write(request)
respond = (await tpkt.read()).read_type(ConnectionConfirmPDU())
if respond.protocolNeg.failureCode._is_readed:
raise RDPSecurityNegoFail("negotiation failure code %x"%respond.protocolNeg.failureCode.value)
selected_protocol = Protocols.PROTOCOL_RDP
if respond.protocolNeg._is_readed:
selected_protocol = respond.protocolNeg.selectedProtocol.value
if selected_protocol in [Protocols.PROTOCOL_HYBRID_EX]:
raise InvalidExpectedDataException("RDPY doesn't support PROTOCOL_HYBRID_EX security Layer")
if selected_protocol == Protocols.PROTOCOL_RDP:
return X224(tpkt, selected_protocol)
elif selected_protocol == Protocols.PROTOCOL_SSL:
return X224(await tpkt.start_tls(), selected_protocol)
elif selected_protocol == Protocols.PROTOCOL_HYBRID:
return X224(await tpkt.start_nla(authentication_protocol), selected_protocol)
class Server(X224):
"""
@summary: Server automata of X224 layer
"""
@@ -248,8 +222,8 @@ class Server(X224Layer):
@param data: {Stream}
@see : http://msdn.microsoft.com/en-us/library/cc240470.aspx
"""
message = ClientConnectionRequestPDU()
data.readType(message)
message = ConnectionRequestPDU()
data.read_type(message)
if not message.protocolNeg._is_readed:
self._requestedProtocol = Protocols.PROTOCOL_RDP
@@ -266,7 +240,7 @@ class Server(X224Layer):
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 = ConnectionConfirmPDU()
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_FAILURE
message.protocolNeg.failureCode.value = NegotiationFailureCode.SSL_REQUIRED_BY_SERVER
self._transport.send(message)
@@ -282,45 +256,15 @@ class Server(X224Layer):
Next state is recvData
@see : http://msdn.microsoft.com/en-us/library/cc240501.aspx
"""
message = ServerConnectionConfirm()
message = ConnectionConfirmPDU()
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_RSP
message.protocolNeg.selectedProtocol.value = self._selectedProtocol
self._transport.send(message)
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))
#self._transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
#connection is done send to presentation
self.setNextState(self.recvData)
self._presentation.connect()
#open ssl needed
from twisted.internet import ssl
from OpenSSL import SSL
class ClientTLSContext(ssl.ClientContextFactory):
"""
@summary: client context factory for open ssl
"""
def getContext(self):
context = SSL.Context(SSL.TLSv1_METHOD)
context.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
context.set_options(SSL.OP_TLS_BLOCK_PADDING_BUG)
return context
class ServerTLSContext(ssl.DefaultOpenSSLContextFactory):
"""
@summary: Server context factory for open ssl
@param privateKeyFileName: Name of a file containing a private key
@param certificateFileName: Name of a file containing a certificate
"""
def __init__(self, privateKeyFileName, certificateFileName):
class TPDUSSLContext(SSL.Context):
def __init__(self, method):
SSL.Context.__init__(self, method)
self.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
self.set_options(SSL.OP_TLS_BLOCK_PADDING_BUG)
ssl.DefaultOpenSSLContextFactory.__init__(self, privateKeyFileName, certificateFileName, SSL.SSLv23_METHOD, TPDUSSLContext)

98
rdpy/model/filetimes.py Normal file
View File

@@ -0,0 +1,98 @@
# Copyright (c) 2009, David Buxton <david@gasmark6.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Tools to convert between Python datetime instances and Microsoft times.
"""
from datetime import datetime, timedelta, tzinfo
from calendar import timegm
# http://support.microsoft.com/kb/167296
# How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME
EPOCH_AS_FILETIME = 116444736000000000 # January 1, 1970 as MS file time
HUNDREDS_OF_NANOSECONDS = 10000000
ZERO = timedelta(0)
HOUR = timedelta(hours=1)
class UTC(tzinfo):
"""UTC"""
def utcoffset(self, dt):
return ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return ZERO
utc = UTC()
def dt_to_filetime(dt):
"""Converts a datetime to Microsoft filetime format. If the object is
time zone-naive, it is forced to UTC before conversion.
>>> "%.0f" % dt_to_filetime(datetime(2009, 7, 25, 23, 0))
'128930364000000000'
>>> "%.0f" % dt_to_filetime(datetime(1970, 1, 1, 0, 0, tzinfo=utc))
'116444736000000000'
>>> "%.0f" % dt_to_filetime(datetime(1970, 1, 1, 0, 0))
'116444736000000000'
>>> dt_to_filetime(datetime(2009, 7, 25, 23, 0, 0, 100))
128930364000001000
"""
if (dt.tzinfo is None) or (dt.tzinfo.utcoffset(dt) is None):
dt = dt.replace(tzinfo=utc)
ft = EPOCH_AS_FILETIME + (timegm(dt.timetuple()) * HUNDREDS_OF_NANOSECONDS)
return ft + (dt.microsecond * 10)
def filetime_to_dt(ft):
"""Converts a Microsoft filetime number to a Python datetime. The new
datetime object is time zone-naive but is equivalent to tzinfo=utc.
>>> filetime_to_dt(116444736000000000)
datetime.datetime(1970, 1, 1, 0, 0)
>>> filetime_to_dt(128930364000000000)
datetime.datetime(2009, 7, 25, 23, 0)
>>> filetime_to_dt(128930364000001000)
datetime.datetime(2009, 7, 25, 23, 0, 0, 100)
"""
# Get seconds and remainder in terms of Unix epoch
(s, ns100) = divmod(ft - EPOCH_AS_FILETIME, HUNDREDS_OF_NANOSECONDS)
# Convert to datetime object
dt = datetime.utcfromtimestamp(s)
# Add remainder in as microseconds. Python 3.2 requires an integer
dt = dt.replace(microsecond=(ns100 // 10))
return dt

View File

@@ -22,8 +22,9 @@ Join RDPY design with twisted design
RDPY use Layer Protocol design (like twisted)
"""
import asyncio
from rdpy.model.error import CallPureVirtualFuntion
from rdpy.core.error import CallPureVirtualFuntion
class IStreamListener(object):
"""
@@ -35,7 +36,8 @@ class IStreamListener(object):
@param s: Stream
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
class IStreamSender(object):
"""
@summary: Interface use to inform stream sender capability
@@ -46,7 +48,8 @@ class IStreamSender(object):
@param data: Type or tuple element handle by transport layer
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "send", "IStreamSender"))
class Layer(object):
"""
@summary: A simple double linked list with presentation and transport layer
@@ -79,7 +82,8 @@ class Layer(object):
"""
if not self._transport is None:
self._transport.close()
class LayerAutomata(Layer, IStreamListener):
"""
@summary: Layer with automata callback
@@ -103,13 +107,8 @@ class LayerAutomata(Layer, IStreamListener):
self.recv = callback
#twisted layer concept
from twisted.internet import protocol
from twisted.internet.abstract import FileDescriptor
#first that handle stream
from type import Stream
class RawLayerClientFactory(protocol.ClientFactory):
class RawLayerClientFactory(asyncio.Protocol):
"""
@summary: Abstract class for Raw layer client factory
"""
@@ -136,37 +135,38 @@ class RawLayerClientFactory(protocol.ClientFactory):
@param reason: twisted reason
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "connectionLost", "RawLayerClientFactory"))
class RawLayerServerFactory(protocol.ClientFactory):
"""
@summary: Abstract class for Raw layer server factory
"""
def buildProtocol(self, addr):
"""
@summary: Function call from twisted
@param addr: destination address
"""
rawLayer = self.buildRawLayer(addr)
rawLayer.setFactory(self)
return rawLayer
def buildRawLayer(self, addr):
"""
@summary: Override this function to build raw layer
@param addr: destination address
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
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"))
class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
# class RawLayerServerFactory(protocol.ServerFactory):
# """
# @summary: Abstract class for Raw layer server factory
# """
# def buildProtocol(self, addr):
# """
# @summary: Function call from twisted
# @param addr: destination address
# """
# rawLayer = self.buildRawLayer(addr)
# rawLayer.setFactory(self)
# return rawLayer
#
# def buildRawLayer(self, addr):
# """
# @summary: Override this function to build raw layer
# @param addr: destination address
# """
# raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
#
# 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"))
#
#
class RawLayer(asyncio.Protocol, LayerAutomata, IStreamSender):
"""
@summary: Wait event from twisted engine
And format correct size packet
@@ -183,14 +183,14 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
#len of next packet pass to next state function
self._expectedLen = 0
self._factory = None
def setFactory(self, factory):
"""
@summary: Call by RawLayer Factory
@param param: RawLayerClientFactory or RawLayerFactory
"""
self._factory = factory
def dataReceived(self, data):
"""
@summary: Inherit from twisted.protocol class
@@ -200,39 +200,45 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
#add in buffer
self._buffer += data
#while buffer have expected size call local callback
while len(self._buffer) >= self._expectedLen:
while self._expectedLen > 0 and len(self._buffer) >= self._expectedLen:
#expected data is first expected bytes
expectedData = Stream(self._buffer[0:self._expectedLen])
#rest is for next event of automata
self._buffer = self._buffer[self._expectedLen:]
#call recv function
self.recv(expectedData)
def connectionMade(self):
"""
@summary: inherit from twisted protocol
"""
#join two scheme
self.connect()
def connectionLost(self, reason):
"""
@summary: Call from twisted engine when protocol is closed
@param reason: str represent reason of close connection
"""
self._factory.connectionLost(self, reason)
def getDescriptor(self):
"""
@return: the twited file descriptor
"""
return self.transport
def close(self):
"""
@summary: Close raw layer
Use File descriptor directly to not use TLS close
Because is bugged
"""
FileDescriptor.loseConnection(self.transport)
FileDescriptor.loseConnection(self.getDescriptor())
def expect(self, expectedLen, callback = None):
"""
@summary: Set next automata callback,
@summary: Set next automata callback,
But this callback will be only called when
data have expectedLen
@param expectedLen: in bytes length use to call next state
@@ -241,7 +247,7 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
self._expectedLen = expectedLen
#default callback is recv from LayerAutomata
self.setNextState(callback)
def send(self, message):
"""
@summary: Send Stream on TCP layer
@@ -250,5 +256,5 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
@param message: (tuple | Type)
"""
s = Stream()
s.writeType(message)
s.write_type(message)
self.transport.write(s.getvalue())

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -22,8 +22,8 @@ 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
from rdpy.model.type import CompositeType, FactoryType, UInt8, UInt16Le, UInt32Le, Buffer, sizeof, Stream
from rdpy.model import log, error
import time
class EventType(object):
@@ -34,6 +34,8 @@ class EventType(object):
SCREEN = 0x0002
INFO = 0x0003
CLOSE = 0x0004
KEY_UNICODE = 0x0005
KEY_SCANCODE = 0x0006
class UpdateFormat(object):
"""
@@ -56,12 +58,12 @@ class Event(CompositeType):
"""
@summary: Closure for event factory
"""
for c in [UpdateEvent, ScreenEvent, InfoEvent, CloseEvent]:
for c in [UpdateEvent, ScreenEvent, InfoEvent, CloseEvent, KeyEventScancode, KeyEventUnicode]:
if self.type.value == c._TYPE_:
return c(readLen = self.length)
log.debug("unknown event type : %s"%hex(self.type.value))
#read entire packet
return String(readLen = self.length)
return Buffer(readLen = self.length)
if event is None:
event = FactoryType(EventFactory)
@@ -86,7 +88,7 @@ class UpdateEvent(CompositeType):
self.bpp = UInt8()
self.format = UInt8()
self.length = UInt32Le(lambda:sizeof(self.data))
self.data = String(readLen = self.length)
self.data = Buffer(readLen = self.length)
class InfoEvent(CompositeType):
"""
@@ -96,13 +98,13 @@ class InfoEvent(CompositeType):
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.lenUsername = UInt16Le(lambda:sizeof(self.username))
self.username = String(readLen = self.lenUsername)
self.username = Buffer(readLen = self.lenUsername)
self.lenPassword = UInt16Le(lambda:sizeof(self.password))
self.password = String(readLen = self.lenPassword)
self.password = Buffer(readLen = self.lenPassword)
self.lenDomain = UInt16Le(lambda:sizeof(self.domain))
self.domain = String(readLen = self.lenDomain)
self.domain = Buffer(readLen = self.lenDomain)
self.lenHostname = UInt16Le(lambda:sizeof(self.hostname))
self.hostname = String(readLen = self.lenHostname)
self.hostname = Buffer(readLen = self.lenHostname)
class ScreenEvent(CompositeType):
"""
@@ -123,6 +125,26 @@ class CloseEvent(CompositeType):
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
class KeyEventUnicode(CompositeType):
"""
@summary: keyboard event (keylogger) as unicode event
"""
_TYPE_ = EventType.KEY_UNICODE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.code = UInt32Le()
self.isPressed = UInt8()
class KeyEventScancode(CompositeType):
"""
@summary: keyboard event (keylogger)
"""
_TYPE_ = EventType.KEY_SCANCODE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.code = UInt32Le()
self.isPressed = UInt8()
def timeMs():
"""
@return: {int} time stamp in milliseconds
@@ -155,7 +177,7 @@ class FileRecorder(object):
self._lastEventTimer = now
s = Stream()
s.writeType(e)
s.write_type(e)
self._file.write(s.getvalue())
@@ -211,6 +233,28 @@ class FileRecorder(object):
infoEvent.domain.value = domain
infoEvent.hostname.value = hostname
self.rec(infoEvent)
def keyUnicode(self, code, isPressed):
"""
@summary: record key event as unicode
@param code: unicode code
@param isPressed: True if a key press event
"""
keyEvent = KeyEventUnicode()
keyEvent.code.value = code
keyEvent.isPressed.value = 0 if isPressed else 1
self.rec(keyEvent)
def keyScancode(self, code, isPressed):
"""
@summary: record key event as scancode
@param code: scancode code
@param isPressed: True if a key press event
"""
keyEvent = KeyEventScancode()
keyEvent.code.value = code
keyEvent.isPressed.value = 0 if isPressed else 1
self.rec(keyEvent)
def close(self):
"""
@@ -232,10 +276,10 @@ class FileReader(object):
"""
@summary: read next event and return it
"""
if self._s.dataLen() == 0:
if self._s.data_len() == 0:
return None
e = Event()
self._s.readType(e)
self._s.read_type(e)
return e
def createRecorder(path):
@@ -252,4 +296,5 @@ def createReader(path):
@param path: {str} path of input file
@return: {FileReader}
"""
return FileReader(open(path, "rb"))
with open(path, "rb") as f:
return FileReader(f)

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

@@ -0,0 +1,60 @@
#
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Basic virtual scancode mapping
"""
_SCANCODE_QWERTY_ = {
0x10 : "q",
0x11 : "w",
0x12 : "e",
0x13 : "r",
0x14 : "t",
0x15 : "y",
0x16 : "u",
0x17 : "i",
0x18 : "o",
0x19 : "p",
0x1e : "a",
0x1f : "s",
0x20 : "d",
0x21 : "f",
0x22 : "g",
0x23 : "h",
0x24 : "j",
0x25 : "k",
0x26 : "l",
0x2c : "z",
0x2d : "x",
0x2e : "c",
0x2f : "v",
0x30 : "b",
0x31 : "n",
0x32 : "m"
}
def scancodeToChar(code):
"""
@summary: try to convert native code to char code
@return: char
"""
if not _SCANCODE_QWERTY_.has_key(code):
return "<unknown scancode %x>"%code
return _SCANCODE_QWERTY_[code];

View File

@@ -1,765 +0,0 @@
#
# 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/>.
#
"""
Implement Remote FrameBuffer protocol use in VNC client and server
@see: http://www.realvnc.com/docs/rfbproto.pdf
@todo: server side of protocol
@todo: more encoding rectangle
"""
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):
"""
@summary: Different protocol version
"""
UNKNOWN = ""
RFB003003 = "RFB 003.003\n"
RFB003007 = "RFB 003.007\n"
RFB003008 = "RFB 003.008\n"
class SecurityType(object):
"""
@summary: Security type supported
"""
INVALID = 0
NONE = 1
VNC = 2
class Pointer(object):
"""
@summary: Mouse event code (which button)
actually in RFB specification only
three buttons are supported
"""
BUTTON1 = 0x1
BUTTON2 = 0x2
BUTTON3 = 0x4
class Encoding(object):
"""
@summary: Encoding types of FrameBuffer update
"""
RAW = 0
class ClientToServerMessages(object):
"""
@summary: Client to server messages types
"""
PIXEL_FORMAT = 0
ENCODING = 2
FRAME_BUFFER_UPDATE_REQUEST = 3
KEY_EVENT = 4
POINTER_EVENT = 5
CUT_TEXT = 6
class PixelFormat(CompositeType):
"""
@summary: Pixel format structure
"""
def __init__(self):
CompositeType.__init__(self)
self.BitsPerPixel = UInt8(32)
self.Depth = UInt8(24)
self.BigEndianFlag = UInt8(False)
self.TrueColorFlag = UInt8(True)
self.RedMax = UInt16Be(255)
self.GreenMax = UInt16Be(255)
self.BlueMax = UInt16Be(255)
self.RedShift = UInt8(16)
self.GreenShift = UInt8(8)
self.BlueShift = UInt8(0)
self.padding = (UInt16Be(), UInt8())
class ServerInit(CompositeType):
"""
@summary: Server init structure
FrameBuffer configuration
"""
def __init__(self):
CompositeType.__init__(self)
self.width = UInt16Be()
self.height = UInt16Be()
self.pixelFormat = PixelFormat()
class FrameBufferUpdateRequest(CompositeType):
"""
@summary: FrameBuffer update request send from client to server
Incremental means that server send update with a specific
order, and client must draw orders in same order
"""
def __init__(self, incremental = False, x = 0, y = 0, width = 0, height = 0):
CompositeType.__init__(self)
self.incremental = UInt8(incremental)
self.x = UInt16Be(x)
self.y = UInt16Be(y)
self.width = UInt16Be(width)
self.height = UInt16Be(height)
class Rectangle(CompositeType):
"""
@summary: Header message of update rectangle
"""
def __init__(self):
CompositeType.__init__(self)
self.x = UInt16Be()
self.y = UInt16Be()
self.width = UInt16Be()
self.height = UInt16Be()
self.encoding = SInt32Be()
class KeyEvent(CompositeType):
"""
@summary: Key event structure message
Use to send a keyboard event
"""
def __init__(self):
CompositeType.__init__(self)
self.downFlag = UInt8(False)
self.padding = UInt16Be()
self.key = UInt32Be()
class PointerEvent(CompositeType):
"""
@summary: Pointer event structure message
Use to send mouse event
"""
def __init__(self):
CompositeType.__init__(self)
self.mask = UInt8()
self.x = UInt16Be()
self.y = UInt16Be()
class ClientCutText(CompositeType):
"""
@summary: Client cut text message message
Use to simulate copy paste (ctrl-c ctrl-v) only for text
"""
def __init__(self, text = ""):
CompositeType.__init__(self)
self.padding = (UInt16Be(), UInt8())
self.size = UInt32Be(len(text))
self.message = String(text)
class ServerCutTextHeader(CompositeType):
"""
@summary: Cut text header send from server to client
"""
def __init__(self):
CompositeType.__init__(self)
self.padding = (UInt16Be(), UInt8())
self.size = UInt32Be()
class RFB(RawLayer):
"""
@summary: Implement RFB protocol
"""
def __init__(self, listener):
"""
@param listener: listener use to inform new orders
"""
RawLayer.__init__(self)
#set client listener
self._clientListener = listener
#useful for RFB protocol
self._callbackBody = None
#protocol version negotiated
self._version = String(ProtocolVersion.RFB003008)
#number security launch by server
self._securityLevel = UInt8(SecurityType.INVALID)
#shared FrameBuffer client init message
self._sharedFlag = UInt8(False)
#server init message
#which contain FrameBuffer dim and pixel format
self._serverInit = ServerInit()
#client pixel format
self._pixelFormat = PixelFormat()
#server name
self._serverName = String()
#nb rectangle
self._nbRect = 0
#current rectangle header
self._currentRect = Rectangle()
#for vnc security type
self._password = '\0' * 8
def expectWithHeader(self, expectedHeaderLen, callbackBody):
"""
2nd level of waiting event
read expectedHeaderLen that contain body size
@param expectedHeaderLen: contains the number of bytes, which body length needs to be encoded
@param callbackBody: next state use when expected date from expectedHeaderLen
are received
"""
self._callbackBody = callbackBody
self.expect(expectedHeaderLen, self.expectedBody)
def expectedBody(self, data):
"""
Read header and wait header value to call next state
@param data: Stream that length are to header length (1|2|4 bytes)
set next state to callBack body when length read from header
are received
"""
bodyLen = None
if data.len == 1:
bodyLen = UInt8()
elif data.len == 2:
bodyLen = UInt16Be()
elif data.len == 4:
bodyLen = UInt32Be()
else:
log.error("invalid header length")
return
data.readType(bodyLen)
self.expect(bodyLen.value, self._callbackBody)
def connect(self):
"""
Call when transport layer connection is made
in Client mode -> wait protocol version
"""
self.expect(12, self.recvProtocolVersion)
def readProtocolVersion(self, data):
"""
Read protocol version
@param data: Stream may contain protocol version string (ProtocolVersion)
"""
data.readType(self._version)
if not self._version.value in [ProtocolVersion.RFB003003, ProtocolVersion.RFB003007, ProtocolVersion.RFB003008]:
self._version.value = ProtocolVersion.UNKNOWN
def recvProtocolVersion(self, data):
"""
Read handshake packet
If protocol receive from client is unknown
try best version of protocol version (ProtocolVersion.RFB003008)
@param data: Stream
"""
self.readProtocolVersion(data)
if self._version.value == ProtocolVersion.UNKNOWN:
log.info("Unknown protocol version %s send 003.008"%data.getvalue())
#protocol version is unknown try best version we can handle
self._version.value = ProtocolVersion.RFB003008
#send same version of
self.send(self._version)
#next state read security
if self._version.value == ProtocolVersion.RFB003003:
self.expect(4, self.recvSecurityServer)
else:
self.expectWithHeader(1, self.recvSecurityList)
def recvSecurityServer(self, data):
"""
Security handshake for 33 RFB version
Server imposed security level
@param data: well formed packet
"""
#TODO!!!
pass
def recvSecurityList(self, data):
"""
Read security list packet send from server to client
@param data: Stream that contains well formed packet
"""
securityList = []
while data.dataLen() > 0:
securityElement = UInt8()
data.readType(securityElement)
securityList.append(securityElement)
#select high security level
for s in securityList:
if s.value in [SecurityType.NONE, SecurityType.VNC] and s > self._securityLevel:
self._securityLevel = s
break
#send back security level choosen
self.send(self._securityLevel)
if self._securityLevel.value == SecurityType.VNC:
self.expect(16, self.recvVNCChallenge)
else:
self.expect(4, self.recvSecurityResult)
def recvVNCChallenge(self, data):
"""
@summary: receive challenge in VNC authentication case
@param data: Stream that contain well formed packet
"""
key = (self._password + '\0' * 8)[:8]
newkey = []
for ki in range(len(key)):
bsrc = ord(key[ki])
btgt = 0
for i in range(8):
if bsrc & (1 << i):
btgt = btgt | (1 << 7-i)
newkey.append(chr(btgt))
algo = des(newkey)
self.send(String(algo.encrypt(data.getvalue())))
self.expect(4, self.recvSecurityResult)
def recvSecurityResult(self, data):
"""
Read security result packet
Use by server to inform connection status of client
@param data: Stream that contain well formed packet
"""
result = UInt32Be()
data.readType(result)
if result == UInt32Be(1):
log.info("Authentification failed")
if self._version.value == ProtocolVersion.RFB003008:
self.expectWithHeader(4, self.recvSecurityFailed)
else:
log.debug("Authentification OK")
self.sendClientInit()
def recvSecurityFailed(self, data):
"""
Send by server to inform reason of why it's refused client
@param data: Stream that contains well formed packet
"""
log.info("Security failed cause to %s"%data.getvalue())
def recvServerInit(self, data):
"""
Read server init packet
@param data: Stream that contains well formed packet
"""
data.readType(self._serverInit)
self.expectWithHeader(4, self.recvServerName)
def recvServerName(self, data):
"""
@summary: Read server name
@param data: Stream that contains well formed packet
"""
data.readType(self._serverName)
log.info("Server name %s"%str(self._serverName))
#end of handshake
#send pixel format
self.sendPixelFormat(self._pixelFormat)
#write encoding
self.sendSetEncoding()
#request entire zone
self.sendFramebufferUpdateRequest(False, 0, 0, self._serverInit.width.value, self._serverInit.height.value)
#now i'm ready to send event
self._clientListener.onReady()
self.expect(1, self.recvServerOrder)
def recvServerOrder(self, data):
"""
@summary: Read order receive from server
Main function for bitmap update from server to client
@param data: Stream that contains well formed packet
"""
packetType = UInt8()
data.readType(packetType)
if packetType.value == 0:
self.expect(3, self.recvFrameBufferUpdateHeader)
elif packetType.value == 2:
self._clientListener.onBell()
elif packetType.value == 3:
self.expect(7, self.recvServerCutTextHeader)
else:
log.error("Unknown message type %s"%packetType.value)
def recvFrameBufferUpdateHeader(self, data):
"""
@summary: Read frame buffer update packet header
@param data: Stream that contains well formed packet
"""
#padding
nbRect = UInt16Be()
self._nbRect = data.readType((UInt8(), nbRect))
self._nbRect = nbRect.value
self.expect(12, self.recvRectHeader)
def recvRectHeader(self, data):
"""
@summary: Read rectangle header
@param data: Stream that contains well formed packet
"""
data.readType(self._currentRect)
if self._currentRect.encoding.value == Encoding.RAW:
self.expect(self._currentRect.width.value * self._currentRect.height.value * (self._pixelFormat.BitsPerPixel.value / 8), self.recvRectBody)
def recvRectBody(self, data):
"""
@summary: Read body of rectangle update
@param data: Stream that contains well formed packet
"""
self._clientListener.recvRectangle(self._currentRect, self._pixelFormat, data.getvalue())
self._nbRect -= 1
#if there is another rect to read
if self._nbRect == 0:
#job is finish send a request
self.sendFramebufferUpdateRequest(True, 0, 0, self._serverInit.width.value, self._serverInit.height.value)
self.expect(1, self.recvServerOrder)
else:
self.expect(12, self.recvRectHeader)
def recvServerCutTextHeader(self, data):
"""
@summary: callback when expect server cut text message
@param data: Stream that contains well formed packet
"""
header = ServerCutTextHeader()
data.readType(header)
self.expect(header.size.value, self.recvServerCutTextBody)
def recvServerCutTextBody(self, data):
"""
@summary: Receive server cut text body
@param data: Stream that contains well formed packet
"""
self._clientListener.onCutText(data.getvalue())
self.expect(1, self.recvServerOrder)
def sendClientInit(self):
"""
@summary: Send client init packet
"""
self.send(self._sharedFlag)
self.expect(20, self.recvServerInit)
def sendPixelFormat(self, pixelFormat):
"""
@summary: Send pixel format structure
Very important packet that inform the image struct supported by the client
@param pixelFormat: PixelFormat struct
"""
self.send((UInt8(ClientToServerMessages.PIXEL_FORMAT), UInt16Be(), UInt8(), pixelFormat))
def sendSetEncoding(self):
"""
@summary: Send set encoding packet
Actually only RAW bitmap encoding are used
"""
self.send((UInt8(ClientToServerMessages.ENCODING), UInt8(), UInt16Be(1), SInt32Be(Encoding.RAW)))
def sendFramebufferUpdateRequest(self, incremental, x, y, width, height):
"""
@summary: Request server the specified zone
incremental means request only change before last update
"""
self.send((UInt8(ClientToServerMessages.FRAME_BUFFER_UPDATE_REQUEST), FrameBufferUpdateRequest(incremental, x, y, width, height)))
def sendKeyEvent(self, keyEvent):
"""
@summary: Write key event packet
@param keyEvent: KeyEvent struct to send
"""
self.send((UInt8(ClientToServerMessages.KEY_EVENT), keyEvent))
def sendPointerEvent(self, pointerEvent):
"""
@summary: Write pointer event packet
@param pointerEvent: PointerEvent struct use
"""
self.send((UInt8(ClientToServerMessages.POINTER_EVENT), pointerEvent))
def sendClientCutText(self, text):
"""
@summary: write client cut text event packet
"""
self.send((UInt8(ClientToServerMessages.CUT_TEXT), ClientCutText(text)))
class RFBClientListener(object):
"""
@summary: Interface use to expose event receive from RFB layer
"""
def recvRectangle(self, rectangle, pixelFormat, data):
"""
@summary: Receive rectangle order
Main update order type
@param rectangle: Rectangle type header of packet
@param pixelFormat: pixelFormat struct of current session
@param data: image data
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recvRectangle", "RFBClientListener"))
def onBell(self):
"""
@summary: receive bip from server
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onBell", "RFBClientListener"))
def onCutText(self, text):
"""
@summary: Receive cut text from server
@param text: text inner cut text event
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onCutText", "RFBClientListener"))
class RFBClientController(RFBClientListener):
"""
@summary: Class use to manage RFB order and dispatch throw observers for client side
"""
def __init__(self):
self._clientObservers = []
#rfb layer to send client orders
self._rfbLayer = RFB(self)
self._isReady = False
def getProtocol(self):
"""
@return: RFB layer build by controller
"""
return self._rfbLayer
def addClientObserver(self, observer):
"""
@summary: Add new observer for this protocol
@param observer: new observer
"""
self._clientObservers.append(observer)
observer._clientListener = self
def getWidth(self):
"""
@return: width of framebuffer
"""
return self._rfbLayer._serverInit.width.value
def getHeight(self):
"""
@return: height of framebuffer
"""
return self._rfbLayer._serverInit.height.value
def getScreen(self):
"""
@return: (width, height) of screen
"""
return (self.getWidth(), self.getHeight())
def setPassword(self, password):
"""
@summary: set password for vnc authentication type
@param password: password for session
"""
self._rfbLayer._password = password
def onReady(self):
"""
@summary: rfb stack is reday to send or receive event
"""
self._isReady = True
for observer in self._clientObservers:
observer.onReady()
def recvRectangle(self, rectangle, pixelFormat, data):
"""
@summary: Receive rectangle order
Main update order type
@param rectangle: Rectangle type header of packet
@param pixelFormat: pixelFormat struct of current session
@param data: image data
"""
for observer in self._clientObservers:
observer.onUpdate(rectangle.width.value, rectangle.height.value, rectangle.x.value, rectangle.y.value, pixelFormat, rectangle.encoding, data)
def onBell(self):
"""
@summary: biiiip event
"""
for observer in self._clientObservers:
observer.onBell()
def onCutText(self, text):
"""
@summary: receive cut text event
@param text: text in cut text event
"""
for observer in self._clientObservers:
observer.onCutText(text)
def onClose(self):
"""
@summary: handle on close events
"""
if not self._isReady:
log.debug("Close on non ready layer means authentication error")
return
for observer in self._clientObservers:
observer.onClose()
def sendKeyEvent(self, isDown, key):
"""
@summary: Send a key event throw RFB protocol
@param isDown: boolean notify if key is pressed or not (True if key is pressed)
@param key: ASCII code of key
"""
if not self._isReady:
log.info("Try to send key event on non ready layer")
return
try:
event = KeyEvent()
event.downFlag.value = isDown
event.key.value = key
self._rfbLayer.sendKeyEvent(event)
except InvalidValue:
log.debug("Try to send an invalid key event")
def sendPointerEvent(self, mask, x, y):
"""
@summary: Send a pointer event throw RFB protocol
@param mask: mask of button if button 1 and 3 are pressed then mask is 00000101
@param x: x coordinate of mouse pointer
@param y: y pointer of mouse pointer
"""
if not self._isReady:
log.info("Try to send pointer event on non ready layer")
return
try:
event = PointerEvent()
event.mask.value = mask
event.x.value = x
event.y.value = y
self._rfbLayer.sendPointerEvent(event)
except InvalidValue:
log.debug("Try to send an invalid pointer event")
def close(self):
"""
@summary: close rfb stack
"""
self._rfbLayer.close()
class ClientFactory(RawLayerClientFactory):
"""
@summary: Twisted Factory of RFB protocol
"""
def buildRawLayer(self, addr):
"""
@summary: Function call by twisted on connection
@param addr: address where client try to connect
"""
controller = RFBClientController()
self.buildObserver(controller, addr)
return controller.getProtocol()
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()
def buildObserver(self, controller, addr):
"""
@summary: Build an RFB observer object
@param controller: controller use for rfb session
@param addr: destination
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildObserver", "ClientFactory"))
class RFBClientObserver(object):
"""
@summary: RFB client protocol observer
"""
def __init__(self, controller):
self._controller = controller
self._controller.addClientObserver(self)
def getController(self):
"""
@return: RFB controller use by observer
"""
return self._controller
def keyEvent(self, isPressed, key):
"""
@summary: Send a key event
@param isPressed: state of key
@param key: ASCII code of key
"""
self._controller.sendKeyEvent(isPressed, key)
def mouseEvent(self, button, x, y):
"""
@summary: Send a mouse event to RFB Layer
@param button: button number which is pressed (0,1,2,3,4,5,6,7)
@param x: x coordinate of mouse pointer
@param y: y coordinate of mouse pointer
"""
mask = 0
if button == 1:
mask = 1
elif button > 1:
mask = 1 << button - 1
self._controller.sendPointerEvent(mask, x, y)
def onReady(self):
"""
@summary: Event when network stack is ready to receive or send event
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RFBClientObserver"))
def onClose(self):
"""
@summary: On close event
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RFBClientObserver"))
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
"""
@summary: Receive FrameBuffer update
@param width : width of image
@param height : height of image
@param x : x position
@param y : y position
@param pixelFormat : pixel format struct from rfb.types
@param encoding : encoding struct from rfb.types
@param data : in respect of dataFormat and pixelFormat
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onUpdate", "RFBClientObserver"))
def onCutText(self, text):
"""
@summary: event when server send cut text event
@param text: text received
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onCutText", "RFBClientObserver"))
def onBell(self):
"""
@summary: event when server send biiip
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onBell", "RFBClientObserver"))

View File

@@ -24,7 +24,7 @@
def KSA(key):
keylength = len(key)
S = range(256)
S = list(range(256))
j = 0
for i in range(256):
@@ -50,8 +50,10 @@ def RC4(key):
S = KSA(key)
return PRGA(S)
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])
def RC4Key(key):
return RC4(key)
def crypt(keystream, plaintext: bytes) -> bytes:
return bytes([c ^ next(keystream) for c in plaintext])

View File

@@ -148,13 +148,10 @@ def extractRSAKey(certificate):
"""
#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
binaryTuple = certificate.getComponentByName('tbsCertificate').getComponentByName('subjectPublicKeyInfo').getComponentByName('subjectPublicKey')
l = int("".join([str(i) for i in binaryTuple]), 2)
return extractRSAKeyFromASN1(hex(l)[2:-1].decode('hex'))
def extractRSAKeyFromASN1(subjectPublicKey):
rsaKey = decoder.decode(subjectPublicKey, asn1Spec=RSAPublicKey())[0]
return rsaKey.getComponentByName('modulus')._value , rsaKey.getComponentByName('publicExponent')._value

View File

@@ -23,13 +23,12 @@ Qt specific code
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.core.error import CallPureVirtualFuntion
from PyQt5 import QtWidgets
from rdpy.core.rdp import RDPClientObserver
from rdpy.model.error import CallPureVirtualFuntion
import sys
import rdpy.core.log as log
import rdpy.model.log as log
import rle
class QAdaptor(object):
@@ -72,112 +71,10 @@ def qtImageFormatFromRFBPixelFormat(pixelFormat):
@summary: convert RFB pixel format to QtGui.QImage format
"""
if pixelFormat.BitsPerPixel.value == 32:
return QtGui.QImage.Format_RGB32
return QtWidgets.QImage.Format_RGB32
elif pixelFormat.BitsPerPixel.value == 16:
return QtGui.QImage.Format_RGB16
return QtWidgets.QImage.Format_RGB16
class RFBClientQt(RFBClientObserver, QAdaptor):
"""
@summary: QAdaptor for specific RFB protocol stack
is to an RFB observer
"""
def __init__(self, controller):
"""
@param controller: controller for observer
@param width: width of widget
@param height: height of widget
"""
RFBClientObserver.__init__(self, controller)
self._widget = QRemoteDesktop(1024, 800, self)
def getWidget(self):
"""
@return: widget use for render
"""
return self._widget
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
"""
@summary: Implement RFBClientObserver interface
@param width: width of new image
@param height: height of new image
@param x: x position of new image
@param y: y position of new image
@param pixelFormat: pixefFormat structure in rfb.message.PixelFormat
@param encoding: encoding type rfb.message.Encoding
@param data: image data in accordance with pixel format and encoding
"""
imageFormat = qtImageFormatFromRFBPixelFormat(pixelFormat)
if imageFormat is None:
log.error("Receive image in bad format")
return
image = QtGui.QImage(data, width, height, imageFormat)
self._widget.notifyImage(x, y, image, width, height)
def onCutText(self, text):
"""
@summary: event when server send cut text event
@param text: text received
"""
def onBell(self):
"""
@summary: event when server send biiip
"""
def onReady(self):
"""
@summary: Event when network stack is ready to receive or send event
"""
(width, height) = self._controller.getScreen()
self._widget.resize(width, height)
def sendMouseEvent(self, e, isPressed):
"""
@summary: Convert Qt mouse event to RFB mouse event
@param e: qMouseEvent
@param isPressed: event come from press or release action
"""
button = e.button()
buttonNumber = 0
if button == QtCore.Qt.LeftButton:
buttonNumber = 1
elif button == QtCore.Qt.MidButton:
buttonNumber = 2
elif button == QtCore.Qt.RightButton:
buttonNumber = 3
self.mouseEvent(buttonNumber, e.pos().x(), e.pos().y())
def sendKeyEvent(self, e, isPressed):
"""
@summary: Convert Qt key press event to RFB press event
@param e: qKeyEvent
@param isPressed: event come from press or release action
"""
self.keyEvent(isPressed, e.nativeVirtualKey())
def sendWheelEvent(self, e):
"""
@summary: Convert Qt wheel event to RFB Wheel event
@param e: QKeyEvent
@param isPressed: event come from press or release action
"""
pass
def closeEvent(self, e):
"""
@summary: Call when you want to close connection
@param: QCloseEvent
"""
self._controller.close()
def onClose(self):
"""
@summary: Call when stack is close
"""
#do something maybe a message
pass
def RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data):
"""
@@ -195,36 +92,36 @@ def RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data):
if isCompress:
buf = bytearray(width * height * 2)
rle.bitmap_decompress(buf, width, height, data, 2)
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB555)
image = QtWidgets.QImage(buf, width, height, QtWidgets.QImage.Format_RGB555)
else:
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB555).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
image = QtWidgets.QImage(data, width, height, QtWidgets.QImage.Format_RGB555).transformed(QtWidgets.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
elif bitsPerPixel == 16:
if isCompress:
buf = bytearray(width * height * 2)
rle.bitmap_decompress(buf, width, height, data, 2)
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB16)
image = QtWidgets.QImage(buf, width, height, QtWidgets.QImage.Format_RGB16)
else:
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB16).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
image = QtWidgets.QImage(data, width, height, QtWidgets.QImage.Format_RGB16).transformed(QtWidgets.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
elif bitsPerPixel == 24:
if isCompress:
buf = bytearray(width * height * 3)
rle.bitmap_decompress(buf, width, height, data, 3)
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB888)
image = QtWidgets.QImage(buf, width, height, QtWidgets.QImage.Format_RGB888)
else:
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB888).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
image = QtWidgets.QImage(data, width, height, QtWidgets.QImage.Format_RGB888).transformed(QtWidgets.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
elif bitsPerPixel == 32:
if isCompress:
buf = bytearray(width * height * 4)
rle.bitmap_decompress(buf, width, height, data, 4)
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB32)
image = QtWidgets.QImage(buf, width, height, QtWidgets.QImage.Format_RGB32)
else:
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB32).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
image = QtWidgets.QImage(data, width, height, QtWidgets.QImage.Format_RGB32).transformed(QtWidgets.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
else:
log.error("Receive image in bad format")
image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
image = QtWidgets.QImage(width, height, QtWidgets.QImage.Format_RGB32)
return image
class RDPClientQt(RDPClientObserver, QAdaptor):
@@ -303,7 +200,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
@param isCompress: {bool} use RLE compression
@param data: {str} bitmap data
"""
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data);
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data)
#if image need to be cut
#For bit alignement server may send more than image pixel
self._widget.notifyImage(destLeft, destTop, image, destRight - destLeft + 1, destBottom - destTop + 1)
@@ -311,17 +208,26 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
def onReady(self):
"""
@summary: Call when stack is ready
@see: rdp.RDPClientObserver.onReady
"""
#do something maybe a loader
def onSessionReady(self):
"""
@summary: Windows session is ready
@see: rdp.RDPClientObserver.onSessionReady
"""
pass
def onClose(self):
"""
@summary: Call when stack is close
@see: rdp.RDPClientObserver.onClose
"""
#do something maybe a message
class QRemoteDesktop(QtGui.QWidget):
class QRemoteDesktop(QtWidgets.QWidget):
"""
@summary: Qt display widget
"""
@@ -336,16 +242,10 @@ class QRemoteDesktop(QtGui.QWidget):
self._adaptor = adaptor
#set correct size
self.resize(width, height)
#refresh stack of image
#because we can update image only in paint
#event function. When protocol receive image
#we will stock into refresh list
#and in paint event paint list of all refresh images
self._refresh = []
#bind mouse event
self.setMouseTracking(True)
#buffer image
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
self._buffer = QtWidgets.QImage(width, height, QtWidgets.QImage.Format_RGB32)
def notifyImage(self, x, y, qimage, width, height):
"""
@@ -354,8 +254,9 @@ class QRemoteDesktop(QtGui.QWidget):
@param y: y position of new image
@param qimage: new QImage
"""
#save in refresh list (order is important)
self._refresh.append((x, y, qimage, width, height))
#fill buffer image
with QtWidgets.QPainter(self._buffer) as qp:
qp.drawImage(x, y, qimage, 0, 0, width, height)
#force update
self.update()
@@ -365,24 +266,17 @@ class QRemoteDesktop(QtGui.QWidget):
@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)
self._buffer = QtWidgets.QImage(width, height, QtWidgets.QImage.Format_RGB32)
QtWidgets.QWidget.resize(self, width, height)
def paintEvent(self, e):
"""
@summary: Call when Qt renderer engine estimate that is needed
@param e: QEvent
"""
#fill buffer image
with QtGui.QPainter(self._buffer) as qp:
#draw image
for (x, y, image, width, height) in self._refresh:
qp.drawImage(x, y, image, 0, 0, width, height)
#draw in widget
with QtGui.QPainter(self) as qp:
with QtWidgets.QPainter(self) as qp:
qp.drawImage(0, 0, self._buffer)
self._refresh = []
def mouseMoveEvent(self, event):
"""

View File

@@ -4,32 +4,23 @@ import setuptools
from distutils.core import setup, Extension
setup(name='rdpy',
version='1.2.1',
version='2.0.0',
description='Remote Desktop Protocol in Python',
long_description="""
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server side).
RDPY is built over the event driven network engine Twisted.
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server side). RDPY is built over the event driven network engine Twisted.
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
RDPY provide RDP and VNC binaries : RDP Man In The Middle proxy which record session, RDP Honeypot, RDP screenshoter, RDP client, VNC client, VNC screenshoter, RSS Player
""",
author='Sylvain Peyrefitte',
author_email='citronneur@gmail.com',
url='https://github.com/citronneur/rdpy',
packages=[
'rdpy',
'rdpy.core',
'rdpy.security',
'rdpy.protocol',
'rdpy.protocol.rdp',
'rdpy.protocol.rdp.pdu',
'rdpy.protocol.rfb',
'rdpy.model',
'rdpy.security',
'rdpy.core.pdu',
'rdpy.core.nla',
'rdpy.core.t125',
'rdpy.ui'
],
ext_modules=[Extension('rle', ['ext/rle.c'])],
@@ -43,11 +34,10 @@ setup(name='rdpy',
'bin/rdpy-vncscreenshot.py'
],
install_requires=[
'twisted',
'pyopenssl',
'PyQt5',
'PyQt5-sip',
'service_identity',
'qt4reactor',
'rsa',
'pyasn1',
'pyasn1'
],
)

View File

@@ -56,7 +56,7 @@ class LayerTest(unittest.TestCase):
"""
class TestAutomata(rdpy.core.layer.RawLayer):
def expectedCallBack(self, data):
if data.dataLen() == 4:
if data.data_len() == 4:
raise LayerTest.LayerCaseException()
t = TestAutomata()
@@ -69,7 +69,7 @@ class LayerTest(unittest.TestCase):
"""
class TestAutomata(rdpy.core.layer.RawLayer):
def expectedCallBack(self, data):
if data.dataLen() == 4:
if data.data_len() == 4:
raise LayerTest.LayerCaseException()
t = TestAutomata()

View File

@@ -56,7 +56,7 @@ class TypeTest(unittest.TestCase):
def __write__(self, s):
raise Exception()
s = rdpy.core.type.Stream()
self.assertRaises(Exception, s.writeType, TestType(conditional = lambda:True))
self.assertRaises(Exception, s.write_type, TestType(conditional = lambda:True))
@unittest.expectedFailure
def test_type_write_conditional_false(self):
@@ -67,7 +67,7 @@ class TypeTest(unittest.TestCase):
def __write__(self, s):
raise Exception()
s = rdpy.core.type.Stream()
self.assertRaises(Exception, s.writeType, TestType(conditional = lambda:False))
self.assertRaises(Exception, s.write_type, TestType(conditional = lambda:False))
def test_type_read_conditional_true(self):
"""
@@ -77,7 +77,7 @@ class TypeTest(unittest.TestCase):
def __read__(self, s):
raise Exception()
s = rdpy.core.type.Stream()
self.assertRaises(Exception, s.readType, TestType(conditional = lambda:True))
self.assertRaises(Exception, s.read_type, TestType(conditional = lambda:True))
@unittest.expectedFailure
def test_type_read_conditional_false(self):
@@ -88,7 +88,7 @@ class TypeTest(unittest.TestCase):
def __read__(self, s):
raise Exception()
s = rdpy.core.type.Stream()
self.assertRaises(Exception, s.readType, TestType(conditional = lambda:False))
self.assertRaises(Exception, s.read_type, TestType(conditional = lambda:False))
def test_sizeof_conditional_true(self):
@@ -138,7 +138,7 @@ class TypeTest(unittest.TestCase):
@summary: test write uint8 in stream
"""
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt8(1))
s.write_type(rdpy.core.type.UInt8(1))
self.assertEqual(''.join(s.buflist), '\x01', "invalid stream write")
def test_stream_write_uint16Le_type(self):
@@ -146,7 +146,7 @@ class TypeTest(unittest.TestCase):
@summary: test write UInt16Le in stream
"""
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt16Le(1))
s.write_type(rdpy.core.type.UInt16Le(1))
self.assertEqual(''.join(s.buflist), '\x01\x00', "invalid stream write")
def test_stream_write_uint16Be_type(self):
@@ -154,7 +154,7 @@ class TypeTest(unittest.TestCase):
@summary: test write UInt16Be in stream
"""
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt16Be(1))
s.write_type(rdpy.core.type.UInt16Be(1))
self.assertEqual(''.join(s.buflist), '\x00\x01', "invalid stream write")
def test_stream_write_uint24Le_type(self):
@@ -162,7 +162,7 @@ class TypeTest(unittest.TestCase):
@summary: test write UInt24Le in stream
"""
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt24Le(1))
s.write_type(rdpy.core.type.UInt24Le(1))
self.assertEqual(''.join(s.buflist), '\x01\x00\x00', "invalid stream write")
def test_stream_write_uint24Be_type(self):
@@ -170,7 +170,7 @@ class TypeTest(unittest.TestCase):
@summary: test write uint24Be in stream
"""
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt24Be(1))
s.write_type(rdpy.core.type.UInt24Be(1))
self.assertEqual(''.join(s.buflist), '\x00\x00\x01', "invalid stream write")
def test_stream_write_uint32Le_type(self):
@@ -178,7 +178,7 @@ class TypeTest(unittest.TestCase):
@summary: test write UInt32Le in stream
"""
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt32Le(1))
s.write_type(rdpy.core.type.UInt32Le(1))
self.assertEqual(''.join(s.buflist), '\x01\x00\x00\x00', "invalid stream write")
def test_stream_write_uint32Be_type(self):
@@ -186,7 +186,7 @@ class TypeTest(unittest.TestCase):
@summary: test write UInt32Be in stream
"""
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt32Be(1))
s.write_type(rdpy.core.type.UInt32Be(1))
self.assertEqual(''.join(s.buflist), '\x00\x00\x00\x01', "invalid stream write")
def test_stream_read_uint8_type(self):
@@ -195,9 +195,9 @@ class TypeTest(unittest.TestCase):
"""
s = rdpy.core.type.Stream('\x01')
t = rdpy.core.type.UInt8()
s.readType(t)
s.read_type(t)
self.assertEqual(t.value, 1, "invalid stream read value")
self.assertEqual(s.dataLen(), 0, "not read all stream")
self.assertEqual(s.data_len(), 0, "not read all stream")
def test_stream_read_uint16Le_type(self):
"""
@@ -205,9 +205,9 @@ class TypeTest(unittest.TestCase):
"""
s = rdpy.core.type.Stream('\x01\x00')
t = rdpy.core.type.UInt16Le()
s.readType(t)
s.read_type(t)
self.assertEqual(t.value, 1, "invalid stream read value")
self.assertEqual(s.dataLen(), 0, "not read all stream")
self.assertEqual(s.data_len(), 0, "not read all stream")
def test_stream_read_uint16Be_type(self):
"""
@@ -215,9 +215,9 @@ class TypeTest(unittest.TestCase):
"""
s = rdpy.core.type.Stream('\x00\x01')
t = rdpy.core.type.UInt16Be()
s.readType(t)
s.read_type(t)
self.assertEqual(t.value, 1, "invalid stream read value")
self.assertEqual(s.dataLen(), 0, "not read all stream")
self.assertEqual(s.data_len(), 0, "not read all stream")
def test_stream_read_uint24Le_type(self):
"""
@@ -225,9 +225,9 @@ class TypeTest(unittest.TestCase):
"""
s = rdpy.core.type.Stream('\x01\x00\x00')
t = rdpy.core.type.UInt24Le()
s.readType(t)
s.read_type(t)
self.assertEqual(t.value, 1, "invalid stream read value")
self.assertEqual(s.dataLen(), 0, "not read all stream")
self.assertEqual(s.data_len(), 0, "not read all stream")
def test_stream_read_uint24Be_type(self):
"""
@@ -235,9 +235,9 @@ class TypeTest(unittest.TestCase):
"""
s = rdpy.core.type.Stream('\x00\x00\x01')
t = rdpy.core.type.UInt24Be()
s.readType(t)
s.read_type(t)
self.assertEqual(t.value, 1, "invalid stream read")
self.assertEqual(s.dataLen(), 0, "not read all stream")
self.assertEqual(s.data_len(), 0, "not read all stream")
def test_stream_read_uint32Le_type(self):
"""
@@ -245,9 +245,9 @@ class TypeTest(unittest.TestCase):
"""
s = rdpy.core.type.Stream('\x01\x00\x00\x00')
t = rdpy.core.type.UInt32Le()
s.readType(t)
s.read_type(t)
self.assertEqual(t.value, 1, "invalid stream read value")
self.assertEqual(s.dataLen(), 0, "not read all stream")
self.assertEqual(s.data_len(), 0, "not read all stream")
def test_stream_read_uint32Be_type(self):
"""
@@ -255,9 +255,9 @@ class TypeTest(unittest.TestCase):
"""
s = rdpy.core.type.Stream('\x00\x00\x00\x01')
t = rdpy.core.type.UInt32Be()
s.readType(t)
s.read_type(t)
self.assertEqual(t.value, 1, "invalid stream read")
self.assertEqual(s.dataLen(), 0, "not read all stream")
self.assertEqual(s.data_len(), 0, "not read all stream")
def test_stream_read_optional_singletype(self):
"""
@@ -267,7 +267,7 @@ class TypeTest(unittest.TestCase):
t = rdpy.core.type.SimpleType("I", 4, False, 0, optional = True)
#empty stream
s1 = rdpy.core.type.Stream()
s1.readType(t)
s1.read_type(t)
self.assertEqual(t.value, 0, "invalid stream read optional value")
def test_stream_read_conditional_singletype_false(self):
@@ -277,7 +277,7 @@ class TypeTest(unittest.TestCase):
#unsigned int case
t = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:False)
s1 = rdpy.core.type.Stream("\x01\x00\x00\x00")
s1.readType(t)
s1.read_type(t)
self.assertEqual(t.value, 0, "invalid stream read conditional value")
def test_stream_read_conditional_singletype_true(self):
@@ -287,7 +287,7 @@ class TypeTest(unittest.TestCase):
#unsigned int case
t = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:True)
s1 = rdpy.core.type.Stream("\x01\x00\x00\x00")
s1.readType(t)
s1.read_type(t)
self.assertEqual(t.value, 1, "invalid stream read conditional value")
def test_stream_read_rollback_constant_constraint(self):
@@ -302,9 +302,9 @@ class TypeTest(unittest.TestCase):
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00")
try:
s.readType(TestComposite())
s.read_type(TestComposite())
except Exception:
self.assertEqual(s.readLen(), 0, "invalid stream roll back operation")
self.assertEqual(s.read_len(), 0, "invalid stream roll back operation")
return
self.assertTrue(False, "Constant constraint fail")
@@ -327,9 +327,9 @@ class TypeTest(unittest.TestCase):
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
try:
s.readType(TestComposite())
s.read_type(TestComposite())
except Exception:
self.assertEqual(s.readLen(), 0, "invalid stream roll back operation")
self.assertEqual(s.read_len(), 0, "invalid stream roll back operation")
return
self.assertTrue(False, "Constant constraint fail")
@@ -352,9 +352,9 @@ class TypeTest(unittest.TestCase):
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
try:
s.readType(TestComposite())
s.read_type(TestComposite())
except Exception:
self.assertEqual(s.readLen(), 0, "invalid stream roll back operation")
self.assertEqual(s.read_len(), 0, "invalid stream roll back operation")
return
self.assertTrue(False, "Constant constraint fail")
@@ -369,8 +369,8 @@ class TypeTest(unittest.TestCase):
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")
s.read_type(TestReadLength(rdpy.core.type.UInt8(10)))
self.assertEqual(s.data_len(), 0, "invalid stream read trash data as padding")
def test_stream_read_with_static_length_inferior(self):
"""
@@ -383,7 +383,7 @@ class TypeTest(unittest.TestCase):
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)))
self.assertRaises(InvalidSize, s.read_type, TestReadLength(rdpy.core.type.UInt8(2)))
def test_stream_read_string(self):
"""

View File

@@ -26,9 +26,9 @@ import os, sys
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.protocol.rdp.ber as ber
import rdpy.core.t125.ber as ber
import rdpy.core.type as type
import rdpy.core.error as error
class BERTest(unittest.TestCase):
"""
@@ -40,7 +40,7 @@ class BERTest(unittest.TestCase):
@summary: test readLength function in ber module
"""
s1 = type.Stream()
s1.writeType(type.UInt8(0x1a))
s1.write_type(type.UInt8(0x1a))
s1.pos = 0
l1 = ber.readLength(s1)
@@ -48,7 +48,7 @@ class BERTest(unittest.TestCase):
self.assertTrue(l1 == 0x1a, "readLength fail in small format")
s2 = type.Stream()
s2.writeType((type.UInt8(0x81),type.UInt8(0xab)))
s2.write_type((type.UInt8(0x81), type.UInt8(0xab)))
s2.pos = 0
l2 = ber.readLength(s2)
@@ -56,7 +56,7 @@ class BERTest(unittest.TestCase):
self.assertTrue(l2 == 0xab, "readLength fail in big format of size 1")
s3 = type.Stream()
s3.writeType((type.UInt8(0x82),type.UInt16Be(0xabab)))
s3.write_type((type.UInt8(0x82), type.UInt16Be(0xabab)))
s3.pos = 0
l3 = ber.readLength(s3)

View File

@@ -0,0 +1,128 @@
#
# 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.nla.cssp and ntlm module
"""
import unittest
import os, sys
# Change path so we find rdpy
sys.path.insert(1, os.path.join(sys.path[0], '..'))
from rdpy.core.nla import ntlm, cssp
from rdpy.security import rc4
pubKeyHex = """
MIGJAoGBAJ6VtUEDxTPqKWUrZe8wcd1zuzA77Mpyz73g+C
H/ppd2oQi10saVgdK6cRBKrCU0N6DD\nV/DqH4yE63vmbF
AmH7dBCljTgIc9C0HZvFQ6D3cUefW5pDjrEwg1rr+zF1ri
WIk5xCJ/FleQCK+R\nO5XIU9DAjhmK8xC8yMdC+xLeLV6D
AgMBAAE=
"""
peer0_0 = """
MC+gAwIBAqEoMCYwJKAiBCBOVExNU1NQAAEAAAA1gghgAA
AAAAAAAAAAAAAAAAAAAA==
"""
peer1_0 = """
MIIBCaADAgECoYIBADCB/TCB+qCB9wSB9E5UTE1TU1AAAg
AAAA4ADgA4AAAANYKJYnPHQ6nn/Lv8\nAAAAAAAAAACuAK
4ARgAAAAYBsR0AAAAPUwBJAFIAQQBEAEUATAACAA4AUwBJ
AFIAQQBEAEUATAAB\nABYAVwBBAFYALQBHAEwAVwAtADAA
MAA5AAQAGgBTAGkAcgBhAGQAZQBsAC4AbABvAGMAYQBsAA
MA\nMgB3AGEAdgAtAGcAbAB3AC0AMAAwADkALgBTAGkAcg
BhAGQAZQBsAC4AbABvAGMAYQBsAAUAGgBT\nAGkAcgBhAG
QAZQBsAC4AbABvAGMAYQBsAAcACABWkzQyx1XQAQAAAAA=
"""
peer0_1 = """
MIICD6ADAgECoYIBYjCCAV4wggFaoIIBVgSCAVJOVExNU1
NQAAMAAAAYABgAUAAAANoA2gBoAAAA\nCAAIAEAAAAAIAA
gASAAAAAAAAABQAAAAEAAQAEIBAAA1gghgYwBvAGMAbwB0
AG8AdABvABqKwrxk\n2sAom6gUCFFt1rgpCdKZGTNwnlGg
bsU5R/OelmrD/LLrx+ABAQAAAAAAAABFCDLHVdABKQnSmR
kz\ncJ4AAAAAAgAOAFMASQBSAEEARABFAEwAAQAWAFcAQQ
BWAC0ARwBMAFcALQAwADAAOQAEABoAUwBp\nAHIAYQBkAG
UAbAAuAGwAbwBjAGEAbAADADIAdwBhAHYALQBnAGwAdwAt
ADAAMAA5AC4AUwBpAHIA\nYQBkAGUAbAAuAGwAbwBjAGEA
bAAFABoAUwBpAHIAYQBkAGUAbAAuAGwAbwBjAGEAbAAHAA
gAVpM0\nMsdV0AEAAAAAv+z19mkBOu9b0Kv+P991MKOCAK
AEggCcAQAAAMQOzZaPZ8DdAAAAAM+IvTDiU0pL\njUnU6a
NjH+gZWeaIlqpQNYECmpElixwPj8aRRFVfTtkbw66U3gmo
3YBkUoVK8tfHESkivuWtV2tP\n3KGuAFv/6GzbFYQYlA7r
zZ1Bw072ps8s9cWeoNmAX6oiZmFW3j7LX3xkr7+nJoOoXI
jzvorm5kz3\nldCo8Iwh+IZ3SSnj0/h4H1GR
"""
class TestCsspNtlm(unittest.TestCase):
"""
@summary: test generate ntlmv2 over cssp authentication protocol
"""
def testCSSPNTLMAuthentication(self):
negotiate_data_request = cssp.decode_der_trequest(peer0_0.decode('base64'))
challenge_data_request = cssp.decode_der_trequest(peer1_0.decode('base64'))
authenticate_data_request = cssp.decode_der_trequest(peer0_1.decode('base64'))
negotiate_data = cssp.getNegoTokens(negotiate_data_request)[0]
challenge_data = cssp.getNegoTokens(challenge_data_request)[0]
authenticate_data = cssp.getNegoTokens(authenticate_data_request)[0]
negotiate = ntlm.NegotiateMessage()
negotiate_data.read_type(negotiate)
challenge = ntlm.ChallengeMessage()
challenge_data.read_type(challenge)
ServerChallenge = challenge.ServerChallenge.value
ServerName = challenge.getTargetInfo()
authenticate = ntlm.AuthenticateMessage()
authenticate_data.read_type(authenticate)
NtChallengeResponseTemp = authenticate.getNtChallengeResponse()
NTProofStr = NtChallengeResponseTemp[:16]
temp = NtChallengeResponseTemp[16:]
Timestamp = temp[8:16]
ClientChallenge = temp[16:24]
EncryptedRandomSessionKey = authenticate.getEncryptedRandomSession()
domain = "coco"
user = "toto"
password = "lolo"
ResponseKeyNT = ntlm.NTOWFv2(password, user, domain)
ResponseKeyLM = ntlm.LMOWFv2(password, user, domain)
NtChallengeResponse, LmChallengeResponse, SessionBaseKey = ntlm.ComputeResponsev2(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ClientChallenge, Timestamp, ServerName)
KeyExchangeKey = ntlm.KXKEYv2(SessionBaseKey, LmChallengeResponse, ServerChallenge)
ExportedSessionKey = ntlm.RC4K(KeyExchangeKey, EncryptedRandomSessionKey)
domain, user = domain, user
if challenge.NegotiateFlags.value & ntlm.Negotiate.NTLMSSP_NEGOTIATE_UNICODE:
domain, user = ntlm.UNICODE(domain), ntlm.UNICODE(user)
ClientSigningKey = ntlm.SIGNKEY(ExportedSessionKey, True)
ServerSigningKey = ntlm.SIGNKEY(ExportedSessionKey, False)
ClientSealingKey = ntlm.SEALKEY(ExportedSessionKey, True)
ServerSealingKey = ntlm.SEALKEY(ExportedSessionKey, False)
interface = ntlm.NTLMv2SecurityInterface(rc4.RC4Key(ClientSealingKey), rc4.RC4Key(ServerSealingKey), ClientSigningKey, ServerSigningKey)
EncryptedPubKeySrc = cssp.getPubKeyAuth(authenticate_data_request)
EncryptedPubKeyDst = interface.GSS_WrapEx(pubKeyHex.decode('base64'))
self.assertTrue(EncryptedPubKeySrc == EncryptedPubKeyDst, "Public key must be equals")

View File

@@ -26,7 +26,7 @@ import os, sys
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
from rdpy.protocol.rdp import lic, sec
from rdpy.core import lic, sec
import rdpy.core.type as type
#dump of server request
@@ -91,7 +91,7 @@ class TestLic(unittest.TestCase):
def test_valid_client_licensing_error_message(self):
l = lic.LicenseManager(None)
s = type.Stream()
s.writeType(lic.createValidClientLicensingErrorMessage())
s.write_type(lic.createValidClientLicensingErrorMessage())
#reinit position
s.pos = 0
@@ -105,10 +105,21 @@ class TestLic(unittest.TestCase):
if flag != sec.SecurityFlag.SEC_LICENSE_PKT:
return
s = type.Stream()
s.writeType(message)
s.write_type(message)
s.pos = 0
s.readType(lic.LicPacket(lic.ClientNewLicenseRequest()))
s.read_type(lic.LicPacket(lic.ClientNewLicenseRequest()))
self._state = True
def getGCCServerSettings(self):
class A:
def __init__(self):
self._is_readed = False
class B:
def __init__(self):
self.serverCertificate = A()
class C:
def __init__(self):
self.SC_SECURITY = B()
return C()
t = Transport()
l = lic.LicenseManager(t)

View File

@@ -26,7 +26,7 @@ import os, sys
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.protocol.rdp.per as per
import rdpy.core.t125.per as per
import rdpy.core.type as type
import rdpy.core.error as error
@@ -40,7 +40,7 @@ class PERTest(unittest.TestCase):
@summary: test readLength function in per module
"""
s1 = type.Stream()
s1.writeType(type.UInt8(0x1a))
s1.write_type(type.UInt8(0x1a))
s1.pos = 0
l1 = per.readLength(s1)
@@ -48,7 +48,7 @@ class PERTest(unittest.TestCase):
self.assertTrue(l1 == 0x1a, "readLength fail in small format")
s2 = type.Stream()
s2.writeType(type.UInt16Be(0x1abc | 0x8000))
s2.write_type(type.UInt16Be(0x1abc | 0x8000))
s2.pos = 0
l2 = per.readLength(s2)
@@ -78,15 +78,15 @@ class PERTest(unittest.TestCase):
for t in [type.UInt8, type.UInt16Be, type.UInt32Be]:
v = t(3)
s = type.Stream()
s.writeType((per.writeLength(type.sizeof(v)), v))
s.write_type((per.writeLength(type.sizeof(v)), v))
s.pos = 0
self.assertTrue(per.readInteger(s) == 3, "invalid readLength for type %s"%t)
self.assertTrue(per.readInteger(s) == 3, "invalid readLength for type %s" % t)
#error case
for l in [0, 3, 5]:
s = type.Stream()
s.writeType(per.writeLength(l))
s.write_type(per.writeLength(l))
s.pos = 0
self.assertRaises(error.InvalidValue, per.readInteger, s)

View File

@@ -26,9 +26,9 @@ import os, sys
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.protocol.rdp.tpkt as tpkt
import rdpy.core.tpkt as tpkt
import rdpy.core.type as type
import rdpy.core.error as error
class TPKTTest(unittest.TestCase):
"""
@@ -60,13 +60,13 @@ class TPKTTest(unittest.TestCase):
def connect(self):
pass
def recv(self, data):
data.readType(type.String("test_tpkt_layer_recv", constant = True))
data.read_type(type.String("test_tpkt_layer_recv", constant = True))
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))
s.write_type((type.UInt8(tpkt.Action.FASTPATH_ACTION_X224), type.UInt8(), type.UInt16Be(type.sizeof(message) + 4), message))
layer = tpkt.TPKT(Presentation())
layer.connect()
@@ -80,13 +80,13 @@ class TPKTTest(unittest.TestCase):
def setFastPathSender(self, fastPathSender):
pass
def recvFastPath(self, secFlag, fastPathS):
fastPathS.readType(type.String("test_tpkt_layer_recv_fastpath", constant = True))
fastPathS.read_type(type.String("test_tpkt_layer_recv_fastpath", constant = True))
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))
s.write_type((type.UInt8(tpkt.Action.FASTPATH_ACTION_FASTPATH), type.UInt8(type.sizeof(message) + 2), message))
layer = tpkt.TPKT(None)
layer.initFastPath(FastPathLayer())
@@ -101,13 +101,13 @@ class TPKTTest(unittest.TestCase):
def setFastPathSender(self, fastPathSender):
pass
def recvFastPath(self, secflag, fastPathS):
fastPathS.readType(type.String("test_tpkt_layer_recv_fastpath_ext_length", constant = True))
fastPathS.read_type(type.String("test_tpkt_layer_recv_fastpath_ext_length", constant = True))
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))
s.write_type((type.UInt8(tpkt.Action.FASTPATH_ACTION_FASTPATH), type.UInt16Be((type.sizeof(message) + 3) | 0x8000), message))
layer = tpkt.TPKT(None)
layer.initFastPath(FastPathLayer())

View File

@@ -26,7 +26,7 @@ import os, sys
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.protocol.rdp.x224 as x224
import rdpy.core.x224 as x224
import rdpy.core.type as type
import rdpy.core.error as error
@@ -53,12 +53,12 @@ class X224Test(unittest.TestCase):
"""
class Presentation(object):
def recv(self, data):
data.readType(type.String('test_x224_layer_recvData', constant = True))
data.read_type(type.String('test_x224_layer_recvData', constant = True))
raise X224Test.X224_PASS()
layer = x224.X224Layer(Presentation())
s = type.Stream()
s.writeType((x224.X224DataHeader(), type.String('test_x224_layer_recvData')))
s.write_type((x224.X224DataHeader(), type.String('test_x224_layer_recvData')))
#reinit position
s.pos = 0
@@ -71,10 +71,10 @@ class X224Test(unittest.TestCase):
class Transport(object):
def send(self, data):
s = type.Stream()
s.writeType(data)
s.write_type(data)
s.pos = 0
s.readType(x224.X224DataHeader())
s.readType(type.String('test_x224_layer_send', constant = True))
s.read_type(x224.X224DataHeader())
s.read_type(type.String('test_x224_layer_send', constant = True))
raise X224Test.X224_PASS()
layer = x224.X224Layer(None)
@@ -89,10 +89,10 @@ class X224Test(unittest.TestCase):
class Transport(object):
def send(self, data):
s = type.Stream()
s.writeType(data)
s.write_type(data)
s.pos = 0
t = x224.ClientConnectionRequestPDU()
s.readType(t)
t = x224.ConnectionRequestPDU()
s.read_type(t)
if t.protocolNeg.code != x224.NegociationType.TYPE_RDP_NEG_REQ:
raise X224Test.X224_FAIL()
@@ -106,29 +106,16 @@ class X224Test(unittest.TestCase):
layer.connect()
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
Server ask another protocol than SSL or RDP
"""
message = x224.ServerConnectionConfirm()
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
check negotiation failure
"""
message = x224.ServerConnectionConfirm()
message = x224.ConnectionConfirmPDU()
message.protocolNeg.code.value = x224.NegociationType.TYPE_RDP_NEG_FAILURE
s = type.Stream()
s.writeType(message)
s.write_type(message)
s.pos = 0
layer = x224.Client(None)
self.assertRaises(error.RDPSecurityNegoFail, layer.recvConnectionConfirm, s)
@@ -141,12 +128,10 @@ class X224Test(unittest.TestCase):
tls_begin = False
presentation_connect = False
class Transport(object):
def __init__(self):
class TLSTransport(object):
def startTLS(self, context):
global tls_begin
tls_begin = True
self.transport = TLSTransport()
def startTLS(self, context):
global tls_begin
tls_begin = True
class Presentation(object):
def connect(self):
@@ -156,11 +141,11 @@ class X224Test(unittest.TestCase):
def recvData(data):
raise X224Test.X224_PASS()
message = x224.ServerConnectionConfirm()
message = x224.ConnectionConfirmPDU()
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_SSL
s = type.Stream()
s.writeType(message)
s.write_type(message)
s.pos = 0
layer = x224.Client(Presentation())
layer._transport = Transport()
@@ -180,17 +165,17 @@ class X224Test(unittest.TestCase):
class Transport(object):
def send(self, data):
if not isinstance(data, x224.ServerConnectionConfirm):
if not isinstance(data, x224.ConnectionConfirmPDU):
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 X224Test.X224_FAIL()
def close(self):
raise X224Test.X224_PASS()
message = x224.ClientConnectionRequestPDU()
message = x224.ConnectionRequestPDU()
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_HYBRID
s = type.Stream()
s.writeType(message)
s.write_type(message)
s.pos = 0
layer = x224.Server(None, "key", "cert", True)
@@ -214,15 +199,12 @@ class X224Test(unittest.TestCase):
x224.ServerTLSContext = ServerTLSContext
class Transport(object):
def __init__(self):
class TLS(object):
def startTLS(self, context):
global tls
tls = True
self.transport = TLS()
def startTLS(self, context):
global tls
tls = True
def send(self, data):
if not isinstance(data, x224.ServerConnectionConfirm):
if not isinstance(data, x224.ConnectionConfirmPDU):
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 X224Test.X224_FAIL()
@@ -232,10 +214,10 @@ class X224Test(unittest.TestCase):
global connect_event
connect_event = True
message = x224.ClientConnectionRequestPDU()
message = x224.ConnectionRequestPDU()
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_SSL | x224.Protocols.PROTOCOL_RDP
s = type.Stream()
s.writeType(message)
s.write_type(message)
s.pos = 0
layer = x224.Server(Presentation(), "key", "cert")