Compare commits
118 Commits
v1.0
...
1e2cf2ef88
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e2cf2ef88 | ||
|
|
040ac6185d | ||
|
|
0f30de7d20 | ||
|
|
a23ae25a1f | ||
|
|
763ed2e3ee | ||
|
|
11d66a4818 | ||
|
|
9b99365f80 | ||
|
|
bd7c708bf3 | ||
|
|
1deb2d69ea | ||
|
|
0a5a1fd12c | ||
|
|
c97b451ce3 | ||
|
|
80f989a804 | ||
|
|
c6e100f9a6 | ||
|
|
15df00ec20 | ||
|
|
342349cf41 | ||
|
|
d6043106e3 | ||
|
|
bd7e73a6e7 | ||
|
|
fc1685e652 | ||
|
|
4320824aae | ||
|
|
5a438174b9 | ||
|
|
bb9483e7e1 | ||
|
|
bd362263f7 | ||
|
|
20de5f6f82 | ||
|
|
95052a323f | ||
|
|
0abf18d130 | ||
|
|
b57b3d7398 | ||
|
|
0695825d98 | ||
|
|
8fb4893b6f | ||
|
|
30a16fbb7a | ||
|
|
98494d0e73 | ||
|
|
a7058f1c54 | ||
|
|
1e2f284e97 | ||
|
|
8cd789480f | ||
|
|
e9a93d117b | ||
|
|
3fe16130d8 | ||
|
|
31b0920a87 | ||
|
|
36c05faa11 | ||
|
|
1c3119cffd | ||
|
|
d6bb21565d | ||
|
|
d6428430eb | ||
|
|
a4f4d71929 | ||
|
|
9e211c0199 | ||
|
|
5bd78cc012 | ||
|
|
e139a2c7eb | ||
|
|
349a8a7227 | ||
|
|
35514a2849 | ||
|
|
222ee76c91 | ||
|
|
30c3611bb9 | ||
|
|
6c93ca17b0 | ||
|
|
a01eb57cef | ||
|
|
9e50c2292d | ||
|
|
82d7798255 | ||
|
|
e9db7d720f | ||
|
|
1d5b15a310 | ||
|
|
a8ddaa77ff | ||
|
|
4c56f55266 | ||
|
|
1039e014c1 | ||
|
|
84ac320e82 | ||
|
|
02dfe8f46e | ||
|
|
8b159e668f | ||
|
|
b22a7d5dce | ||
|
|
1b1dfa06c8 | ||
|
|
8e7e6cdcb4 | ||
|
|
ff65c4b701 | ||
|
|
c5b8588e5f | ||
|
|
9dcac862ff | ||
|
|
e9fd801237 | ||
|
|
0686b2b677 | ||
|
|
9a4f5f254c | ||
|
|
522eda39e8 | ||
|
|
1b4fd4a686 | ||
|
|
33dbbf45d4 | ||
|
|
a3839e47c4 | ||
|
|
b378a1d15c | ||
|
|
7ff5c5caa3 | ||
|
|
f4808d0ae2 | ||
|
|
28f50b1f0e | ||
|
|
f9b30a668a | ||
|
|
d330564d2b | ||
|
|
69b3f6befe | ||
|
|
a0ae3d97ec | ||
|
|
f2481149d9 | ||
|
|
f9f92b8215 | ||
|
|
f36f8222df | ||
|
|
d4d98471eb | ||
|
|
f3a3ad8ac3 | ||
|
|
47a9f75fa6 | ||
|
|
92c642ffed | ||
|
|
551af0aa7a | ||
|
|
ccf0156150 | ||
|
|
e7c6e61a25 | ||
|
|
de1347840b | ||
|
|
873d1fac41 | ||
|
|
fc3efa60ee | ||
|
|
e4c04a7dd1 | ||
|
|
cdcfdec379 | ||
|
|
894dc44a52 | ||
|
|
47dba5bb6e | ||
|
|
c4d071a2e0 | ||
|
|
44d99eaca4 | ||
|
|
9419bea9b0 | ||
|
|
bd91093617 | ||
|
|
15fadfe3dd | ||
|
|
fa25a40721 | ||
|
|
99198321a4 | ||
|
|
3c3d7423a5 | ||
|
|
5af9f0708a | ||
|
|
4e7ea06906 | ||
|
|
7fc25e35f1 | ||
|
|
078aaa13f6 | ||
|
|
7e98bc373a | ||
|
|
0d9b766a03 | ||
|
|
c755148a3c | ||
|
|
84a3d7619b | ||
|
|
3e1b8e57eb | ||
|
|
744ff023ed | ||
|
|
bc89e67974 | ||
|
|
55ba109166 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -2,7 +2,8 @@
|
|||||||
.project
|
.project
|
||||||
.pydevproject
|
.pydevproject
|
||||||
README.md~
|
README.md~
|
||||||
rdpy/core/tmp/*
|
|
||||||
*.so
|
*.so
|
||||||
*.os
|
*.os
|
||||||
.sconsign.dblite
|
dist/*
|
||||||
|
build/*
|
||||||
|
rdpy.egg-info/*
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ before_install:
|
|||||||
- sudo apt-get install python-qt4
|
- 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/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/
|
- ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-packages/
|
||||||
- pip install qt4reactor pyopenssl twisted
|
- pip install qt4reactor pyopenssl twisted service_identity rsa pyasn1
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- python setup.py install
|
- python setup.py install
|
||||||
|
|||||||
302
README.md
302
README.md
@@ -1,25 +1,56 @@
|
|||||||
# RDPY [](https://travis-ci.org/citronneur/rdpy)
|
# RDPY [](https://travis-ci.org/citronneur/rdpy) [](http://badge.fury.io/py/rdpy)
|
||||||
|
|
||||||
Remote Desktop Protocol in twisted PYthon.
|
Remote Desktop Protocol in twisted python.
|
||||||
|
|
||||||
RDPY is still under development.
|
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 is a pure Python implementation ot the Microsoft RDP (Remote Desktop Protocol) protocol. RDPY is built over the event driven network engine Twisted.
|
RDPY provides the following 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
|
||||||
|
|
||||||
## Build
|
## 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
|
||||||
|
|
||||||
* python2.7
|
Dependencies are only needed for pyqt4 binaries :
|
||||||
* python-qt4
|
* rdpy-rdpclient
|
||||||
|
* rdpy-rdpscreenshot
|
||||||
|
* rdpy-vncclient
|
||||||
|
* rdpy-vncscreenshot
|
||||||
|
* rdpy-rssplayer
|
||||||
|
|
||||||
### Make
|
#### Linux
|
||||||
|
|
||||||
|
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
|
||||||
|
----|-------
|
||||||
|
[PyQt4](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x32.exe) | [PyQt4](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x64.exe/download)
|
||||||
|
[PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download) | [PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download)
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://github.com/citronneur/rdpy.git rdpy
|
$ git clone https://github.com/citronneur/rdpy.git rdpy
|
||||||
$ pip install twisted pyopenssl qt4reactor
|
$ pip install twisted pyopenssl qt4reactor service_identity rsa pyasn1
|
||||||
$ python rdpy/setup.py install
|
$ python rdpy/setup.py install
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -28,7 +59,7 @@ Or use PIP:
|
|||||||
$ pip install rdpy
|
$ pip install rdpy
|
||||||
```
|
```
|
||||||
|
|
||||||
For virtualenv, tou 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/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/
|
$ ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-packages/
|
||||||
@@ -36,145 +67,244 @@ $ ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-
|
|||||||
|
|
||||||
## RDPY Binaries
|
## RDPY Binaries
|
||||||
|
|
||||||
RDPY comes with some very useful binaries; These binaries are linux and windows compatible.
|
RDPY comes with some very useful binaries. These binaries are linux and windows compatible.
|
||||||
|
|
||||||
### rdpy-rdpclient
|
### rdpy-rdpclient
|
||||||
|
|
||||||
rdpy-rdpclient is a simple RDP Qt4 client .
|
rdpy-rdpclient is a simple RDP Qt4 client.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rdpy/bin/rdpy-rdpclient [-u username] [-p password] [-d domain] [...] XXX.XXX.XXX.XXX[:3389]
|
$ rdpy-rdpclient.py [-u username] [-p password] [-d domain] [-r rss_ouput_file] [...] XXX.XXX.XXX.XXX[:3389]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can use rdpy-rdpclient in a Recorder Session Scenario, used in rdpy-rdphoneypot.
|
||||||
|
|
||||||
### rdpy-vncclient
|
### rdpy-vncclient
|
||||||
|
|
||||||
rdpy-vncclient is a simple VNC Qt4 client .
|
rdpy-vncclient is a simple VNC Qt4 client .
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rdpy/bin/rdpy-vncclient [-p password] XXX.XXX.XXX.XXX[:5900]
|
$ rdpy-vncclient.py [-p password] XXX.XXX.XXX.XXX[:5900]
|
||||||
```
|
```
|
||||||
|
|
||||||
### rdpy-rdpscreenshot
|
### rdpy-rdpscreenshot
|
||||||
|
|
||||||
rdpy-rdpscreenshot save login screen in file.
|
rdpy-rdpscreenshot saves login screen in file.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rdpy/bin/rdpy-rdpscreenshot [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX.XXX[:3389]
|
$ rdpy-rdpscreenshot.py [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX.XXX[:3389]
|
||||||
```
|
```
|
||||||
|
|
||||||
### rdpy-vncscreenshot
|
### rdpy-vncscreenshot
|
||||||
|
|
||||||
rdpy-vncscreenshot save first screen update in file.
|
rdpy-vncscreenshot saves the first screen update in file.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rdpy/bin/rdpy-vncscreenshot [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900]
|
$ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900]
|
||||||
```
|
```
|
||||||
|
|
||||||
### rdpy-rdpproxy
|
### rdpy-rdpmitm
|
||||||
|
|
||||||
rdpy-rdpproxy is a RDP proxy. It is used to manage and control access to the RDP servers as well as watch live sessions through any RDP client. It can be compared to a HTTP reverse proxy with added spy features.
|
rdpy-rdpmitm is a RDP proxy allows you to do a Man In The Middle attack on RDP protocol.
|
||||||
|
Record Session Scenario into rss file which can be replayed by rdpy-rssplayer.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rdpy/bin/rdpy-rdpproxy -f credentials_file_path -k private_key_file_path -c certificate_file_path [-i admin_ip[:admin_port]] listen_port
|
$ rdpy-rdpmitm.py -o output_dir [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] [-r (for XP or server 2003 client)] target_host[:target_port]
|
||||||
```
|
```
|
||||||
|
|
||||||
The credentials file is JSON file that must conform with the following format:
|
Output directory is 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 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_1 ... rss_file_path_N
|
||||||
"domain1":
|
|
||||||
{
|
|
||||||
"username1":
|
|
||||||
[
|
|
||||||
{"ip":"machine1", "port":3389"},
|
|
||||||
{"ip":"machine2", "port":3389"}
|
|
||||||
],
|
|
||||||
"username2":
|
|
||||||
[
|
|
||||||
{"ip":"machine1", "port":3389"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
In this exemple domain1\username1 can access to machine1 and machine2 and domain1\username2 can only access to machine1.
|
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer. 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.
|
||||||
|
|
||||||
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer but RDPY is limited to SSL. The CredSSP security layer is planned for an upcoming release. The basic RDP security layer is not supported (windows wp sp1&2).
|
### rdpy-rssplayer
|
||||||
|
|
||||||
The IP and port admin are used in order to spy active sessions thanks to a RDP client (rdpy-rdpclient, remina, mstsc). Common values are 127.0.0.1:3389 to protect from connections by unauthorized user.
|
rdpy-rssplayer is use to replay Record Session Scenario (rss) files generates by either rdpy-rdpmitm or rdpy-rdpclient binaries.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rdpy-rssplayer.py rss_file_path
|
||||||
|
```
|
||||||
|
|
||||||
## RDPY Qt Widget
|
## 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
|
## 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.
|
||||||
|
|
||||||
The RDP client code looks like this:
|
### Simple RDP Client
|
||||||
|
|
||||||
```
|
```python
|
||||||
from rdpy.protocol.rdp import rdp
|
from rdpy.protocol.rdp import rdp
|
||||||
|
|
||||||
class MyRDPFactory(rdp.ClientFactory):
|
class MyRDPFactory(rdp.ClientFactory):
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
def clientConnectionLost(self, connector, reason):
|
||||||
reactor.stop()
|
reactor.stop()
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
def clientConnectionFailed(self, connector, reason):
|
||||||
reactor.stop()
|
reactor.stop()
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
|
||||||
class MyObserver(rdp.RDPClientObserver)
|
|
||||||
|
|
||||||
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
|
||||||
#here code handle bitmap
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onReady(self):
|
|
||||||
#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 onClose(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return MyObserver(controller)
|
def buildObserver(self, controller, addr):
|
||||||
|
|
||||||
|
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: Windows session is ready
|
||||||
|
"""
|
||||||
|
|
||||||
|
def onClose(self):
|
||||||
|
"""
|
||||||
|
@summary: Call when stack is close
|
||||||
|
"""
|
||||||
|
|
||||||
|
return MyObserver(controller)
|
||||||
|
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory())
|
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389, MyRDPFactory())
|
||||||
reactor.run()
|
reactor.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
The VNC client code looks like this:
|
### Simple RDP Server
|
||||||
```
|
```python
|
||||||
from rdpy.protocol.rfb import rdp
|
from rdpy.protocol.rdp import rdp
|
||||||
|
|
||||||
class MyRDPFactory(rfb.ClientFactory):
|
class MyRDPFactory(rdp.ServerFactory):
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
|
||||||
reactor.stop()
|
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
|
||||||
reactor.stop()
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
class MyObserver(rfb.RFBClientObserver)
|
|
||||||
|
|
||||||
def onReady(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
|
class MyObserver(rdp.RDPServerObserver):
|
||||||
#here code handle bitmap
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onClose(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return MyObserver(controller)
|
def onReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Call when server is ready
|
||||||
|
to send and receive messages
|
||||||
|
"""
|
||||||
|
|
||||||
|
def onKeyEventScancode(self, code, isPressed):
|
||||||
|
"""
|
||||||
|
@summary: Event call when a keyboard event is catch in scan code format
|
||||||
|
@param code: scan code of key
|
||||||
|
@param isPressed: True if key is down
|
||||||
|
@see: rdp.RDPServerObserver.onKeyEventScancode
|
||||||
|
"""
|
||||||
|
|
||||||
|
def onKeyEventUnicode(self, code, isPressed):
|
||||||
|
"""
|
||||||
|
@summary: Event call when a keyboard event is catch in unicode format
|
||||||
|
@param code: unicode of key
|
||||||
|
@param isPressed: True if key is down
|
||||||
|
@see: rdp.RDPServerObserver.onKeyEventUnicode
|
||||||
|
"""
|
||||||
|
|
||||||
|
def onPointerEvent(self, x, y, button, isPressed):
|
||||||
|
"""
|
||||||
|
@summary: Event call on mouse event
|
||||||
|
@param x: x position
|
||||||
|
@param y: y position
|
||||||
|
@param button: 1, 2 or 3 button
|
||||||
|
@param isPressed: True if mouse button is pressed
|
||||||
|
@see: rdp.RDPServerObserver.onPointerEvent
|
||||||
|
"""
|
||||||
|
|
||||||
|
def onClose(self):
|
||||||
|
"""
|
||||||
|
@summary: Call when human client close connection
|
||||||
|
@see: rdp.RDPServerObserver.onClose
|
||||||
|
"""
|
||||||
|
|
||||||
|
return MyObserver(controller)
|
||||||
|
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory())
|
reactor.listenTCP(3389, MyRDPFactory())
|
||||||
|
reactor.run()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Simple VNC Client
|
||||||
|
```python
|
||||||
|
from rdpy.protocol.rfb import rfb
|
||||||
|
|
||||||
|
class MyRFBFactory(rfb.ClientFactory):
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
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.run()
|
reactor.run()
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
#
|
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
|
||||||
#
|
|
||||||
# This file is part of rdpy.
|
|
||||||
#
|
|
||||||
# rdpy is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
example of use rdpy as rdp client
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys, os, getopt
|
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
|
||||||
from rdpy.ui.qt4 import RDPClientQt
|
|
||||||
from rdpy.protocol.rdp import rdp
|
|
||||||
|
|
||||||
import rdpy.base.log as log
|
|
||||||
log._LOG_LEVEL = log.Level.INFO
|
|
||||||
|
|
||||||
class RDPClientQtFactory(rdp.ClientFactory):
|
|
||||||
"""
|
|
||||||
@summary: Factory create a RDP GUI client
|
|
||||||
"""
|
|
||||||
def __init__(self, width, height, username, password, domain):
|
|
||||||
"""
|
|
||||||
@param width: width of client
|
|
||||||
@param heigth: heigth of client
|
|
||||||
@param username: username present to the server
|
|
||||||
@param password: password present to the server
|
|
||||||
@param domain: microsoft domain
|
|
||||||
"""
|
|
||||||
self._width = width
|
|
||||||
self._height = height
|
|
||||||
self._username = username
|
|
||||||
self._passwod = password
|
|
||||||
self._domain = domain
|
|
||||||
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
|
|
||||||
client = RDPClientQt(controller, self._width, self._height)
|
|
||||||
#create qt widget
|
|
||||||
self._w = client.getWidget()
|
|
||||||
self._w.setWindowTitle('rdpy-rdpclient')
|
|
||||||
self._w.show()
|
|
||||||
|
|
||||||
controller.setUsername(self._username)
|
|
||||||
controller.setPassword(self._passwod)
|
|
||||||
controller.setDomain(self._domain)
|
|
||||||
controller.setPerformanceSession()
|
|
||||||
|
|
||||||
return client
|
|
||||||
|
|
||||||
def startedConnecting(self, connector):
|
|
||||||
pass
|
|
||||||
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
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 help():
|
|
||||||
print "Usage: rdpy-rdpclient [options] ip[:port]"
|
|
||||||
print "\t-u: user name"
|
|
||||||
print "\t-p: password"
|
|
||||||
print "\t-d: domain"
|
|
||||||
print "\t-w: width of screen default value is 1024"
|
|
||||||
print "\t-l: height of screen default value is 800"
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
#default script argument
|
|
||||||
username = ""
|
|
||||||
password = ""
|
|
||||||
domain = ""
|
|
||||||
width = 1024
|
|
||||||
height = 800
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "hu:p:d:w:l:")
|
|
||||||
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)
|
|
||||||
|
|
||||||
if ':' in args[0]:
|
|
||||||
ip, port = args[0].split(':')
|
|
||||||
else:
|
|
||||||
ip, port = args[0], "3389"
|
|
||||||
|
|
||||||
#create application
|
|
||||||
app = QtGui.QApplication(sys.argv)
|
|
||||||
|
|
||||||
#add qt4 reactor
|
|
||||||
import qt4reactor
|
|
||||||
qt4reactor.install()
|
|
||||||
|
|
||||||
from twisted.internet import reactor
|
|
||||||
reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain))
|
|
||||||
reactor.runReturn()
|
|
||||||
app.exec_()
|
|
||||||
288
bin/rdpy-rdpclient.py
Executable file
288
bin/rdpy-rdpclient.py
Executable file
@@ -0,0 +1,288 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
"""
|
||||||
|
example of use rdpy as rdp client
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, os, getopt, socket
|
||||||
|
|
||||||
|
from PyQt4 import QtGui, QtCore
|
||||||
|
from rdpy.ui.qt4 import RDPClientQt
|
||||||
|
from rdpy.protocol.rdp import rdp
|
||||||
|
from rdpy.core.error import RDPSecurityNegoFail
|
||||||
|
from rdpy.core import rss
|
||||||
|
|
||||||
|
import rdpy.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:
|
||||||
|
#compute start nego nla need credentials
|
||||||
|
if username != "" and password != "":
|
||||||
|
self._security = rdp.SecurityLevel.RDP_LEVEL_NLA
|
||||||
|
else:
|
||||||
|
self._security = rdp.SecurityLevel.RDP_LEVEL_SSL
|
||||||
|
else:
|
||||||
|
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.SecurityLevel.RDP_LEVEL_RDP
|
||||||
|
self._client._widget.hide()
|
||||||
|
connector.connect()
|
||||||
|
return
|
||||||
|
|
||||||
|
log.info("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
|
||||||
|
"""
|
||||||
|
log.info("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_()
|
||||||
184
bin/rdpy-rdphoneypot.py
Executable file
184
bin/rdpy-rdphoneypot.py
Executable file
@@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
RDP Honey pot use Rss scenario file to simulate RDP server
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, os, getopt, time
|
||||||
|
|
||||||
|
from rdpy.core import log, error, rss
|
||||||
|
from rdpy.protocol.rdp import rdp
|
||||||
|
from twisted.internet import reactor
|
||||||
|
|
||||||
|
log._LOG_LEVEL = log.Level.INFO
|
||||||
|
|
||||||
|
class HoneyPotServer(rdp.RDPServerObserver):
|
||||||
|
def __init__(self, controller, rssFileSizeList):
|
||||||
|
"""
|
||||||
|
@param controller: {RDPServerController}
|
||||||
|
@param rssFileSizeList: {Tuple} Tuple(Tuple(width, height), rssFilePath)
|
||||||
|
"""
|
||||||
|
rdp.RDPServerObserver.__init__(self, controller)
|
||||||
|
self._rssFileSizeList = rssFileSizeList
|
||||||
|
self._dx, self._dy = 0, 0
|
||||||
|
self._rssFile = None
|
||||||
|
|
||||||
|
def onReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Event use to inform state of server stack
|
||||||
|
First time this event is called is when human client is connected
|
||||||
|
Second time is after color depth nego, because color depth nego
|
||||||
|
restart a connection sequence
|
||||||
|
@see: rdp.RDPServerObserver.onReady
|
||||||
|
"""
|
||||||
|
if self._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("select file (%s, %s) -> %s"%(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));
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def onClose(self):
|
||||||
|
""" HoneyPot """
|
||||||
|
|
||||||
|
def onKeyEventScancode(self, code, isPressed, isExtended):
|
||||||
|
""" HoneyPot """
|
||||||
|
|
||||||
|
def onKeyEventUnicode(self, code, isPressed):
|
||||||
|
""" HoneyPot """
|
||||||
|
|
||||||
|
def onPointerEvent(self, x, y, button, isPressed):
|
||||||
|
""" HoneyPot """
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.loopScenario(self._rssFile.nextEvent())
|
||||||
|
|
||||||
|
def loopScenario(self, nextEvent):
|
||||||
|
"""
|
||||||
|
@summary: main loop event
|
||||||
|
"""
|
||||||
|
if nextEvent.type.value == rss.EventType.UPDATE:
|
||||||
|
self._controller.sendUpdate(nextEvent.event.destLeft.value + self._dx, nextEvent.event.destTop.value + self._dy, nextEvent.event.destRight.value + self._dx, nextEvent.event.destBottom.value + self._dy, nextEvent.event.width.value, nextEvent.event.height.value, nextEvent.event.bpp.value, nextEvent.event.format.value == rss.UpdateFormat.BMP, nextEvent.event.data.value)
|
||||||
|
|
||||||
|
elif nextEvent.type.value == rss.EventType.CLOSE:
|
||||||
|
self._controller.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
elif nextEvent.type.value == rss.EventType.SCREEN:
|
||||||
|
self._controller.setColorDepth(nextEvent.event.colorDepth.value)
|
||||||
|
#compute centering because we cannot resize client
|
||||||
|
clientSize = nextEvent.event.width.value, nextEvent.event.height.value
|
||||||
|
serverSize = self._controller.getScreen()
|
||||||
|
|
||||||
|
self._dx, self._dy = (max(0, serverSize[0] - clientSize[0]) / 2), max(0, (serverSize[1] - clientSize[1]) / 2)
|
||||||
|
#restart connection sequence
|
||||||
|
return
|
||||||
|
|
||||||
|
e = self._rssFile.nextEvent()
|
||||||
|
reactor.callLater(float(e.timestamp.value) / 1000.0, lambda:self.loopScenario(e))
|
||||||
|
|
||||||
|
class HoneyPotServerFactory(rdp.ServerFactory):
|
||||||
|
"""
|
||||||
|
@summary: Factory on listening events
|
||||||
|
"""
|
||||||
|
def __init__(self, rssFileSizeList, privateKeyFilePath, certificateFilePath):
|
||||||
|
"""
|
||||||
|
@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._rssFileSizeList = rssFileSizeList
|
||||||
|
|
||||||
|
def buildObserver(self, controller, addr):
|
||||||
|
"""
|
||||||
|
@param controller: {rdp.RDPServerController}
|
||||||
|
@param addr: destination address
|
||||||
|
@see: rdp.ServerFactory.buildObserver
|
||||||
|
"""
|
||||||
|
log.info("Connection from %s:%s"%(addr.host, addr.port))
|
||||||
|
return HoneyPotServer(controller, 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(1..n)
|
||||||
|
[-l listen_port default 3389]
|
||||||
|
[-k private_key_file_path (mandatory for SSL)]
|
||||||
|
[-c certificate_file_path (mandatory for SSL)]
|
||||||
|
"""
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
listen = "3389"
|
||||||
|
privateKeyFilePath = None
|
||||||
|
certificateFilePath = None
|
||||||
|
rssFileSizeList = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:")
|
||||||
|
except getopt.GetoptError:
|
||||||
|
help()
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt == "-h":
|
||||||
|
help()
|
||||||
|
sys.exit()
|
||||||
|
elif opt == "-l":
|
||||||
|
listen = arg
|
||||||
|
elif opt == "-k":
|
||||||
|
privateKeyFilePath = arg
|
||||||
|
elif opt == "-c":
|
||||||
|
certificateFilePath = arg
|
||||||
|
|
||||||
|
#build size map
|
||||||
|
log.info("Build size map")
|
||||||
|
for arg in args:
|
||||||
|
size = readSize(arg)
|
||||||
|
rssFileSizeList.append((size, arg))
|
||||||
|
log.info("(%s, %s) -> %s"%(size[0], size[1], arg))
|
||||||
|
|
||||||
|
reactor.listenTCP(int(listen), HoneyPotServerFactory(rssFileSizeList, privateKeyFilePath, certificateFilePath))
|
||||||
|
reactor.run()
|
||||||
310
bin/rdpy-rdpmitm.py
Executable file
310
bin/rdpy-rdpmitm.py
Executable file
@@ -0,0 +1,310 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
RDP proxy with Man in the middle capabilities
|
||||||
|
Save RDP events in output RSR file format
|
||||||
|
RSR file format can be read by rdpy-rsrplayer.py
|
||||||
|
----------------------------
|
||||||
|
Client RDP -> | ProxyServer | ProxyClient | -> Server RDP
|
||||||
|
----------------------------
|
||||||
|
| Record Session |
|
||||||
|
-----------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, os, getopt, time
|
||||||
|
|
||||||
|
from rdpy.core import log, error, rss
|
||||||
|
from rdpy.protocol.rdp import rdp
|
||||||
|
from twisted.internet import reactor
|
||||||
|
|
||||||
|
log._LOG_LEVEL = log.Level.INFO
|
||||||
|
|
||||||
|
class ProxyServer(rdp.RDPServerObserver):
|
||||||
|
"""
|
||||||
|
@summary: Server side of proxy
|
||||||
|
"""
|
||||||
|
def __init__(self, controller, target, clientSecurityLevel, rssRecorder):
|
||||||
|
"""
|
||||||
|
@param controller: {RDPServerController}
|
||||||
|
@param target: {tuple(ip, port)}
|
||||||
|
@param rssRecorder: {rss.FileRecorder} use to record session
|
||||||
|
"""
|
||||||
|
rdp.RDPServerObserver.__init__(self, controller)
|
||||||
|
self._target = target
|
||||||
|
self._client = None
|
||||||
|
self._rss = rssRecorder
|
||||||
|
self._clientSecurityLevel = clientSecurityLevel
|
||||||
|
|
||||||
|
def setClient(self, client):
|
||||||
|
"""
|
||||||
|
@summary: Event throw by client when it's ready
|
||||||
|
@param client: {ProxyClient}
|
||||||
|
"""
|
||||||
|
self._client = client
|
||||||
|
|
||||||
|
def onReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Event use to inform state of server stack
|
||||||
|
First time this event is called is when human client is connected
|
||||||
|
Second time is after color depth nego, because color depth nego
|
||||||
|
restart a connection sequence
|
||||||
|
@see: rdp.RDPServerObserver.onReady
|
||||||
|
"""
|
||||||
|
if self._client is None:
|
||||||
|
#try a connection
|
||||||
|
domain, username, password = self._controller.getCredentials()
|
||||||
|
self._rss.credentials(username, password, domain, self._controller.getHostname())
|
||||||
|
|
||||||
|
width, height = self._controller.getScreen()
|
||||||
|
self._rss.screen(width, height, self._controller.getColorDepth())
|
||||||
|
|
||||||
|
reactor.connectTCP(self._target[0], int(self._target[1]), ProxyClientFactory(self, width, height,
|
||||||
|
domain, username, password,self._clientSecurityLevel))
|
||||||
|
|
||||||
|
def onClose(self):
|
||||||
|
"""
|
||||||
|
@summary: Call when human client close connection
|
||||||
|
@see: rdp.RDPServerObserver.onClose
|
||||||
|
"""
|
||||||
|
#end scenario
|
||||||
|
self._rss.close()
|
||||||
|
|
||||||
|
#close network stack
|
||||||
|
if self._client is None:
|
||||||
|
return
|
||||||
|
self._client._controller.close()
|
||||||
|
|
||||||
|
def onKeyEventScancode(self, code, isPressed, isExtended):
|
||||||
|
"""
|
||||||
|
@summary: Event call when a keyboard event is catch in scan code format
|
||||||
|
@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, isExtended)
|
||||||
|
self._rss.keyScancode(code, isPressed)
|
||||||
|
|
||||||
|
def onKeyEventUnicode(self, code, isPressed):
|
||||||
|
"""
|
||||||
|
@summary: Event call when a keyboard event is catch in unicode format
|
||||||
|
@param code: unicode of key
|
||||||
|
@param isPressed: True if key is down
|
||||||
|
@see: rdp.RDPServerObserver.onKeyEventUnicode
|
||||||
|
"""
|
||||||
|
if self._client is None:
|
||||||
|
return
|
||||||
|
self._client._controller.sendKeyEventUnicode(code, isPressed)
|
||||||
|
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 isPressed: {bool} True if mouse button is pressed
|
||||||
|
@see: rdp.RDPServerObserver.onPointerEvent
|
||||||
|
"""
|
||||||
|
if self._client is None:
|
||||||
|
return
|
||||||
|
self._client._controller.sendPointerEvent(x, y, button, isPressed)
|
||||||
|
|
||||||
|
class ProxyServerFactory(rdp.ServerFactory):
|
||||||
|
"""
|
||||||
|
@summary: Factory on listening events
|
||||||
|
"""
|
||||||
|
def __init__(self, target, ouputDir, privateKeyFilePath, certificateFilePath, clientSecurity):
|
||||||
|
"""
|
||||||
|
@param target: {tuple(ip, prt)}
|
||||||
|
@param privateKeyFilePath: {str} file contain server private key (if none -> back to standard RDP security)
|
||||||
|
@param certificateFilePath: {str} file contain server certificate (if none -> back to standard RDP security)
|
||||||
|
@param clientSecurity: {str(ssl|rdp)} security layer use in client connection side
|
||||||
|
"""
|
||||||
|
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
|
||||||
|
self._target = target
|
||||||
|
self._ouputDir = ouputDir
|
||||||
|
self._clientSecurity = clientSecurity
|
||||||
|
#use produce unique file by connection
|
||||||
|
self._uniqueId = 0
|
||||||
|
|
||||||
|
def buildObserver(self, controller, addr):
|
||||||
|
"""
|
||||||
|
@param controller: {rdp.RDPServerController}
|
||||||
|
@param addr: destination address
|
||||||
|
@see: rdp.ServerFactory.buildObserver
|
||||||
|
"""
|
||||||
|
self._uniqueId += 1
|
||||||
|
return ProxyServer(controller, self._target, self._clientSecurity, rss.createRecorder(os.path.join(self._ouputDir, "%s_%s_%s.rss"%(time.strftime('%Y%m%d%H%M%S'), addr.host, self._uniqueId))))
|
||||||
|
|
||||||
|
class ProxyClient(rdp.RDPClientObserver):
|
||||||
|
"""
|
||||||
|
@summary: Client side of proxy
|
||||||
|
"""
|
||||||
|
def __init__(self, controller, server):
|
||||||
|
"""
|
||||||
|
@param controller: {rdp.RDPClientController}
|
||||||
|
@param server: {ProxyServer}
|
||||||
|
"""
|
||||||
|
rdp.RDPClientObserver.__init__(self, controller)
|
||||||
|
self._server = server
|
||||||
|
|
||||||
|
def onReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Event use to signal that RDP stack is ready
|
||||||
|
Inform ProxyServer that i'm connected
|
||||||
|
@see: rdp.RDPClientObserver.onReady
|
||||||
|
"""
|
||||||
|
self._server.setClient(self)
|
||||||
|
#maybe color depth change
|
||||||
|
self._server._controller.setColorDepth(self._controller.getColorDepth())
|
||||||
|
|
||||||
|
def 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
|
||||||
|
self._server._rss.close()
|
||||||
|
self._server._controller.close()
|
||||||
|
|
||||||
|
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||||
|
"""
|
||||||
|
@summary: Event use to inform bitmap update
|
||||||
|
@param destLeft: {int} xmin position
|
||||||
|
@param destTop: {int} ymin position
|
||||||
|
@param destRight: {int} xmax position because RDP can send bitmap with padding
|
||||||
|
@param destBottom: {int} ymax position because RDP can send bitmap with padding
|
||||||
|
@param width: {int} width of bitmap
|
||||||
|
@param height: {int} height of bitmap
|
||||||
|
@param bitsPerPixel: {int} number of bit per pixel
|
||||||
|
@param isCompress: {bool} use RLE compression
|
||||||
|
@param data: {str} bitmap data
|
||||||
|
@see: rdp.RDPClientObserver.onUpdate
|
||||||
|
"""
|
||||||
|
self._server._rss.update(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, rss.UpdateFormat.BMP if isCompress else rss.UpdateFormat.RAW, data)
|
||||||
|
self._server._controller.sendUpdate(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data)
|
||||||
|
|
||||||
|
class ProxyClientFactory(rdp.ClientFactory):
|
||||||
|
"""
|
||||||
|
@summary: Factory for proxy client
|
||||||
|
"""
|
||||||
|
def __init__(self, server, width, height, domain, username, password, security):
|
||||||
|
"""
|
||||||
|
@param server: {ProxyServer}
|
||||||
|
@param width: {int} screen width
|
||||||
|
@param height: {int} screen height
|
||||||
|
@param domain: {str} domain session
|
||||||
|
@param username: {str} username session
|
||||||
|
@param password: {str} password session
|
||||||
|
@param security: {str(ssl|rdp)} security level
|
||||||
|
"""
|
||||||
|
self._server = server
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._domain = domain
|
||||||
|
self._username = username
|
||||||
|
self._password = password
|
||||||
|
self._security = security
|
||||||
|
|
||||||
|
def buildObserver(self, controller, addr):
|
||||||
|
"""
|
||||||
|
@summary: Build observer
|
||||||
|
@param controller: rdp.RDPClientController
|
||||||
|
@param addr: destination address
|
||||||
|
@see: rdp.ClientFactory.buildObserver
|
||||||
|
@return: ProxyClient
|
||||||
|
"""
|
||||||
|
#set screen resolution
|
||||||
|
controller.setScreen(self._width, self._height)
|
||||||
|
#set credential
|
||||||
|
controller.setDomain(self._domain)
|
||||||
|
controller.setUsername(self._username)
|
||||||
|
controller.setPassword(self._password)
|
||||||
|
controller.setSecurityLevel(self._security)
|
||||||
|
controller.setPerformanceSession()
|
||||||
|
return ProxyClient(controller, self._server)
|
||||||
|
|
||||||
|
def help():
|
||||||
|
"""
|
||||||
|
@summary: Print help in console
|
||||||
|
"""
|
||||||
|
print """
|
||||||
|
Usage: rdpy-rdpmitm.py -o output_directory target
|
||||||
|
[-l listen_port default 3389]
|
||||||
|
[-k private_key_file_path (mandatory for SSL)]
|
||||||
|
[-c certificate_file_path (mandatory for SSL)]
|
||||||
|
[-o output directory for recoded files]
|
||||||
|
[-r RDP standard security (XP or server 2003 client or older)]
|
||||||
|
[-n For NLA Client authentication (need to provide credentials)]
|
||||||
|
"""
|
||||||
|
|
||||||
|
def parseIpPort(interface, defaultPort = "3389"):
|
||||||
|
if ':' in interface:
|
||||||
|
return interface.split(':')
|
||||||
|
else:
|
||||||
|
return interface, defaultPort
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
listen = "3389"
|
||||||
|
privateKeyFilePath = None
|
||||||
|
certificateFilePath = None
|
||||||
|
ouputDirectory = None
|
||||||
|
#for anonymous authentication
|
||||||
|
clientSecurity = rdp.SecurityLevel.RDP_LEVEL_SSL
|
||||||
|
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:o:rn")
|
||||||
|
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.SecurityLevel.RDP_LEVEL_RDP
|
||||||
|
elif opt == "-n":
|
||||||
|
clientSecurity = rdp.SecurityLevel.RDP_LEVEL_NLA
|
||||||
|
|
||||||
|
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()
|
||||||
@@ -1,560 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
#
|
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
|
||||||
#
|
|
||||||
# This file is part of rdpy.
|
|
||||||
#
|
|
||||||
# rdpy is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
RDP proxy with spy capabilities
|
|
||||||
---------------------------
|
|
||||||
Client RDP -> | ProxyServer | ProxyClient | -> Server RDP
|
|
||||||
---------------------------
|
|
||||||
| ProxyAdmin |
|
|
||||||
------------
|
|
||||||
^
|
|
||||||
Admin ----------------------|
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys, os, getopt, json
|
|
||||||
|
|
||||||
from rdpy.base import log, error
|
|
||||||
from rdpy.protocol.rdp import rdp
|
|
||||||
from rdpy.ui import view
|
|
||||||
from twisted.internet import reactor
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
|
|
||||||
log._LOG_LEVEL = log.Level.INFO
|
|
||||||
|
|
||||||
class ProxyServer(rdp.RDPServerObserver):
|
|
||||||
"""
|
|
||||||
@summary: Server side of proxy
|
|
||||||
"""
|
|
||||||
def __init__(self, controller, credentialProvider):
|
|
||||||
"""
|
|
||||||
@param controller: RDPServerController
|
|
||||||
@param credentialProvider: CredentialProvider
|
|
||||||
"""
|
|
||||||
rdp.RDPServerObserver.__init__(self, controller)
|
|
||||||
self._credentialProvider = credentialProvider
|
|
||||||
self._client = None
|
|
||||||
self._window = None
|
|
||||||
|
|
||||||
def showSelectView(self, machines):
|
|
||||||
"""
|
|
||||||
@summary: Show select sever view to the client
|
|
||||||
@param machines: [(ip, port)]
|
|
||||||
"""
|
|
||||||
self._machines = machines
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
self._window = view.Window(width, height, QtGui.QColor(8, 24, 66))
|
|
||||||
|
|
||||||
self._window.addView(view.Anchor(width / 2 - 250, 100,
|
|
||||||
view.Label("Please select following server",
|
|
||||||
500, 50, QtGui.QFont('arial', 18, QtGui.QFont.Bold),
|
|
||||||
backgroundColor = QtGui.QColor(8, 24, 66))))
|
|
||||||
|
|
||||||
self._window.addView(view.Anchor(width / 2 - 250, 150,
|
|
||||||
view.List(["%s:%s"%(ip, port) for ip, port in machines],
|
|
||||||
500, 500, self.onSelectMachine,
|
|
||||||
QtGui.QColor(8, 24, 66))), True)
|
|
||||||
|
|
||||||
self._window.update(view.RDPRenderer(self._controller), True)
|
|
||||||
|
|
||||||
def onSelectMachine(self, index):
|
|
||||||
"""
|
|
||||||
@summary: Callback of view.List in Select server view
|
|
||||||
@param: index in list
|
|
||||||
"""
|
|
||||||
ip, port = self._machines[index]
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
domain, username, password = self._controller.getCredentials()
|
|
||||||
reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height, domain, username, password))
|
|
||||||
|
|
||||||
def clientConnected(self, client):
|
|
||||||
"""
|
|
||||||
@summary: Event throw by client when it's ready
|
|
||||||
@param client: ProxyClient
|
|
||||||
"""
|
|
||||||
self._client = client
|
|
||||||
#need to reevaluate color depth
|
|
||||||
self._controller.setColorDepth(self._client._controller.getColorDepth())
|
|
||||||
|
|
||||||
def showMessage(self, message):
|
|
||||||
"""
|
|
||||||
@summary: Print a message to the client
|
|
||||||
@param message: string
|
|
||||||
"""
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
|
|
||||||
popup = view.Window(width, height, QtGui.QColor(8, 24, 66))
|
|
||||||
|
|
||||||
popup.addView(view.Anchor(width / 2 - 250, height / 2 - 25,
|
|
||||||
view.Label(message, 500, 50,
|
|
||||||
QtGui.QFont('arial', 18, QtGui.QFont.Bold),
|
|
||||||
backgroundColor = QtGui.QColor(8, 24, 66))))
|
|
||||||
|
|
||||||
popup.update(view.RDPRenderer(self._controller), True)
|
|
||||||
|
|
||||||
def onReady(self):
|
|
||||||
"""
|
|
||||||
@summary: Event use to inform state of server stack
|
|
||||||
First time this event is called is when human client is connected
|
|
||||||
Second time is after color depth nego, because color depth nego
|
|
||||||
restart a connection sequence
|
|
||||||
Use to connect proxy client or show available server
|
|
||||||
@see: rdp.RDPServerObserver.onReady
|
|
||||||
"""
|
|
||||||
if self._client is None:
|
|
||||||
#try a connection
|
|
||||||
domain, username, password = self._controller.getCredentials()
|
|
||||||
machines = self._credentialProvider.getProxyPass(domain, username)
|
|
||||||
|
|
||||||
if len(machines) == 0:
|
|
||||||
self.showMessage("No servers attach to account %s\\%s"%(domain, username))
|
|
||||||
elif len(machines) == 1:
|
|
||||||
ip, port = machines[0]
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height,
|
|
||||||
domain, username, password))
|
|
||||||
else:
|
|
||||||
self.showSelectView(machines)
|
|
||||||
else:
|
|
||||||
#refresh client
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
self._client._controller.sendRefreshOrder(0, 0, width, height)
|
|
||||||
|
|
||||||
def onClose(self):
|
|
||||||
"""
|
|
||||||
@summary: Call when human client close connection
|
|
||||||
@see: rdp.RDPServerObserver.onClose
|
|
||||||
"""
|
|
||||||
if self._client is None:
|
|
||||||
return
|
|
||||||
#close proxy client
|
|
||||||
self._client._controller.close()
|
|
||||||
|
|
||||||
def onKeyEventScancode(self, code, isPressed):
|
|
||||||
"""
|
|
||||||
@summary: Event call when a keyboard event is catch in scan code format
|
|
||||||
@param code: scan code of key
|
|
||||||
@param isPressed: True if key is down
|
|
||||||
@see: rdp.RDPServerObserver.onKeyEventScancode
|
|
||||||
"""
|
|
||||||
#no client connected
|
|
||||||
if not self._client is None:
|
|
||||||
self._client._controller.sendKeyEventScancode(code, isPressed)
|
|
||||||
elif not self._window is None and isPressed:
|
|
||||||
self._window.keyEvent(code)
|
|
||||||
self._window.update(view.RDPRenderer(self._controller))
|
|
||||||
|
|
||||||
|
|
||||||
def onKeyEventUnicode(self, code, isPressed):
|
|
||||||
"""
|
|
||||||
@summary: Event call when a keyboard event is catch in unicode format
|
|
||||||
@param code: unicode of key
|
|
||||||
@param isPressed: True if key is down
|
|
||||||
@see: rdp.RDPServerObserver.onKeyEventUnicode
|
|
||||||
"""
|
|
||||||
#no client connected domain
|
|
||||||
if self._client is None:
|
|
||||||
return
|
|
||||||
self._client._controller.sendKeyEventUnicode(code, isPressed)
|
|
||||||
|
|
||||||
def onPointerEvent(self, x, y, button, isPressed):
|
|
||||||
"""
|
|
||||||
@summary: Event call on mouse event
|
|
||||||
@param x: x position
|
|
||||||
@param y: y position
|
|
||||||
@param button: 1, 2 or 3 button
|
|
||||||
@param isPressed: True if mouse button is pressed
|
|
||||||
@see: rdp.RDPServerObserver.onPointerEvent
|
|
||||||
"""
|
|
||||||
#no client connected
|
|
||||||
if self._client is None:
|
|
||||||
return
|
|
||||||
self._client._controller.sendPointerEvent(x, y, button, isPressed)
|
|
||||||
|
|
||||||
class ProxyServerFactory(rdp.ServerFactory):
|
|
||||||
"""
|
|
||||||
@summary: Factory on listening events
|
|
||||||
"""
|
|
||||||
def __init__(self, credentialProvider, privateKeyFilePath, certificateFilePath):
|
|
||||||
"""
|
|
||||||
@param credentialProvider: CredentialProvider
|
|
||||||
@param privateKeyFilePath: file contain server private key
|
|
||||||
@param certificateFilePath: file contain server certificate
|
|
||||||
"""
|
|
||||||
rdp.ServerFactory.__init__(self, privateKeyFilePath, certificateFilePath, 16)
|
|
||||||
self._credentialProvider = credentialProvider
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
|
||||||
"""
|
|
||||||
@param controller: rdp.RDPServerController
|
|
||||||
@param addr: destination address
|
|
||||||
@see: rdp.ServerFactory.buildObserver
|
|
||||||
"""
|
|
||||||
return ProxyServer(controller, self._credentialProvider)
|
|
||||||
|
|
||||||
class ProxyClient(rdp.RDPClientObserver):
|
|
||||||
"""
|
|
||||||
@summary: Client side of proxy
|
|
||||||
"""
|
|
||||||
_CONNECTED_ = []
|
|
||||||
def __init__(self, controller, server, name = None):
|
|
||||||
"""
|
|
||||||
@param controller: rdp.RDPClientController
|
|
||||||
@param server: ProxyServer
|
|
||||||
@param name: name of session None if you don't
|
|
||||||
want to spy this session
|
|
||||||
"""
|
|
||||||
rdp.RDPClientObserver.__init__(self, controller)
|
|
||||||
self._server = server
|
|
||||||
self._name = name
|
|
||||||
self._connected = False
|
|
||||||
|
|
||||||
def onReady(self):
|
|
||||||
"""
|
|
||||||
@summary: Event use to signal that RDP stack is ready
|
|
||||||
Inform ProxyServer that i'm connected
|
|
||||||
@see: rdp.RDPClientObserver.onReady
|
|
||||||
"""
|
|
||||||
#prevent multiple on ready event
|
|
||||||
#because each deactive-reactive sequence
|
|
||||||
#launch an onReady message
|
|
||||||
if self._connected:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self._connected = True
|
|
||||||
|
|
||||||
if not self._name is None:
|
|
||||||
ProxyClient._CONNECTED_.append(self)
|
|
||||||
self._server.clientConnected(self)
|
|
||||||
|
|
||||||
def onClose(self):
|
|
||||||
"""
|
|
||||||
@summary: Event inform that stack is close
|
|
||||||
@see: rdp.RDPClientObserver.onClose
|
|
||||||
"""
|
|
||||||
if not self._name is None:
|
|
||||||
ProxyClient._CONNECTED_.remove(self)
|
|
||||||
|
|
||||||
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
|
||||||
"""
|
|
||||||
@summary: Event use to inform bitmap update
|
|
||||||
@param destLeft: xmin position
|
|
||||||
@param destTop: ymin position
|
|
||||||
@param destRight: xmax position because RDP can send bitmap with padding
|
|
||||||
@param destBottom: ymax position because RDP can send bitmap with padding
|
|
||||||
@param width: width of bitmap
|
|
||||||
@param height: height of bitmap
|
|
||||||
@param bitsPerPixel: number of bit per pixel
|
|
||||||
@param isCompress: use RLE compression
|
|
||||||
@param data: bitmap data
|
|
||||||
@see: rdp.RDPClientObserver.onUpdate
|
|
||||||
"""
|
|
||||||
self._server._controller.sendUpdate(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data)
|
|
||||||
|
|
||||||
class ProxyClientFactory(rdp.ClientFactory):
|
|
||||||
"""
|
|
||||||
@summary: Factory for proxy client
|
|
||||||
"""
|
|
||||||
def __init__(self, server, width, height, domain, username, password):
|
|
||||||
"""
|
|
||||||
@param server: ProxyServer
|
|
||||||
@param width: screen width
|
|
||||||
@param height: screen height
|
|
||||||
@param domain: domain session
|
|
||||||
@param username: username session
|
|
||||||
@param password: password session
|
|
||||||
"""
|
|
||||||
self._server = server
|
|
||||||
self._width = width
|
|
||||||
self._height = height
|
|
||||||
self._domain = domain
|
|
||||||
self._username = username
|
|
||||||
self._password = password
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
|
||||||
"""
|
|
||||||
@summary: Build observer
|
|
||||||
@param controller: rdp.RDPClientController
|
|
||||||
@param addr: destination address
|
|
||||||
@see: rdp.ClientFactory.buildObserver
|
|
||||||
@return: ProxyClient
|
|
||||||
"""
|
|
||||||
#set screen resolution
|
|
||||||
controller.setScreen(self._width, self._height)
|
|
||||||
#set credential
|
|
||||||
controller.setDomain(self._domain)
|
|
||||||
controller.setUsername(self._username)
|
|
||||||
controller.setPassword(self._password)
|
|
||||||
return ProxyClient(controller, self._server, "%s\\%s on %s"%(self._domain, self._username, addr))
|
|
||||||
|
|
||||||
def startedConnecting(self, connector):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ProxyAdmin(rdp.RDPServerObserver):
|
|
||||||
"""
|
|
||||||
@summary: Use to manage admin session
|
|
||||||
Add GUI to select which session to see
|
|
||||||
Just escape key is authorized during spy session
|
|
||||||
To switch from spy state to admin state
|
|
||||||
"""
|
|
||||||
class State(object):
|
|
||||||
GUI = 0 #->list of active session
|
|
||||||
SPY = 1 #->watch active session
|
|
||||||
|
|
||||||
def __init__(self, controller):
|
|
||||||
"""
|
|
||||||
@param server: rdp.RDPServerController
|
|
||||||
"""
|
|
||||||
rdp.RDPServerObserver.__init__(self, controller)
|
|
||||||
self._spy = None
|
|
||||||
self._state = ProxyAdmin.State.GUI
|
|
||||||
|
|
||||||
def initView(self):
|
|
||||||
"""
|
|
||||||
@summary: Initialize Admin GUI view
|
|
||||||
"""
|
|
||||||
self._sessions = list(ProxyClient._CONNECTED_) #copy at t time
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
self._window = view.Window(width, height, QtGui.QColor(8, 24, 66))
|
|
||||||
|
|
||||||
self._window.addView(view.Anchor(width / 2 - 250, 100,
|
|
||||||
view.Label("Please select following session",
|
|
||||||
500, 50, QtGui.QFont('arial', 18, QtGui.QFont.Bold),
|
|
||||||
backgroundColor = QtGui.QColor(8, 24, 66))))
|
|
||||||
|
|
||||||
self._window.addView(view.Anchor(width / 2 - 250, 150,
|
|
||||||
view.List([p._name for p in self._sessions],
|
|
||||||
500, 500, self.onSelect,
|
|
||||||
QtGui.QColor(8, 24, 66))), True)
|
|
||||||
|
|
||||||
def clientConnected(self, client):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onReady(self):
|
|
||||||
"""
|
|
||||||
@summary: Stack is ready and connected
|
|
||||||
May be called after an setColorDepth too
|
|
||||||
@see: rdp.RDPServerObserver.onReady
|
|
||||||
"""
|
|
||||||
if self._state == ProxyAdmin.State.GUI:
|
|
||||||
self.initView()
|
|
||||||
self._window.update(view.RDPRenderer(self._controller), True)
|
|
||||||
elif self._state == ProxyAdmin.State.SPY:
|
|
||||||
#refresh client
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
self._spy._controller.sendRefreshOrder(0, 0, width, height)
|
|
||||||
|
|
||||||
def onClose(self):
|
|
||||||
"""
|
|
||||||
@summary: Stack is close
|
|
||||||
@see: rdp.RDPServerObserver.onClose
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onKeyEventScancode(self, code, isPressed):
|
|
||||||
"""
|
|
||||||
@summary: Event call when a keyboard event
|
|
||||||
is catch in scan code format
|
|
||||||
@param code: scan code of key
|
|
||||||
@param isPressed: True if key is down
|
|
||||||
@see: rdp.RDPServerObserver.onKeyEventScancode
|
|
||||||
"""
|
|
||||||
if self._state == ProxyAdmin.State.GUI:
|
|
||||||
if not isPressed:
|
|
||||||
return
|
|
||||||
self._window.keyEvent(code)
|
|
||||||
self._window.update(view.RDPRenderer(self._controller))
|
|
||||||
elif code == 1:
|
|
||||||
#escape button refresh GUI
|
|
||||||
self._state = ProxyAdmin.State.GUI
|
|
||||||
self._spy._controller.removeClientObserver(self._spy)
|
|
||||||
self.onReady()
|
|
||||||
|
|
||||||
def onKeyEventUnicode(self, code, isPressed):
|
|
||||||
"""
|
|
||||||
@summary: Event call when a keyboard event is catch in unicode format
|
|
||||||
Admin GUI add filter for this event
|
|
||||||
@param code: unicode of key
|
|
||||||
@param isPressed: True if key is down
|
|
||||||
@see: rdp.RDPServerObserver.onKeyEventUnicode
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onPointerEvent(self, x, y, button, isPressed):
|
|
||||||
"""
|
|
||||||
@summary: Event call on mouse event
|
|
||||||
Admin GUI add filter for this event
|
|
||||||
@param x: x position
|
|
||||||
@param y: y position
|
|
||||||
@param button: 1, 2 or 3 button
|
|
||||||
@param isPressed: True if mouse button is pressed
|
|
||||||
@see: rdp.RDPServerObserver.onPointerEvent
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onSelect(self, index):
|
|
||||||
"""
|
|
||||||
@summary: Callback of list view of active session
|
|
||||||
Connect to select session
|
|
||||||
@param index: index in sessions array
|
|
||||||
"""
|
|
||||||
self._state = ProxyAdmin.State.SPY
|
|
||||||
self._spy = ProxyClient(self._sessions[index]._controller, self)
|
|
||||||
self._controller.setColorDepth(self._spy._controller.getColorDepth())
|
|
||||||
|
|
||||||
class ProxyAdminFactory(rdp.ServerFactory):
|
|
||||||
"""
|
|
||||||
@summary: Factory for admin session
|
|
||||||
"""
|
|
||||||
def __init__(self, privateKeyFilePath, certificateFilePath):
|
|
||||||
"""
|
|
||||||
@param privateKeyFilePath: private key for admin session
|
|
||||||
@param certificateFilePath: certificate for admin session
|
|
||||||
"""
|
|
||||||
rdp.ServerFactory.__init__(self, privateKeyFilePath, certificateFilePath, 16)
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
|
||||||
"""
|
|
||||||
@summary: Build ProxyAdmin
|
|
||||||
@param controller: rdp.RDPServerController
|
|
||||||
@param addr: destination address
|
|
||||||
@return: ProxyAdmin
|
|
||||||
@see: rdp.ServerFactory.buildObserver
|
|
||||||
"""
|
|
||||||
return ProxyAdmin(controller)
|
|
||||||
|
|
||||||
class CredentialProvider(object):
|
|
||||||
"""
|
|
||||||
@summary: Credential provider for proxy
|
|
||||||
"""
|
|
||||||
def __init__(self, config):
|
|
||||||
"""
|
|
||||||
@param config: rdp proxy config
|
|
||||||
"""
|
|
||||||
self._config = config
|
|
||||||
|
|
||||||
def getAccount(self, domain, username):
|
|
||||||
"""
|
|
||||||
@summary: Find account that match domain::username in config file
|
|
||||||
@param domain: Windows domain
|
|
||||||
@param username: username for session
|
|
||||||
@return: [(unicode(ip), port] or None if not found
|
|
||||||
"""
|
|
||||||
if not self._config.has_key(domain) or not self._config[domain].has_key(username):
|
|
||||||
return None
|
|
||||||
return self._config[domain][username]
|
|
||||||
|
|
||||||
def getProxyPass(self, domain, username):
|
|
||||||
"""
|
|
||||||
@summary: Find list of server available for thi account
|
|
||||||
@param domain: domain to check
|
|
||||||
@param username: username in domain
|
|
||||||
@return: [(ip, port)]
|
|
||||||
"""
|
|
||||||
account = self.getAccount(domain, username)
|
|
||||||
if account is None:
|
|
||||||
return []
|
|
||||||
return [(str(machine["ip"]), machine["port"]) for machine in account]
|
|
||||||
|
|
||||||
def help():
|
|
||||||
"""
|
|
||||||
@summary: Print help in console
|
|
||||||
"""
|
|
||||||
print "Usage: rdpy-rdpproxy -f credential_file_path -k private_key_file_path -c certificate_file_path [-i admin_ip[:admin_port]] listen_port"
|
|
||||||
|
|
||||||
def loadConfig(configFilePath):
|
|
||||||
"""
|
|
||||||
@summary: Load and check config file
|
|
||||||
@param configFilePath: config file path
|
|
||||||
"""
|
|
||||||
if not os.path.isfile(configFilePath):
|
|
||||||
log.error("File %s doesn't exist"%configFilePath)
|
|
||||||
return None
|
|
||||||
|
|
||||||
f = open(configFilePath, 'r')
|
|
||||||
config = json.load(f)
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
return config
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
configFilePath = None
|
|
||||||
privateKeyFilePath = None
|
|
||||||
certificateFilePath = None
|
|
||||||
adminInterface = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "hf:k:c:i:")
|
|
||||||
except getopt.GetoptError:
|
|
||||||
help()
|
|
||||||
for opt, arg in opts:
|
|
||||||
if opt == "-h":
|
|
||||||
help()
|
|
||||||
sys.exit()
|
|
||||||
elif opt == "-f":
|
|
||||||
configFilePath = arg
|
|
||||||
elif opt == "-k":
|
|
||||||
privateKeyFilePath = arg
|
|
||||||
elif opt == "-c":
|
|
||||||
certificateFilePath = arg
|
|
||||||
elif opt == "-i":
|
|
||||||
adminInterface = arg
|
|
||||||
|
|
||||||
if configFilePath is None:
|
|
||||||
print "Config file is mandatory"
|
|
||||||
help()
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if certificateFilePath is None:
|
|
||||||
print "Certificate file is mandatory"
|
|
||||||
help()
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if privateKeyFilePath is None:
|
|
||||||
print "Private key file is mandatory"
|
|
||||||
help()
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
#load config file
|
|
||||||
config = loadConfig(configFilePath)
|
|
||||||
if config is None:
|
|
||||||
log.error("Bad configuration file")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
#use to init font
|
|
||||||
app = QtGui.QApplication(sys.argv)
|
|
||||||
|
|
||||||
reactor.listenTCP(int(args[0]), ProxyServerFactory(CredentialProvider(config), privateKeyFilePath, certificateFilePath))
|
|
||||||
|
|
||||||
if not adminInterface is None:
|
|
||||||
if ':' in adminInterface:
|
|
||||||
adminInterface, adminPort = adminInterface.split(':')
|
|
||||||
else:
|
|
||||||
adminInterface, adminPort = adminInterface, "3390"
|
|
||||||
log.info("Admin listen on %s:%s"%(adminInterface, adminPort))
|
|
||||||
reactor.listenTCP(int(adminPort), ProxyAdminFactory(privateKeyFilePath, certificateFilePath), interface = adminInterface)
|
|
||||||
reactor.run()
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -28,7 +28,8 @@ import sys, os, getopt
|
|||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
from rdpy.protocol.rdp import rdp
|
from rdpy.protocol.rdp import rdp
|
||||||
from rdpy.ui.qt4 import RDPBitmapToQtImage
|
from rdpy.ui.qt4 import RDPBitmapToQtImage
|
||||||
import rdpy.base.log as log
|
import rdpy.core.log as log
|
||||||
|
from rdpy.core.error import RDPSecurityNegoFail
|
||||||
from twisted.internet import task
|
from twisted.internet import task
|
||||||
|
|
||||||
#set log level
|
#set log level
|
||||||
@@ -38,17 +39,25 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
@summary: Factory for screenshot exemple
|
@summary: Factory for screenshot exemple
|
||||||
"""
|
"""
|
||||||
def __init__(self, width, height, path, timeout):
|
__INSTANCE__ = 0
|
||||||
|
__STATE__ = []
|
||||||
|
def __init__(self, reactor, app, width, height, path, timeout):
|
||||||
"""
|
"""
|
||||||
@param width: width of screen
|
@param reactor: twisted reactor
|
||||||
@param height: height of screen
|
@param width: {integer} width of screen
|
||||||
@param path: path of output screenshot
|
@param height: {integer} height of screen
|
||||||
@param timeout: close connection after timeout s without any updating
|
@param path: {str} path of output screenshot
|
||||||
|
@param timeout: {float} close connection after timeout s without any updating
|
||||||
"""
|
"""
|
||||||
|
RDPScreenShotFactory.__INSTANCE__ += 1
|
||||||
|
self._reactor = reactor
|
||||||
|
self._app = app
|
||||||
self._width = width
|
self._width = width
|
||||||
self._height = height
|
self._height = height
|
||||||
self._path = path
|
self._path = path
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
|
#NLA server can't be screenshooting
|
||||||
|
self._security = rdp.SecurityLevel.RDP_LEVEL_SSL
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
def clientConnectionLost(self, connector, reason):
|
||||||
"""
|
"""
|
||||||
@@ -56,9 +65,18 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
|
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
|
||||||
@param reason: str use to advertise reason of lost connection
|
@param reason: str use to advertise reason of lost connection
|
||||||
"""
|
"""
|
||||||
|
if reason.type == RDPSecurityNegoFail and self._security != "rdp":
|
||||||
|
log.info("due to RDPSecurityNegoFail try standard security layer")
|
||||||
|
self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
|
||||||
|
connector.connect()
|
||||||
|
return
|
||||||
|
|
||||||
log.info("connection lost : %s"%reason)
|
log.info("connection lost : %s"%reason)
|
||||||
reactor.stop()
|
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
|
||||||
app.exit()
|
RDPScreenShotFactory.__INSTANCE__ -= 1
|
||||||
|
if(RDPScreenShotFactory.__INSTANCE__ == 0):
|
||||||
|
self._reactor.stop()
|
||||||
|
self._app.exit()
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
def clientConnectionFailed(self, connector, reason):
|
||||||
"""
|
"""
|
||||||
@@ -66,9 +84,12 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
|
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
|
||||||
@param reason: str use to advertise reason of lost connection
|
@param reason: str use to advertise reason of lost connection
|
||||||
"""
|
"""
|
||||||
log.info("connection failes : %s"%reason)
|
log.info("connection failed : %s"%reason)
|
||||||
reactor.stop()
|
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
|
||||||
app.exit()
|
RDPScreenShotFactory.__INSTANCE__ -= 1
|
||||||
|
if(RDPScreenShotFactory.__INSTANCE__ == 0):
|
||||||
|
self._reactor.stop()
|
||||||
|
self._app.exit()
|
||||||
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
@@ -81,31 +102,33 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
@summary: observer that connect, cache every image received and save at deconnection
|
@summary: observer that connect, cache every image received and save at deconnection
|
||||||
"""
|
"""
|
||||||
def __init__(self, controller, width, height, path, timeout):
|
def __init__(self, controller, width, height, path, timeout, reactor):
|
||||||
"""
|
"""
|
||||||
@param controller: RDPClientController
|
@param controller: {RDPClientController}
|
||||||
@param width: width of screen
|
@param width: {integer} width of screen
|
||||||
@param height: height of screen
|
@param height: {integer} height of screen
|
||||||
@param path: path of output screenshot
|
@param path: {str} path of output screenshot
|
||||||
@param timeout: close connection after timeout s without any updating
|
@param timeout: {float} close connection after timeout s without any updating
|
||||||
|
@param reactor: twisted reactor
|
||||||
"""
|
"""
|
||||||
rdp.RDPClientObserver.__init__(self, controller)
|
rdp.RDPClientObserver.__init__(self, controller)
|
||||||
controller.setScreen(width, height);
|
|
||||||
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
|
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
|
||||||
self._path = path
|
self._path = path
|
||||||
self._hasUpdated = True
|
self._timeout = timeout
|
||||||
self._brandWidthTask = task.LoopingCall(self.checkUpdate)
|
self._startTimeout = False
|
||||||
self._brandWidthTask.start(timeout) # call every second
|
self._reactor = reactor
|
||||||
|
|
||||||
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||||
"""
|
"""
|
||||||
@summary: callback use when bitmap is received
|
@summary: callback use when bitmap is received
|
||||||
"""
|
"""
|
||||||
self._hasUpdated = True
|
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data);
|
||||||
image = RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data);
|
|
||||||
with QtGui.QPainter(self._buffer) as qp:
|
with QtGui.QPainter(self._buffer) as qp:
|
||||||
#draw image
|
#draw image
|
||||||
qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1)
|
qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1)
|
||||||
|
if not self._startTimeout:
|
||||||
|
self._startTimeout = False
|
||||||
|
self._reactor.callLater(self._timeout, self.checkUpdate)
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@@ -113,6 +136,13 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
log.info("connected %s"%addr)
|
log.info("connected %s"%addr)
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Windows session is ready
|
||||||
|
@see: rdp.RDPClientObserver.onSessionReady
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: callback use when RDP stack is closed
|
@summary: callback use when RDP stack is closed
|
||||||
@@ -121,13 +151,41 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
self._buffer.save(self._path)
|
self._buffer.save(self._path)
|
||||||
|
|
||||||
def checkUpdate(self):
|
def checkUpdate(self):
|
||||||
if not self._hasUpdated:
|
self._controller.close();
|
||||||
log.info("close connection on timeout without updating orders")
|
|
||||||
self._controller.close();
|
controller.setScreen(width, height);
|
||||||
return
|
controller.setSecurityLevel(self._security)
|
||||||
self._hasUpdated = False
|
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout, self._reactor)
|
||||||
|
|
||||||
|
def main(width, height, path, timeout, hosts):
|
||||||
|
"""
|
||||||
|
@summary: main algorithm
|
||||||
|
@param height: {integer} height of screenshot
|
||||||
|
@param width: {integer} width of screenshot
|
||||||
|
@param timeout: {float} in sec
|
||||||
|
@param hosts: {list(str(ip[:port]))}
|
||||||
|
@return: {list(tuple(ip, port, Failure instance)} list of connection state
|
||||||
|
"""
|
||||||
|
#create application
|
||||||
|
app = QtGui.QApplication(sys.argv)
|
||||||
|
|
||||||
|
#add qt4 reactor
|
||||||
|
import qt4reactor
|
||||||
|
qt4reactor.install()
|
||||||
|
|
||||||
|
from twisted.internet import reactor
|
||||||
|
|
||||||
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout)
|
for host in hosts:
|
||||||
|
if ':' in host:
|
||||||
|
ip, port = host.split(':')
|
||||||
|
else:
|
||||||
|
ip, port = host, "3389"
|
||||||
|
|
||||||
|
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(reactor, app, width, height, path + "%s.jpg"%ip, timeout))
|
||||||
|
|
||||||
|
reactor.runReturn()
|
||||||
|
app.exec_()
|
||||||
|
return RDPScreenShotFactory.__STATE__
|
||||||
|
|
||||||
def help():
|
def help():
|
||||||
print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
|
print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
|
||||||
@@ -140,8 +198,8 @@ if __name__ == '__main__':
|
|||||||
#default script argument
|
#default script argument
|
||||||
width = 1024
|
width = 1024
|
||||||
height = 800
|
height = 800
|
||||||
path = "/tmp/rdpy-rdpscreenshot.jpg"
|
path = "/tmp/"
|
||||||
timeout = 2.0
|
timeout = 5.0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "hw:l:o:t:")
|
opts, args = getopt.getopt(sys.argv[1:], "hw:l:o:t:")
|
||||||
@@ -159,20 +217,5 @@ if __name__ == '__main__':
|
|||||||
path = arg
|
path = arg
|
||||||
elif opt == "-t":
|
elif opt == "-t":
|
||||||
timeout = float(arg)
|
timeout = float(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
|
main(width, height, path, timeout, args)
|
||||||
import qt4reactor
|
|
||||||
qt4reactor.install()
|
|
||||||
|
|
||||||
from twisted.internet import reactor
|
|
||||||
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(width, height, path, timeout))
|
|
||||||
reactor.runReturn()
|
|
||||||
app.exec_()
|
|
||||||
125
bin/rdpy-rssplayer.py
Executable file
125
bin/rdpy-rssplayer.py
Executable file
@@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
"""
|
||||||
|
rss file player
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, os, getopt, socket
|
||||||
|
|
||||||
|
from PyQt4 import QtGui, QtCore
|
||||||
|
|
||||||
|
from rdpy.core import log, rss
|
||||||
|
from rdpy.ui.qt4 import QRemoteDesktop, RDPBitmapToQtImage
|
||||||
|
from rdpy.core.scancode import scancodeToChar
|
||||||
|
log._LOG_LEVEL = log.Level.INFO
|
||||||
|
|
||||||
|
class RssPlayerWidget(QRemoteDesktop):
|
||||||
|
"""
|
||||||
|
@summary: special rss player widget
|
||||||
|
"""
|
||||||
|
def __init__(self, width, height):
|
||||||
|
class RssAdaptor(object):
|
||||||
|
def sendMouseEvent(self, e, isPressed):
|
||||||
|
""" Not Handle """
|
||||||
|
def sendKeyEvent(self, e, isPressed):
|
||||||
|
""" Not Handle """
|
||||||
|
def sendWheelEvent(self, e):
|
||||||
|
""" Not Handle """
|
||||||
|
def closeEvent(self, e):
|
||||||
|
""" Not Handle """
|
||||||
|
QRemoteDesktop.__init__(self, width, height, RssAdaptor())
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
def start(widget, rssFile):
|
||||||
|
loop(widget, rssFile, rssFile.nextEvent())
|
||||||
|
|
||||||
|
def loop(widget, rssFile, nextEvent):
|
||||||
|
"""
|
||||||
|
@summary: timer function
|
||||||
|
@param widget: {QRemoteDesktop}
|
||||||
|
@param rssFile: {rss.FileReader}
|
||||||
|
"""
|
||||||
|
|
||||||
|
if nextEvent.type.value == rss.EventType.UPDATE:
|
||||||
|
image = RDPBitmapToQtImage(nextEvent.event.width.value, nextEvent.event.height.value, nextEvent.event.bpp.value, nextEvent.event.format.value == rss.UpdateFormat.BMP, nextEvent.event.data.value);
|
||||||
|
widget._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._viewer.resize(nextEvent.event.width.value, nextEvent.event.height.value)
|
||||||
|
|
||||||
|
elif nextEvent.type.value == rss.EventType.INFO:
|
||||||
|
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:
|
||||||
|
return
|
||||||
|
|
||||||
|
e = rssFile.nextEvent()
|
||||||
|
QtCore.QTimer.singleShot(e.timestamp.value,lambda:loop(widget, rssFile, e))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "h")
|
||||||
|
except getopt.GetoptError:
|
||||||
|
help()
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt == "-h":
|
||||||
|
help()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
filepath = args[0]
|
||||||
|
#create application
|
||||||
|
app = QtGui.QApplication(sys.argv)
|
||||||
|
|
||||||
|
mainWindow = RssPlayerWindow()
|
||||||
|
mainWindow.show()
|
||||||
|
|
||||||
|
rssFile = rss.createReader(filepath)
|
||||||
|
start(mainWindow, rssFile)
|
||||||
|
sys.exit(app.exec_())
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -27,7 +27,7 @@ from PyQt4 import QtGui
|
|||||||
from rdpy.ui.qt4 import RFBClientQt
|
from rdpy.ui.qt4 import RFBClientQt
|
||||||
from rdpy.protocol.rfb import rfb
|
from rdpy.protocol.rfb import rfb
|
||||||
|
|
||||||
import rdpy.base.log as log
|
import rdpy.core.log as log
|
||||||
log._LOG_LEVEL = log.Level.INFO
|
log._LOG_LEVEL = log.Level.INFO
|
||||||
|
|
||||||
class RFBClientQtFactory(rfb.ClientFactory):
|
class RFBClientQtFactory(rfb.ClientFactory):
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -26,7 +26,7 @@ take screenshot of login page
|
|||||||
import sys, os, getopt
|
import sys, os, getopt
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
from rdpy.protocol.rfb import rfb
|
from rdpy.protocol.rfb import rfb
|
||||||
import rdpy.base.log as log
|
import rdpy.core.log as log
|
||||||
from rdpy.ui.qt4 import qtImageFormatFromRFBPixelFormat
|
from rdpy.ui.qt4 import qtImageFormatFromRFBPixelFormat
|
||||||
from twisted.internet import task
|
from twisted.internet import task
|
||||||
|
|
||||||
@@ -37,11 +37,13 @@ class RFBScreenShotFactory(rfb.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
@summary: Factory for screenshot exemple
|
@summary: Factory for screenshot exemple
|
||||||
"""
|
"""
|
||||||
|
__INSTANCE__ = 0
|
||||||
def __init__(self, password, path):
|
def __init__(self, password, path):
|
||||||
"""
|
"""
|
||||||
@param password: password for VNC authentication
|
@param password: password for VNC authentication
|
||||||
@param path: path of output screenshot
|
@param path: path of output screenshot
|
||||||
"""
|
"""
|
||||||
|
RFBScreenShotFactory.__INSTANCE__ += 1
|
||||||
self._path = path
|
self._path = path
|
||||||
self._password = password
|
self._password = password
|
||||||
|
|
||||||
@@ -52,8 +54,10 @@ class RFBScreenShotFactory(rfb.ClientFactory):
|
|||||||
@param reason: str use to advertise reason of lost connection
|
@param reason: str use to advertise reason of lost connection
|
||||||
"""
|
"""
|
||||||
log.info("connection lost : %s"%reason)
|
log.info("connection lost : %s"%reason)
|
||||||
reactor.stop()
|
RFBScreenShotFactory.__INSTANCE__ -= 1
|
||||||
app.exit()
|
if(RFBScreenShotFactory.__INSTANCE__ == 0):
|
||||||
|
reactor.stop()
|
||||||
|
app.exit()
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
def clientConnectionFailed(self, connector, reason):
|
||||||
"""
|
"""
|
||||||
@@ -62,8 +66,10 @@ class RFBScreenShotFactory(rfb.ClientFactory):
|
|||||||
@param reason: str use to advertise reason of lost connection
|
@param reason: str use to advertise reason of lost connection
|
||||||
"""
|
"""
|
||||||
log.info("connection failed : %s"%reason)
|
log.info("connection failed : %s"%reason)
|
||||||
reactor.stop()
|
RFBScreenShotFactory.__INSTANCE__ -= 1
|
||||||
app.exit()
|
if(RFBScreenShotFactory.__INSTANCE__ == 0):
|
||||||
|
reactor.stop()
|
||||||
|
app.exit()
|
||||||
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
@@ -132,7 +138,7 @@ def help():
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
#default script argument
|
#default script argument
|
||||||
path = "/tmp/rdpy-vncscreenshot.jpg"
|
path = "/tmp/"
|
||||||
password = ""
|
password = ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -148,20 +154,23 @@ if __name__ == '__main__':
|
|||||||
elif opt == "-p":
|
elif opt == "-p":
|
||||||
password = arg
|
password = arg
|
||||||
|
|
||||||
|
|
||||||
if ':' in args[0]:
|
|
||||||
ip, port = args[0].split(':')
|
|
||||||
else:
|
|
||||||
ip, port = args[0], "5900"
|
|
||||||
|
|
||||||
#create application
|
#create application
|
||||||
app = QtGui.QApplication(sys.argv)
|
app = QtGui.QApplication(sys.argv)
|
||||||
|
|
||||||
#add qt4 reactor
|
#add qt4 reactor
|
||||||
import qt4reactor
|
import qt4reactor
|
||||||
qt4reactor.install()
|
qt4reactor.install()
|
||||||
|
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
reactor.connectTCP(ip, int(port), RFBScreenShotFactory(password, path))
|
|
||||||
|
|
||||||
|
for arg in args:
|
||||||
|
if ':' in arg:
|
||||||
|
ip, port = arg.split(':')
|
||||||
|
else:
|
||||||
|
ip, port = arg, "5900"
|
||||||
|
|
||||||
|
reactor.connectTCP(ip, int(port), RFBScreenShotFactory(password, path + "%s.jpg"%ip))
|
||||||
|
|
||||||
|
|
||||||
reactor.runReturn()
|
reactor.runReturn()
|
||||||
app.exec_()
|
app.exec_()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -90,3 +90,13 @@ class ErrorReportedFromPeer(Exception):
|
|||||||
@param message: message show when exception is raised
|
@param message: message show when exception is raised
|
||||||
"""
|
"""
|
||||||
Exception.__init__(self, message)
|
Exception.__init__(self, message)
|
||||||
|
|
||||||
|
class RDPSecurityNegoFail(Exception):
|
||||||
|
"""
|
||||||
|
@summary: Raise when security nego fail
|
||||||
|
"""
|
||||||
|
def __init__(self, message = ""):
|
||||||
|
"""
|
||||||
|
@param message: message show when exception is raised
|
||||||
|
"""
|
||||||
|
Exception.__init__(self, message)
|
||||||
98
rdpy/core/filetimes.py
Normal file
98
rdpy/core/filetimes.py
Normal 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
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -23,7 +23,7 @@ Join RDPY design with twisted design
|
|||||||
RDPY use Layer Protocol design (like twisted)
|
RDPY use Layer Protocol design (like twisted)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.base.error import CallPureVirtualFuntion
|
from rdpy.core.error import CallPureVirtualFuntion
|
||||||
|
|
||||||
class IStreamListener(object):
|
class IStreamListener(object):
|
||||||
"""
|
"""
|
||||||
@@ -99,7 +99,7 @@ class LayerAutomata(Layer, IStreamListener):
|
|||||||
@param callback: a callable object
|
@param callback: a callable object
|
||||||
"""
|
"""
|
||||||
if callback is None:
|
if callback is None:
|
||||||
callback = self.__class__.recv
|
callback = lambda x:self.__class__.recv(self, x)
|
||||||
|
|
||||||
self.recv = callback
|
self.recv = callback
|
||||||
|
|
||||||
@@ -129,14 +129,15 @@ class RawLayerClientFactory(protocol.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildRawLayer", "RawLayerClientFactory"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildRawLayer", "RawLayerClientFactory"))
|
||||||
|
|
||||||
def connectionLost(self, rawlayer):
|
def connectionLost(self, rawlayer, reason):
|
||||||
"""
|
"""
|
||||||
@summary: Override this method to handle connection lost
|
@summary: Override this method to handle connection lost
|
||||||
@param rawlayer: rawLayer that cause connectionLost event
|
@param rawlayer: rawLayer that cause connectionLost event
|
||||||
|
@param reason: twisted reason
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "connectionLost", "RawLayerClientFactory"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "connectionLost", "RawLayerClientFactory"))
|
||||||
|
|
||||||
class RawLayerServerFactory(protocol.ClientFactory):
|
class RawLayerServerFactory(protocol.ServerFactory):
|
||||||
"""
|
"""
|
||||||
@summary: Abstract class for Raw layer server factory
|
@summary: Abstract class for Raw layer server factory
|
||||||
"""
|
"""
|
||||||
@@ -156,10 +157,11 @@ class RawLayerServerFactory(protocol.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
|
||||||
|
|
||||||
def connectionLost(self, rawlayer):
|
def connectionLost(self, rawlayer, reason):
|
||||||
"""
|
"""
|
||||||
@summary: Override this method to handle connection lost
|
@summary: Override this method to handle connection lost
|
||||||
@param rawlayer: rawLayer that cause connectionLost event
|
@param rawlayer: rawLayer that cause connectionLost event
|
||||||
|
@param reason: twisted reason
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
|
||||||
|
|
||||||
@@ -198,7 +200,7 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
|
|||||||
#add in buffer
|
#add in buffer
|
||||||
self._buffer += data
|
self._buffer += data
|
||||||
#while buffer have expected size call local callback
|
#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
|
#expected data is first expected bytes
|
||||||
expectedData = Stream(self._buffer[0:self._expectedLen])
|
expectedData = Stream(self._buffer[0:self._expectedLen])
|
||||||
#rest is for next event of automata
|
#rest is for next event of automata
|
||||||
@@ -218,7 +220,13 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
|
|||||||
@summary: Call from twisted engine when protocol is closed
|
@summary: Call from twisted engine when protocol is closed
|
||||||
@param reason: str represent reason of close connection
|
@param reason: str represent reason of close connection
|
||||||
"""
|
"""
|
||||||
self._factory.connectionLost(self)
|
self._factory.connectionLost(self, reason)
|
||||||
|
|
||||||
|
def getDescriptor(self):
|
||||||
|
"""
|
||||||
|
@return: the twited file descriptor
|
||||||
|
"""
|
||||||
|
return self.transport
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
@@ -226,7 +234,7 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
|
|||||||
Use File descriptor directly to not use TLS close
|
Use File descriptor directly to not use TLS close
|
||||||
Because is bugged
|
Because is bugged
|
||||||
"""
|
"""
|
||||||
FileDescriptor.loseConnection(self.transport)
|
FileDescriptor.loseConnection(self.getDescriptor())
|
||||||
|
|
||||||
def expect(self, expectedLen, callback = None):
|
def expect(self, expectedLen, callback = None):
|
||||||
"""
|
"""
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -30,6 +30,7 @@ class Level(object):
|
|||||||
INFO = 1
|
INFO = 1
|
||||||
WARNING = 2
|
WARNING = 2
|
||||||
ERROR = 3
|
ERROR = 3
|
||||||
|
NONE = 4
|
||||||
|
|
||||||
_LOG_LEVEL = Level.DEBUG
|
_LOG_LEVEL = Level.DEBUG
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ def log(message):
|
|||||||
@summary: Main log function
|
@summary: Main log function
|
||||||
@param message: string to print
|
@param message: string to print
|
||||||
"""
|
"""
|
||||||
print message
|
print "[*] %s"%message
|
||||||
|
|
||||||
def error(message):
|
def error(message):
|
||||||
"""
|
"""
|
||||||
@@ -47,7 +48,7 @@ def error(message):
|
|||||||
"""
|
"""
|
||||||
if _LOG_LEVEL > Level.ERROR:
|
if _LOG_LEVEL > Level.ERROR:
|
||||||
return
|
return
|
||||||
log("ERROR : %s"%message)
|
log("ERROR:\t%s"%message)
|
||||||
|
|
||||||
def warning(message):
|
def warning(message):
|
||||||
"""
|
"""
|
||||||
@@ -56,7 +57,7 @@ def warning(message):
|
|||||||
"""
|
"""
|
||||||
if _LOG_LEVEL > Level.WARNING:
|
if _LOG_LEVEL > Level.WARNING:
|
||||||
return
|
return
|
||||||
log("WARNING : %s"%message)
|
log("WARNING:\t%s"%message)
|
||||||
|
|
||||||
def info(message):
|
def info(message):
|
||||||
"""
|
"""
|
||||||
@@ -65,7 +66,7 @@ def info(message):
|
|||||||
"""
|
"""
|
||||||
if _LOG_LEVEL > Level.INFO:
|
if _LOG_LEVEL > Level.INFO:
|
||||||
return
|
return
|
||||||
log("INFO : %s"%message)
|
log("INFO:\t%s"%message)
|
||||||
|
|
||||||
def debug(message):
|
def debug(message):
|
||||||
"""
|
"""
|
||||||
@@ -74,4 +75,4 @@ def debug(message):
|
|||||||
"""
|
"""
|
||||||
if _LOG_LEVEL > Level.DEBUG:
|
if _LOG_LEVEL > Level.DEBUG:
|
||||||
return
|
return
|
||||||
log("DEBUG : %s"%message)
|
log("DEBUG:\t%s"%message)
|
||||||
300
rdpy/core/rss.py
Normal file
300
rdpy/core/rss.py
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Remote Session Scenario File format
|
||||||
|
Private protocol format to save events
|
||||||
|
"""
|
||||||
|
|
||||||
|
from rdpy.core.type import CompositeType, FactoryType, UInt8, UInt16Le, UInt32Le, String, sizeof, Stream
|
||||||
|
from rdpy.core import log, error
|
||||||
|
import time
|
||||||
|
|
||||||
|
class EventType(object):
|
||||||
|
"""
|
||||||
|
@summary: event type
|
||||||
|
"""
|
||||||
|
UPDATE = 0x0001
|
||||||
|
SCREEN = 0x0002
|
||||||
|
INFO = 0x0003
|
||||||
|
CLOSE = 0x0004
|
||||||
|
KEY_UNICODE = 0x0005
|
||||||
|
KEY_SCANCODE = 0x0006
|
||||||
|
|
||||||
|
class UpdateFormat(object):
|
||||||
|
"""
|
||||||
|
@summary: format of update bitmap
|
||||||
|
"""
|
||||||
|
RAW = 0x01
|
||||||
|
BMP = 0x02
|
||||||
|
|
||||||
|
class Event(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: A recorded event
|
||||||
|
"""
|
||||||
|
def __init__(self, event = None):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.type = UInt16Le(lambda:event.__class__._TYPE_)
|
||||||
|
self.timestamp = UInt32Le()
|
||||||
|
self.length = UInt32Le(lambda:(sizeof(self) - 10))
|
||||||
|
|
||||||
|
def EventFactory():
|
||||||
|
"""
|
||||||
|
@summary: Closure for event factory
|
||||||
|
"""
|
||||||
|
for c in [UpdateEvent, ScreenEvent, InfoEvent, CloseEvent, 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)
|
||||||
|
|
||||||
|
if event is None:
|
||||||
|
event = FactoryType(EventFactory)
|
||||||
|
elif not "_TYPE_" in event.__class__.__dict__:
|
||||||
|
raise error.InvalidExpectedDataException("Try to send an invalid event block")
|
||||||
|
|
||||||
|
self.event = event
|
||||||
|
|
||||||
|
class UpdateEvent(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Update event
|
||||||
|
"""
|
||||||
|
_TYPE_ = EventType.UPDATE
|
||||||
|
def __init__(self, readLen = None):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
self.destLeft = UInt16Le()
|
||||||
|
self.destTop = UInt16Le()
|
||||||
|
self.destRight = UInt16Le()
|
||||||
|
self.destBottom = UInt16Le()
|
||||||
|
self.width = UInt16Le()
|
||||||
|
self.height = UInt16Le()
|
||||||
|
self.bpp = UInt8()
|
||||||
|
self.format = UInt8()
|
||||||
|
self.length = UInt32Le(lambda:sizeof(self.data))
|
||||||
|
self.data = String(readLen = self.length)
|
||||||
|
|
||||||
|
class InfoEvent(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Info event
|
||||||
|
"""
|
||||||
|
_TYPE_ = EventType.INFO
|
||||||
|
def __init__(self, readLen = None):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
self.lenUsername = UInt16Le(lambda:sizeof(self.username))
|
||||||
|
self.username = String(readLen = self.lenUsername)
|
||||||
|
self.lenPassword = UInt16Le(lambda:sizeof(self.password))
|
||||||
|
self.password = String(readLen = self.lenPassword)
|
||||||
|
self.lenDomain = UInt16Le(lambda:sizeof(self.domain))
|
||||||
|
self.domain = String(readLen = self.lenDomain)
|
||||||
|
self.lenHostname = UInt16Le(lambda:sizeof(self.hostname))
|
||||||
|
self.hostname = String(readLen = self.lenHostname)
|
||||||
|
|
||||||
|
class ScreenEvent(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: screen information event
|
||||||
|
"""
|
||||||
|
_TYPE_ = EventType.SCREEN
|
||||||
|
def __init__(self, readLen = None):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
self.width = UInt16Le()
|
||||||
|
self.height = UInt16Le()
|
||||||
|
self.colorDepth = UInt8()
|
||||||
|
|
||||||
|
class CloseEvent(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: end of session event
|
||||||
|
"""
|
||||||
|
_TYPE_ = EventType.CLOSE
|
||||||
|
def __init__(self, readLen = None):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
return int(time.time() * 1000)
|
||||||
|
|
||||||
|
class FileRecorder(object):
|
||||||
|
"""
|
||||||
|
@summary: RSR File recorder
|
||||||
|
"""
|
||||||
|
def __init__(self, f):
|
||||||
|
"""
|
||||||
|
@param f: {file} file pointer use to write
|
||||||
|
"""
|
||||||
|
self._file = f
|
||||||
|
#init timer
|
||||||
|
self._lastEventTimer = timeMs()
|
||||||
|
|
||||||
|
def rec(self, event):
|
||||||
|
"""
|
||||||
|
@summary: save event in file
|
||||||
|
@param event: {UpdateEvent}
|
||||||
|
"""
|
||||||
|
|
||||||
|
now = timeMs()
|
||||||
|
#wrap around event message
|
||||||
|
e = Event(event)
|
||||||
|
#timestamp is time since last event
|
||||||
|
e.timestamp.value = now - self._lastEventTimer
|
||||||
|
self._lastEventTimer = now
|
||||||
|
|
||||||
|
s = Stream()
|
||||||
|
s.writeType(e)
|
||||||
|
|
||||||
|
self._file.write(s.getvalue())
|
||||||
|
|
||||||
|
def update(self, destLeft, destTop, destRight, destBottom, width, height, bpp, upateFormat, data):
|
||||||
|
"""
|
||||||
|
@summary: record update event
|
||||||
|
@param destLeft: {int} xmin position
|
||||||
|
@param destTop: {int} ymin position
|
||||||
|
@param destRight: {int} xmax position because RDP can send bitmap with padding
|
||||||
|
@param destBottom: {int} ymax position because RDP can send bitmap with padding
|
||||||
|
@param width: {int} width of bitmap
|
||||||
|
@param height: {int} height of bitmap
|
||||||
|
@param bpp: {int} number of bit per pixel
|
||||||
|
@param upateFormat: {UpdateFormat} use RLE compression
|
||||||
|
@param data: {str} bitmap data
|
||||||
|
"""
|
||||||
|
updateEvent = UpdateEvent()
|
||||||
|
updateEvent.destLeft.value = destLeft
|
||||||
|
updateEvent.destTop.value = destTop
|
||||||
|
updateEvent.destRight.value = destRight
|
||||||
|
updateEvent.destBottom.value = destBottom
|
||||||
|
updateEvent.width.value = width
|
||||||
|
updateEvent.height.value = height
|
||||||
|
updateEvent.bpp.value = bpp
|
||||||
|
updateEvent.format.value = upateFormat
|
||||||
|
updateEvent.data.value = data
|
||||||
|
self.rec(updateEvent)
|
||||||
|
|
||||||
|
def screen(self, width, height, colorDepth):
|
||||||
|
"""
|
||||||
|
@summary: record resize event of screen (maybe first event)
|
||||||
|
@param width: {int} width of screen
|
||||||
|
@param height: {int} height of screen
|
||||||
|
@param colorDepth: {int} colorDepth
|
||||||
|
"""
|
||||||
|
screenEvent = ScreenEvent()
|
||||||
|
screenEvent.width.value = width
|
||||||
|
screenEvent.height.value = height
|
||||||
|
screenEvent.colorDepth.value = colorDepth
|
||||||
|
self.rec(screenEvent)
|
||||||
|
|
||||||
|
def credentials(self, username, password, domain = "", hostname = ""):
|
||||||
|
"""
|
||||||
|
@summary: Record informations event
|
||||||
|
@param username: {str} username of session
|
||||||
|
@param password: {str} password of session
|
||||||
|
@param domain: {str} domain of session
|
||||||
|
@param hostname: {str} hostname of session
|
||||||
|
"""
|
||||||
|
infoEvent = InfoEvent()
|
||||||
|
infoEvent.username.value = username
|
||||||
|
infoEvent.password.value = password
|
||||||
|
infoEvent.domain.value = domain
|
||||||
|
infoEvent.hostname.value = hostname
|
||||||
|
self.rec(infoEvent)
|
||||||
|
|
||||||
|
def 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):
|
||||||
|
"""
|
||||||
|
@summary: end of scenario
|
||||||
|
"""
|
||||||
|
self.rec(CloseEvent())
|
||||||
|
|
||||||
|
class FileReader(object):
|
||||||
|
"""
|
||||||
|
@summary: RSR File reader
|
||||||
|
"""
|
||||||
|
def __init__(self, f):
|
||||||
|
"""
|
||||||
|
@param f: {file} file pointer use to read
|
||||||
|
"""
|
||||||
|
self._s = Stream(f.read())
|
||||||
|
|
||||||
|
def nextEvent(self):
|
||||||
|
"""
|
||||||
|
@summary: read next event and return it
|
||||||
|
"""
|
||||||
|
if self._s.dataLen() == 0:
|
||||||
|
return None
|
||||||
|
e = Event()
|
||||||
|
self._s.readType(e)
|
||||||
|
return e
|
||||||
|
|
||||||
|
def createRecorder(path):
|
||||||
|
"""
|
||||||
|
@summary: open file from path and return FileRecorder
|
||||||
|
@param path: {str} path of output file
|
||||||
|
@return: {FileRecorder}
|
||||||
|
"""
|
||||||
|
return FileRecorder(open(path, "wb"))
|
||||||
|
|
||||||
|
def createReader(path):
|
||||||
|
"""
|
||||||
|
@summary: open file from path and return FileReader
|
||||||
|
@param path: {str} path of input file
|
||||||
|
@return: {FileReader}
|
||||||
|
"""
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
return FileReader(f)
|
||||||
60
rdpy/core/scancode.py
Normal file
60
rdpy/core/scancode.py
Normal 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];
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -27,8 +27,8 @@ We are in python!
|
|||||||
import struct
|
import struct
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from rdpy.base.error import InvalidExpectedDataException, InvalidSize, CallPureVirtualFuntion, InvalidValue
|
from rdpy.core.error import InvalidExpectedDataException, InvalidSize, CallPureVirtualFuntion, InvalidValue
|
||||||
import rdpy.base.log as log
|
import rdpy.core.log as log
|
||||||
|
|
||||||
def sizeof(element):
|
def sizeof(element):
|
||||||
"""
|
"""
|
||||||
@@ -257,7 +257,7 @@ class SimpleType(Type, CallableValue):
|
|||||||
@raise InvalidSize: if there is not enough data in stream
|
@raise InvalidSize: if there is not enough data in stream
|
||||||
"""
|
"""
|
||||||
if s.dataLen() < self._typeSize:
|
if s.dataLen() < self._typeSize:
|
||||||
raise InvalidSize("Stream is too small to read expected Simple")
|
raise InvalidSize("Stream is too small to read expected SimpleType")
|
||||||
self.value = struct.unpack(self._structFormat, s.read(self._typeSize))[0]
|
self.value = struct.unpack(self._structFormat, s.read(self._typeSize))[0]
|
||||||
|
|
||||||
def mask(self):
|
def mask(self):
|
||||||
@@ -463,8 +463,9 @@ class CompositeType(Type):
|
|||||||
if not self._readLen is None and readLen > self._readLen.value:
|
if not self._readLen is None and readLen > self._readLen.value:
|
||||||
#roll back
|
#roll back
|
||||||
s.pos -= sizeof(self.__dict__[name])
|
s.pos -= sizeof(self.__dict__[name])
|
||||||
#and notify
|
#and notify if not optional
|
||||||
raise InvalidSize("Impossible to read type %s : read length is too small"%(self.__class__))
|
if not self.__dict__[name]._optional:
|
||||||
|
raise InvalidSize("Impossible to read type %s : read length is too small"%(self.__class__))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("Error during read %s::%s"%(self.__class__, name))
|
log.error("Error during read %s::%s"%(self.__class__, name))
|
||||||
@@ -476,7 +477,7 @@ class CompositeType(Type):
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
if not self._readLen is None and readLen < self._readLen.value:
|
if not self._readLen is None and readLen < self._readLen.value:
|
||||||
log.debug("Still have correct data in packet %s, read it as padding"%self.__class__)
|
log.debug("Still have correct data in packet %s, read %s bytes as padding"%(self.__class__, self._readLen.value - readLen))
|
||||||
s.read(self._readLen.value - readLen)
|
s.read(self._readLen.value - readLen)
|
||||||
|
|
||||||
def __write__(self, s):
|
def __write__(self, s):
|
||||||
@@ -497,6 +498,9 @@ class CompositeType(Type):
|
|||||||
@summary: Call sizeof on each sub type
|
@summary: Call sizeof on each sub type
|
||||||
@return: sum of sizeof of each Type attributes
|
@return: sum of sizeof of each Type attributes
|
||||||
"""
|
"""
|
||||||
|
if self._is_readed and not self._readLen is None:
|
||||||
|
return self._readLen.value
|
||||||
|
|
||||||
size = 0
|
size = 0
|
||||||
for name in self._typeName:
|
for name in self._typeName:
|
||||||
size += sizeof(self.__dict__[name])
|
size += sizeof(self.__dict__[name])
|
||||||
@@ -807,7 +811,7 @@ class String(Type, CallableValue):
|
|||||||
self.value = s.getvalue()[s.pos:]
|
self.value = s.getvalue()[s.pos:]
|
||||||
else:
|
else:
|
||||||
self.value = ""
|
self.value = ""
|
||||||
while self.value[-len(self._until):] != self._until or s.dataLen() == 0:
|
while self.value[-len(self._until):] != self._until and s.dataLen() != 0:
|
||||||
self.value += s.read(1)
|
self.value += s.read(1)
|
||||||
else:
|
else:
|
||||||
self.value = s.read(self._readLen.value)
|
self.value = s.read(self._readLen.value)
|
||||||
@@ -958,6 +962,13 @@ class ArrayType(Type):
|
|||||||
@param s: Stream
|
@param s: Stream
|
||||||
"""
|
"""
|
||||||
s.writeType(self._array)
|
s.writeType(self._array)
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
"""
|
||||||
|
@summary: Magic function to be FactoryType as transparent as possible
|
||||||
|
@return: index of _value
|
||||||
|
"""
|
||||||
|
return self._array.__getitem__(item)
|
||||||
|
|
||||||
def __sizeof__(self):
|
def __sizeof__(self):
|
||||||
"""
|
"""
|
||||||
351
rdpy/protocol/rdp/lic.py
Normal file
351
rdpy/protocol/rdp/lic.py
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
#
|
||||||
|
# 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: RDP extended license
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241880.aspx
|
||||||
|
"""
|
||||||
|
|
||||||
|
from rdpy.core.type import CompositeType, CallableValue, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType, Stream
|
||||||
|
from rdpy.core.error import InvalidExpectedDataException
|
||||||
|
import rdpy.core.log as log
|
||||||
|
import sec
|
||||||
|
from t125 import gcc
|
||||||
|
from rdpy.security import rc4
|
||||||
|
from rdpy.security import rsa_wrapper as rsa
|
||||||
|
|
||||||
|
class MessageType(object):
|
||||||
|
"""
|
||||||
|
@summary: License packet message type
|
||||||
|
"""
|
||||||
|
LICENSE_REQUEST = 0x01
|
||||||
|
PLATFORM_CHALLENGE = 0x02
|
||||||
|
NEW_LICENSE = 0x03
|
||||||
|
UPGRADE_LICENSE = 0x04
|
||||||
|
LICENSE_INFO = 0x12
|
||||||
|
NEW_LICENSE_REQUEST = 0x13
|
||||||
|
PLATFORM_CHALLENGE_RESPONSE = 0x15
|
||||||
|
ERROR_ALERT = 0xFF
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorCode(object):
|
||||||
|
"""
|
||||||
|
@summary: License error message code
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
|
||||||
|
"""
|
||||||
|
ERR_INVALID_SERVER_CERTIFICATE = 0x00000001
|
||||||
|
ERR_NO_LICENSE = 0x00000002
|
||||||
|
ERR_INVALID_SCOPE = 0x00000004
|
||||||
|
ERR_NO_LICENSE_SERVER = 0x00000006
|
||||||
|
STATUS_VALID_CLIENT = 0x00000007
|
||||||
|
ERR_INVALID_CLIENT = 0x00000008
|
||||||
|
ERR_INVALID_PRODUCTID = 0x0000000B
|
||||||
|
ERR_INVALID_MESSAGE_LEN = 0x0000000C
|
||||||
|
ERR_INVALID_MAC = 0x00000003
|
||||||
|
|
||||||
|
class StateTransition(object):
|
||||||
|
"""
|
||||||
|
@summary: Automata state transition
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
|
||||||
|
"""
|
||||||
|
ST_TOTAL_ABORT = 0x00000001
|
||||||
|
ST_NO_TRANSITION = 0x00000002
|
||||||
|
ST_RESET_PHASE_TO_START = 0x00000003
|
||||||
|
ST_RESEND_LAST_MESSAGE = 0x00000004
|
||||||
|
|
||||||
|
class BinaryBlobType(object):
|
||||||
|
"""
|
||||||
|
@summary: Binary blob data type
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
|
||||||
|
"""
|
||||||
|
BB_ANY_BLOB = 0x0000
|
||||||
|
BB_DATA_BLOB = 0x0001
|
||||||
|
BB_RANDOM_BLOB = 0x0002
|
||||||
|
BB_CERTIFICATE_BLOB = 0x0003
|
||||||
|
BB_ERROR_BLOB = 0x0004
|
||||||
|
BB_ENCRYPTED_DATA_BLOB = 0x0009
|
||||||
|
BB_KEY_EXCHG_ALG_BLOB = 0x000D
|
||||||
|
BB_SCOPE_BLOB = 0x000E
|
||||||
|
BB_CLIENT_USER_NAME_BLOB = 0x000F
|
||||||
|
BB_CLIENT_MACHINE_NAME_BLOB = 0x0010
|
||||||
|
|
||||||
|
class Preambule(object):
|
||||||
|
"""
|
||||||
|
@summary: Preambule version
|
||||||
|
"""
|
||||||
|
PREAMBLE_VERSION_2_0 = 0x2
|
||||||
|
PREAMBLE_VERSION_3_0 = 0x3
|
||||||
|
EXTENDED_ERROR_MSG_SUPPORTED = 0x80
|
||||||
|
|
||||||
|
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, 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)
|
||||||
|
|
||||||
|
class LicensingErrorMessage(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: License error message
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
|
||||||
|
"""
|
||||||
|
_MESSAGE_TYPE_ = MessageType.ERROR_ALERT
|
||||||
|
|
||||||
|
def __init__(self, readLen = None):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
self.dwErrorCode = UInt32Le()
|
||||||
|
self.dwStateTransition = UInt32Le()
|
||||||
|
self.blob = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
|
||||||
|
|
||||||
|
class ProductInformation(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: License server product information
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241915.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
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.cbProductId = UInt32Le(lambda:sizeof(self.pbProductId))
|
||||||
|
#may contain "A02" from microsoft license server
|
||||||
|
self.pbProductId = String("A02", readLen = self.cbProductId, unicode = True)
|
||||||
|
|
||||||
|
|
||||||
|
class Scope(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Use in license nego
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241917.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.scope = LicenseBinaryBlob(BinaryBlobType.BB_SCOPE_BLOB)
|
||||||
|
|
||||||
|
class ScopeList(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Use in license nego
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241916.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.scopeCount = UInt32Le(lambda:sizeof(self.scopeArray))
|
||||||
|
self.scopeArray = ArrayType(Scope, readLen = self.scopeCount)
|
||||||
|
|
||||||
|
class ServerLicenseRequest(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Send by server to signal license request
|
||||||
|
server -> client
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241914.aspx
|
||||||
|
"""
|
||||||
|
_MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST
|
||||||
|
|
||||||
|
def __init__(self, readLen = None):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
self.serverRandom = String("\x00" * 32, readLen = CallableValue(32))
|
||||||
|
self.productInfo = ProductInformation()
|
||||||
|
self.keyExchangeList = LicenseBinaryBlob(BinaryBlobType.BB_KEY_EXCHG_ALG_BLOB)
|
||||||
|
self.serverCertificate = LicenseBinaryBlob(BinaryBlobType.BB_CERTIFICATE_BLOB)
|
||||||
|
self.scopeList = ScopeList()
|
||||||
|
|
||||||
|
class ClientNewLicenseRequest(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Send by client to ask new license for client.
|
||||||
|
RDPY doesn'support license reuse, need it in futur version
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
|
||||||
|
"""
|
||||||
|
_MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST
|
||||||
|
|
||||||
|
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 = 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)
|
||||||
|
|
||||||
|
class ServerPlatformChallenge(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: challenge send from server to client
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241921.aspx
|
||||||
|
"""
|
||||||
|
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE
|
||||||
|
|
||||||
|
def __init__(self, readLen = None):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
self.connectFlags = UInt32Le()
|
||||||
|
self.encryptedPlatformChallenge = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
|
||||||
|
self.MACData = String(readLen = CallableValue(16))
|
||||||
|
|
||||||
|
class ClientPLatformChallengeResponse(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: client challenge response
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241922.aspx
|
||||||
|
"""
|
||||||
|
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE_RESPONSE
|
||||||
|
|
||||||
|
def __init__(self, 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 = CallableValue(16))
|
||||||
|
|
||||||
|
class LicPacket(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: A license packet
|
||||||
|
"""
|
||||||
|
def __init__(self, message = None):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
#preambule
|
||||||
|
self.bMsgtype = UInt8(lambda:self.licensingMessage.__class__._MESSAGE_TYPE_)
|
||||||
|
self.flag = UInt8(Preambule.PREAMBLE_VERSION_3_0)
|
||||||
|
self.wMsgSize = UInt16Le(lambda: sizeof(self))
|
||||||
|
|
||||||
|
def LicensingMessageFactory():
|
||||||
|
"""
|
||||||
|
@summary: factory for message nego
|
||||||
|
Use in read mode
|
||||||
|
"""
|
||||||
|
for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest, ServerPlatformChallenge, ClientPLatformChallengeResponse]:
|
||||||
|
if self.bMsgtype.value == c._MESSAGE_TYPE_:
|
||||||
|
return c(readLen = self.wMsgSize - 4)
|
||||||
|
log.debug("unknown license message : %s"%self.bMsgtype.value)
|
||||||
|
return String(readLen = self.wMsgSize - 4)
|
||||||
|
|
||||||
|
if message is None:
|
||||||
|
message = FactoryType(LicensingMessageFactory)
|
||||||
|
elif not "_MESSAGE_TYPE_" in message.__class__.__dict__:
|
||||||
|
raise InvalidExpectedDataException("Try to send an invalid license message")
|
||||||
|
|
||||||
|
self.licensingMessage = message
|
||||||
|
|
||||||
|
def createValidClientLicensingErrorMessage():
|
||||||
|
"""
|
||||||
|
@summary: Create a licensing error message that accept client
|
||||||
|
server automata message
|
||||||
|
"""
|
||||||
|
message = LicensingErrorMessage()
|
||||||
|
message.dwErrorCode.value = ErrorCode.STATUS_VALID_CLIENT
|
||||||
|
message.dwStateTransition.value = StateTransition.ST_NO_TRANSITION
|
||||||
|
return LicPacket(message)
|
||||||
|
|
||||||
|
class LicenseManager(object):
|
||||||
|
"""
|
||||||
|
@summary: handle license automata (client side)
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241890.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self, transport):
|
||||||
|
"""
|
||||||
|
@param transport: layer use to send packet
|
||||||
|
"""
|
||||||
|
self._transport = transport
|
||||||
|
self._username = ""
|
||||||
|
self._hostname = ""
|
||||||
|
|
||||||
|
def recv(self, s):
|
||||||
|
"""
|
||||||
|
@summary: receive license packet from PDU layer
|
||||||
|
@return true when license automata is finish
|
||||||
|
"""
|
||||||
|
licPacket = LicPacket()
|
||||||
|
s.readType(licPacket)
|
||||||
|
|
||||||
|
#end of automata
|
||||||
|
if licPacket.bMsgtype.value == MessageType.ERROR_ALERT and licPacket.licensingMessage.dwErrorCode.value == ErrorCode.STATUS_VALID_CLIENT and licPacket.licensingMessage.dwStateTransition.value == StateTransition.ST_NO_TRANSITION:
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif licPacket.bMsgtype.value == MessageType.LICENSE_REQUEST:
|
||||||
|
self.sendClientNewLicenseRequest(licPacket.licensingMessage)
|
||||||
|
return False
|
||||||
|
|
||||||
|
elif licPacket.bMsgtype.value == MessageType.PLATFORM_CHALLENGE:
|
||||||
|
self.sendClientChallengeResponse(licPacket.licensingMessage)
|
||||||
|
return False
|
||||||
|
|
||||||
|
#yes get a new license
|
||||||
|
elif licPacket.bMsgtype.value == MessageType.NEW_LICENSE:
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise InvalidExpectedDataException("Not a valid license packet")
|
||||||
|
|
||||||
|
|
||||||
|
def sendClientNewLicenseRequest(self, licenseRequest):
|
||||||
|
"""
|
||||||
|
@summary: Create new license request in response to server license request
|
||||||
|
@param licenseRequest: {ServerLicenseRequest}
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241989.aspx
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
|
||||||
|
"""
|
||||||
|
#get server information
|
||||||
|
serverRandom = licenseRequest.serverRandom.value
|
||||||
|
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.readType(serverCertificate)
|
||||||
|
|
||||||
|
#generate crypto values
|
||||||
|
clientRandom = rsa.random(256)
|
||||||
|
preMasterSecret = rsa.random(384)
|
||||||
|
masterSecret = sec.masterSecret(preMasterSecret, clientRandom, serverRandom)
|
||||||
|
sessionKeyBlob = sec.masterSecret(masterSecret, serverRandom, clientRandom)
|
||||||
|
self._macSalt = sessionKeyBlob[:16]
|
||||||
|
self._licenseKey = sec.finalHash(sessionKeyBlob[16:32], clientRandom, serverRandom)
|
||||||
|
|
||||||
|
#format message
|
||||||
|
message = ClientNewLicenseRequest()
|
||||||
|
message.clientRandom.value = clientRandom
|
||||||
|
message.encryptedPreMasterSecret.blobData.value = rsa.encrypt(preMasterSecret[::-1], serverCertificate.certData.getPublicKey())[::-1] + "\x00" * 8
|
||||||
|
message.ClientMachineName.blobData.value = self._hostname + "\x00"
|
||||||
|
message.ClientUserName.blobData.value = self._username + "\x00"
|
||||||
|
self._transport.sendFlagged(sec.SecurityFlag.SEC_LICENSE_PKT, LicPacket(message))
|
||||||
|
|
||||||
|
def sendClientChallengeResponse(self, platformChallenge):
|
||||||
|
"""
|
||||||
|
@summary: generate valid challenge response
|
||||||
|
@param platformChallenge: {ServerPlatformChallenge}
|
||||||
|
"""
|
||||||
|
serverEncryptedChallenge = platformChallenge.encryptedPlatformChallenge.blobData.value
|
||||||
|
#decrypt server challenge
|
||||||
|
#it should be TEST word in unicode format
|
||||||
|
serverChallenge = rc4.crypt(rc4.RC4Key(self._licenseKey), serverEncryptedChallenge)
|
||||||
|
if serverChallenge != "T\x00E\x00S\x00T\x00\x00\x00":
|
||||||
|
raise InvalidExpectedDataException("bad license server challenge")
|
||||||
|
|
||||||
|
#generate hwid
|
||||||
|
s = Stream()
|
||||||
|
s.writeType((UInt32Le(2), String(self._hostname + self._username + "\x00" * 16)))
|
||||||
|
hwid = s.getvalue()[:20]
|
||||||
|
|
||||||
|
message = ClientPLatformChallengeResponse()
|
||||||
|
message.encryptedPlatformChallengeResponse.blobData.value = serverEncryptedChallenge
|
||||||
|
message.encryptedHWID.blobData.value = rc4.crypt(rc4.RC4Key(self._licenseKey), hwid)
|
||||||
|
message.MACData.value = sec.macData(self._macSalt, serverChallenge + hwid)
|
||||||
|
|
||||||
|
self._transport.sendFlagged(sec.SecurityFlag.SEC_LICENSE_PKT, LicPacket(message))
|
||||||
291
rdpy/protocol/rdp/nla/cssp.py
Normal file
291
rdpy/protocol/rdp/nla/cssp.py
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
#
|
||||||
|
# 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
|
||||||
|
import pyasn1.codec.ber.encoder as ber_encoder
|
||||||
|
|
||||||
|
from rdpy.core.type import Stream
|
||||||
|
from twisted.internet import protocol
|
||||||
|
from OpenSSL import crypto
|
||||||
|
from rdpy.security import x509
|
||||||
|
from rdpy.core import error
|
||||||
|
|
||||||
|
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)))
|
||||||
|
)
|
||||||
|
|
||||||
|
class OpenSSLRSAPublicKey(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: asn1 public rsa key
|
||||||
|
@see: https://tools.ietf.org/html/rfc3447
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('unknow', univ.Integer()),
|
||||||
|
namedtype.NamedType('modulus', univ.Integer()),
|
||||||
|
namedtype.NamedType('publicExponent', univ.Integer()),
|
||||||
|
)
|
||||||
|
|
||||||
|
def encodeDERTRequest(negoTypes = [], authInfo = None, pubKeyAuth = None):
|
||||||
|
"""
|
||||||
|
@summary: create TSRequest from list of Type
|
||||||
|
@param negoTypes: {list(Type)}
|
||||||
|
@param authInfo: {str} authentication info TSCredentials encrypted with authentication protocol
|
||||||
|
@param pubKeyAuth: {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 negoTypes:
|
||||||
|
s = Stream()
|
||||||
|
s.writeType(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 authInfo is None:
|
||||||
|
request.setComponentByName("authInfo", univ.OctetString(authInfo).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
|
||||||
|
|
||||||
|
if not pubKeyAuth is None:
|
||||||
|
request.setComponentByName("pubKeyAuth", univ.OctetString(pubKeyAuth).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||||
|
|
||||||
|
return der_encoder.encode(request)
|
||||||
|
|
||||||
|
def decodeDERTRequest(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 getPubKeyAuth(tRequest):
|
||||||
|
return tRequest.getComponentByName("pubKeyAuth").asOctets()
|
||||||
|
|
||||||
|
def encodeDERTCredentials(domain, username, password):
|
||||||
|
passwordCred = TSPasswordCreds()
|
||||||
|
passwordCred.setComponentByName("domainName", univ.OctetString(domain).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
|
||||||
|
passwordCred.setComponentByName("userName", univ.OctetString(username).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
||||||
|
passwordCred.setComponentByName("password", univ.OctetString(password).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
|
||||||
|
|
||||||
|
credentials = TSCredentials()
|
||||||
|
credentials.setComponentByName("credType", univ.Integer(1).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
|
||||||
|
credentials.setComponentByName("credentials", univ.OctetString(der_encoder.encode(passwordCred)).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
||||||
|
|
||||||
|
return der_encoder.encode(credentials)
|
||||||
|
|
||||||
|
class CSSP(protocol.Protocol):
|
||||||
|
"""
|
||||||
|
@summary: Handle CSSP connection
|
||||||
|
Proxy class for authentication
|
||||||
|
"""
|
||||||
|
def __init__(self, layer, authenticationProtocol):
|
||||||
|
"""
|
||||||
|
@param layer: {type.Layer.RawLayer}
|
||||||
|
@param authenticationProtocol: {sspi.IAuthenticationProtocol}
|
||||||
|
"""
|
||||||
|
self._layer = layer
|
||||||
|
self._authenticationProtocol = authenticationProtocol
|
||||||
|
#IGenericSecurityService
|
||||||
|
self._interface = None
|
||||||
|
#function call at the end of nego
|
||||||
|
self._callback = None
|
||||||
|
|
||||||
|
def setFactory(self, factory):
|
||||||
|
"""
|
||||||
|
@summary: Call by RawLayer Factory
|
||||||
|
@param param: RawLayerClientFactory or RawLayerFactory
|
||||||
|
"""
|
||||||
|
self._layer.setFactory(factory)
|
||||||
|
|
||||||
|
def dataReceived(self, data):
|
||||||
|
"""
|
||||||
|
@summary: Inherit from twisted.protocol class
|
||||||
|
main event of received data
|
||||||
|
@param data: string data receive from twisted
|
||||||
|
"""
|
||||||
|
self._layer.dataReceived(data)
|
||||||
|
|
||||||
|
def connectionLost(self, reason):
|
||||||
|
"""
|
||||||
|
@summary: Call from twisted engine when protocol is closed
|
||||||
|
@param reason: str represent reason of close connection
|
||||||
|
"""
|
||||||
|
self._layer._factory.connectionLost(self, reason)
|
||||||
|
|
||||||
|
def connectionMade(self):
|
||||||
|
"""
|
||||||
|
@summary: install proxy
|
||||||
|
"""
|
||||||
|
self._layer.transport = self
|
||||||
|
self._layer.getDescriptor = lambda:self.transport
|
||||||
|
self._layer.connectionMade()
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
"""
|
||||||
|
@summary: write data on transport layer
|
||||||
|
@param data: {str}
|
||||||
|
"""
|
||||||
|
self.transport.write(data)
|
||||||
|
|
||||||
|
def startTLS(self, sslContext):
|
||||||
|
"""
|
||||||
|
@summary: start TLS protocol
|
||||||
|
@param sslContext: {ssl.ClientContextFactory | ssl.DefaultOpenSSLContextFactory} context use for TLS protocol
|
||||||
|
"""
|
||||||
|
self.transport.startTLS(sslContext)
|
||||||
|
|
||||||
|
def startNLA(self, sslContext, callback = None):
|
||||||
|
"""
|
||||||
|
@summary: start NLA authentication
|
||||||
|
@param sslContext: {ssl.ClientContextFactory | ssl.DefaultOpenSSLContextFactory} context use for TLS protocol
|
||||||
|
@param callback: {function} function call when cssp layer is read
|
||||||
|
"""
|
||||||
|
self._callback = callback
|
||||||
|
self.startTLS(sslContext)
|
||||||
|
#send negotiate message
|
||||||
|
self.transport.write(encodeDERTRequest( negoTypes = [ self._authenticationProtocol.getNegotiateMessage() ] ))
|
||||||
|
#next state is receive a challenge
|
||||||
|
self.dataReceived = self.recvChallenge
|
||||||
|
|
||||||
|
def recvChallenge(self, data):
|
||||||
|
"""
|
||||||
|
@summary: second state in cssp automata
|
||||||
|
@param data : {str} all data available on buffer
|
||||||
|
"""
|
||||||
|
request = decodeDERTRequest(data)
|
||||||
|
message, self._interface = self._authenticationProtocol.getAuthenticateMessage(getNegoTokens(request)[0])
|
||||||
|
#get back public key
|
||||||
|
#convert from der to ber...
|
||||||
|
pubKeyDer = crypto.dump_privatekey(crypto.FILETYPE_ASN1, self.transport.protocol._tlsConnection.get_peer_certificate().get_pubkey())
|
||||||
|
pubKey = der_decoder.decode(pubKeyDer, asn1Spec=OpenSSLRSAPublicKey())[0]
|
||||||
|
|
||||||
|
rsa = x509.RSAPublicKey()
|
||||||
|
rsa.setComponentByName("modulus", univ.Integer(pubKey.getComponentByName('modulus')._value))
|
||||||
|
rsa.setComponentByName("publicExponent", univ.Integer(pubKey.getComponentByName('publicExponent')._value))
|
||||||
|
self._pubKeyBer = ber_encoder.encode(rsa)
|
||||||
|
|
||||||
|
#send authenticate message with public key encoded
|
||||||
|
self.transport.write(encodeDERTRequest( negoTypes = [ message ], pubKeyAuth = self._interface.GSS_WrapEx(self._pubKeyBer)))
|
||||||
|
#next step is received public key incremented by one
|
||||||
|
self.dataReceived = self.recvPubKeyInc
|
||||||
|
|
||||||
|
def recvPubKeyInc(self, data):
|
||||||
|
"""
|
||||||
|
@summary: the server send the pubKeyBer + 1
|
||||||
|
@param data : {str} all data available on buffer
|
||||||
|
"""
|
||||||
|
request = decodeDERTRequest(data)
|
||||||
|
pubKeyInc = self._interface.GSS_UnWrapEx(getPubKeyAuth(request))
|
||||||
|
#check pubKeyInc = self._pubKeyBer + 1
|
||||||
|
if not (self._pubKeyBer[1:] == pubKeyInc[1:] and ord(self._pubKeyBer[0]) + 1 == ord(pubKeyInc[0])):
|
||||||
|
raise error.InvalidExpectedDataException("CSSP : Invalid public key increment")
|
||||||
|
|
||||||
|
domain, user, password = self._authenticationProtocol.getEncodedCredentials()
|
||||||
|
#send credentials
|
||||||
|
self.transport.write(encodeDERTRequest( authInfo = self._interface.GSS_WrapEx(encodeDERTCredentials(domain, user, password))))
|
||||||
|
#reset state back to normal state
|
||||||
|
self.dataReceived = lambda x: self.__class__.dataReceived(self, x)
|
||||||
|
if not self._callback is None:
|
||||||
|
self._callback()
|
||||||
635
rdpy/protocol/rdp/nla/ntlm.py
Normal file
635
rdpy/protocol/rdp/nla/ntlm.py
Normal file
@@ -0,0 +1,635 @@
|
|||||||
|
#
|
||||||
|
# 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
|
||||||
|
import sspi
|
||||||
|
import rdpy.security.pyDes as pyDes
|
||||||
|
import rdpy.security.rc4 as rc4
|
||||||
|
from rdpy.security.rsa_wrapper import random
|
||||||
|
from rdpy.core.type import CompositeType, CallableValue, String, UInt8, UInt16Le, UInt24Le, UInt32Le, sizeof, Stream
|
||||||
|
from rdpy.core 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 = String(readLen = self.AvLen)
|
||||||
|
|
||||||
|
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 = String(readLen = CallableValue(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):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.Signature = String("NTLMSSP\x00", readLen = CallableValue(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 = String()
|
||||||
|
|
||||||
|
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 = String("NTLMSSP\x00", readLen = CallableValue(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 = String(readLen = CallableValue(8))
|
||||||
|
self.Reserved = String("\x00" * 8, readLen = CallableValue(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 = String()
|
||||||
|
|
||||||
|
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.readType(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 = String("NTLMSSP\x00", readLen = CallableValue(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 = String("\x00" * 16, readLen = CallableValue(16))
|
||||||
|
self.Payload = String()
|
||||||
|
|
||||||
|
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 "\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 + "session key to client-to-server sealing key magic constant\0")
|
||||||
|
else:
|
||||||
|
return MD5(ExportedSessionKey + "session key to server-to-client sealing key magic constant\0")
|
||||||
|
|
||||||
|
def SIGNKEY(ExportedSessionKey, client):
|
||||||
|
if client:
|
||||||
|
return MD5(ExportedSessionKey + "session key to client-to-server signing key magic constant\0")
|
||||||
|
else:
|
||||||
|
return MD5(ExportedSessionKey + "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 = "\x01"
|
||||||
|
HiResponserversion = "\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.writeType(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.writeType((negotiateMessage, challengeMessage, authenticateMessage))
|
||||||
|
return HMAC_MD5(ExportedSessionKey, s.getvalue())
|
||||||
|
|
||||||
|
class NTLMv2(sspi.IAuthenticationProtocol):
|
||||||
|
"""
|
||||||
|
@summary: Handle NTLMv2 Authentication
|
||||||
|
"""
|
||||||
|
def __init__(self, domain, user, password):
|
||||||
|
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.readType(self._challengeMessage)
|
||||||
|
|
||||||
|
ServerChallenge = self._challengeMessage.ServerChallenge.value
|
||||||
|
ClientChallenge = random(64)
|
||||||
|
|
||||||
|
computeMIC = False
|
||||||
|
ServerName = self._challengeMessage.getTargetInfo()
|
||||||
|
infos = self._challengeMessage.getTargetInfoAsAvPairArray()
|
||||||
|
if infos.has_key(AvId.MsvAvTimestamp):
|
||||||
|
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, "")
|
||||||
|
|
||||||
|
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.writeType(signature)
|
||||||
|
return s.getvalue() + encryptedData
|
||||||
|
|
||||||
|
def GSS_UnWrapEx(self, data):
|
||||||
|
"""
|
||||||
|
@summary: decrypt data with key exchange in Authentication protocol
|
||||||
|
@param data: {str}
|
||||||
|
"""
|
||||||
|
signature = MessageSignatureEx()
|
||||||
|
message = String()
|
||||||
|
s = Stream(data)
|
||||||
|
s.readType((signature, message))
|
||||||
|
|
||||||
|
#decrypt message
|
||||||
|
plaintextMessage = rc4.crypt(self._decryptHandle, message.value)
|
||||||
|
checksum = rc4.crypt(self._decryptHandle, signature.Checksum.value)
|
||||||
|
|
||||||
|
#recompute checksum
|
||||||
|
t = Stream()
|
||||||
|
t.writeType(signature.SeqNum)
|
||||||
|
verify = HMAC_MD5(self._verifyKey, t.getvalue() + plaintextMessage)[:8]
|
||||||
|
if verify != checksum:
|
||||||
|
raise error.InvalidExpectedDataException("NTLMv2SecurityInterface : Invalid checksum")
|
||||||
|
|
||||||
|
return plaintextMessage
|
||||||
69
rdpy/protocol/rdp/nla/sspi.py
Normal file
69
rdpy/protocol/rdp/nla/sspi.py
Normal 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.core.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"))
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -16,19 +16,19 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from rdpy.base.error import InvalidExpectedDataException
|
from rdpy.core.error import InvalidExpectedDataException
|
||||||
import rdpy.base.log as log
|
import rdpy.core.log as log
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Definition of structure use for capabilities nego
|
Definition of structure use for capabilities nego
|
||||||
Use in PDU layer
|
Use in PDU layer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.network.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
|
from rdpy.core.type import CompositeType, CallableValue, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
|
||||||
|
|
||||||
class CapsType(object):
|
class CapsType(object):
|
||||||
"""
|
"""
|
||||||
Different type of capabilities
|
@summary: Different type of capabilities
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240486.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240486.aspx
|
||||||
"""
|
"""
|
||||||
CAPSTYPE_GENERAL = 0x0001
|
CAPSTYPE_GENERAL = 0x0001
|
||||||
@@ -62,7 +62,7 @@ class CapsType(object):
|
|||||||
|
|
||||||
class MajorType(object):
|
class MajorType(object):
|
||||||
"""
|
"""
|
||||||
Use in general capability
|
@summary: Use in general capability
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
|
||||||
"""
|
"""
|
||||||
OSMAJORTYPE_UNSPECIFIED = 0x0000
|
OSMAJORTYPE_UNSPECIFIED = 0x0000
|
||||||
@@ -76,7 +76,7 @@ class MajorType(object):
|
|||||||
|
|
||||||
class MinorType(object):
|
class MinorType(object):
|
||||||
"""
|
"""
|
||||||
Use in general capability
|
@summary: Use in general capability
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
|
||||||
"""
|
"""
|
||||||
OSMINORTYPE_UNSPECIFIED = 0x0000
|
OSMINORTYPE_UNSPECIFIED = 0x0000
|
||||||
@@ -92,7 +92,7 @@ class MinorType(object):
|
|||||||
|
|
||||||
class GeneralExtraFlag(object):
|
class GeneralExtraFlag(object):
|
||||||
"""
|
"""
|
||||||
Use in general capability
|
@summary: Use in general capability
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
|
||||||
"""
|
"""
|
||||||
FASTPATH_OUTPUT_SUPPORTED = 0x0001
|
FASTPATH_OUTPUT_SUPPORTED = 0x0001
|
||||||
@@ -107,7 +107,7 @@ class Boolean(object):
|
|||||||
|
|
||||||
class OrderFlag(object):
|
class OrderFlag(object):
|
||||||
"""
|
"""
|
||||||
Use in order capability
|
@summary: Use in order capability
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240556.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240556.aspx
|
||||||
"""
|
"""
|
||||||
NEGOTIATEORDERSUPPORT = 0x0002
|
NEGOTIATEORDERSUPPORT = 0x0002
|
||||||
@@ -118,7 +118,7 @@ class OrderFlag(object):
|
|||||||
|
|
||||||
class Order(object):
|
class Order(object):
|
||||||
"""
|
"""
|
||||||
Drawing orders supported
|
@summary: Drawing orders supported
|
||||||
Use in order capability
|
Use in order capability
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240556.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240556.aspx
|
||||||
"""
|
"""
|
||||||
@@ -146,7 +146,7 @@ class Order(object):
|
|||||||
|
|
||||||
class OrderEx(object):
|
class OrderEx(object):
|
||||||
"""
|
"""
|
||||||
Extension orders
|
@summary: Extension orders
|
||||||
Use in order capability
|
Use in order capability
|
||||||
"""
|
"""
|
||||||
ORDERFLAGS_EX_CACHE_BITMAP_REV3_SUPPORT = 0x0002
|
ORDERFLAGS_EX_CACHE_BITMAP_REV3_SUPPORT = 0x0002
|
||||||
@@ -154,7 +154,7 @@ class OrderEx(object):
|
|||||||
|
|
||||||
class InputFlags(object):
|
class InputFlags(object):
|
||||||
"""
|
"""
|
||||||
Input flag use in input capability
|
@summary: Input flag use in input capability
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240563.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240563.aspx
|
||||||
"""
|
"""
|
||||||
INPUT_FLAG_SCANCODES = 0x0001
|
INPUT_FLAG_SCANCODES = 0x0001
|
||||||
@@ -168,7 +168,7 @@ class InputFlags(object):
|
|||||||
|
|
||||||
class BrushSupport(object):
|
class BrushSupport(object):
|
||||||
"""
|
"""
|
||||||
Brush support of client
|
@summary: Brush support of client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240564.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240564.aspx
|
||||||
"""
|
"""
|
||||||
BRUSH_DEFAULT = 0x00000000
|
BRUSH_DEFAULT = 0x00000000
|
||||||
@@ -177,7 +177,7 @@ class BrushSupport(object):
|
|||||||
|
|
||||||
class GlyphSupport(object):
|
class GlyphSupport(object):
|
||||||
"""
|
"""
|
||||||
Use by glyph order
|
@summary: Use by glyph order
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240565.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240565.aspx
|
||||||
"""
|
"""
|
||||||
GLYPH_SUPPORT_NONE = 0x0000
|
GLYPH_SUPPORT_NONE = 0x0000
|
||||||
@@ -187,7 +187,7 @@ class GlyphSupport(object):
|
|||||||
|
|
||||||
class OffscreenSupportLevel(object):
|
class OffscreenSupportLevel(object):
|
||||||
"""
|
"""
|
||||||
Use to determine offscreen cache level supported
|
@summary: Use to determine offscreen cache level supported
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240550.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240550.aspx
|
||||||
"""
|
"""
|
||||||
FALSE = 0x00000000
|
FALSE = 0x00000000
|
||||||
@@ -195,7 +195,7 @@ class OffscreenSupportLevel(object):
|
|||||||
|
|
||||||
class VirtualChannelCompressionFlag(object):
|
class VirtualChannelCompressionFlag(object):
|
||||||
"""
|
"""
|
||||||
Use to determine virtual channel compression
|
@summary: Use to determine virtual channel compression
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240551.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240551.aspx
|
||||||
"""
|
"""
|
||||||
VCCAPS_NO_COMPR = 0x00000000
|
VCCAPS_NO_COMPR = 0x00000000
|
||||||
@@ -204,7 +204,7 @@ class VirtualChannelCompressionFlag(object):
|
|||||||
|
|
||||||
class SoundFlag(object):
|
class SoundFlag(object):
|
||||||
"""
|
"""
|
||||||
Use in sound capability to inform it
|
@summary: Use in sound capability to inform it
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240552.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240552.aspx
|
||||||
"""
|
"""
|
||||||
NONE = 0x0000
|
NONE = 0x0000
|
||||||
@@ -212,7 +212,7 @@ class SoundFlag(object):
|
|||||||
|
|
||||||
class CacheEntry(CompositeType):
|
class CacheEntry(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use in capability cache exchange
|
@summary: Use in capability cache exchange
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240566.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240566.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -223,7 +223,7 @@ class CacheEntry(CompositeType):
|
|||||||
|
|
||||||
class Capability(CompositeType):
|
class Capability(CompositeType):
|
||||||
"""
|
"""
|
||||||
A capability
|
@summary: A capability
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240486.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240486.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, capability = None):
|
def __init__(self, capability = None):
|
||||||
@@ -235,8 +235,8 @@ class Capability(CompositeType):
|
|||||||
"""
|
"""
|
||||||
Closure for capability factory
|
Closure for capability factory
|
||||||
"""
|
"""
|
||||||
for c in [GeneralCapability, BitmapCapability, OrderCapability, BitmapCacheCapability, PointerCapability, InputCapability, BrushCapability, GlyphCapability, OffscreenBitmapCacheCapability, VirtualChannelCapability, SoundCapability, ControlCapability, WindowActivationCapability, FontCapability, ColorCacheCapability, ShareCapability]:
|
for c in [GeneralCapability, BitmapCapability, OrderCapability, BitmapCacheCapability, PointerCapability, InputCapability, BrushCapability, GlyphCapability, OffscreenBitmapCacheCapability, VirtualChannelCapability, SoundCapability, ControlCapability, WindowActivationCapability, FontCapability, ColorCacheCapability, ShareCapability, MultiFragmentUpdate]:
|
||||||
if self.capabilitySetType.value == c._TYPE_:
|
if self.capabilitySetType.value == c._TYPE_ and (self.lengthCapability.value - 4) > 0:
|
||||||
return c(readLen = self.lengthCapability - 4)
|
return c(readLen = self.lengthCapability - 4)
|
||||||
log.debug("unknown Capability type : %s"%hex(self.capabilitySetType.value))
|
log.debug("unknown Capability type : %s"%hex(self.capabilitySetType.value))
|
||||||
#read entire packet
|
#read entire packet
|
||||||
@@ -251,7 +251,7 @@ class Capability(CompositeType):
|
|||||||
|
|
||||||
class GeneralCapability(CompositeType):
|
class GeneralCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
General capability (protocol version and compression mode)
|
@summary: General capability (protocol version and compression mode)
|
||||||
client -> server
|
client -> server
|
||||||
server -> client
|
server -> client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
|
||||||
@@ -274,7 +274,7 @@ class GeneralCapability(CompositeType):
|
|||||||
|
|
||||||
class BitmapCapability(CompositeType):
|
class BitmapCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
Bitmap format Capability
|
@summary: Bitmap format Capability
|
||||||
client -> server
|
client -> server
|
||||||
server -> client
|
server -> client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240554.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240554.aspx
|
||||||
@@ -299,7 +299,7 @@ class BitmapCapability(CompositeType):
|
|||||||
|
|
||||||
class OrderCapability(CompositeType):
|
class OrderCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
Order capability list all drawing order supported
|
@summary: Order capability list all drawing order supported
|
||||||
client -> server
|
client -> server
|
||||||
server -> client
|
server -> client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240556.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240556.aspx
|
||||||
@@ -308,7 +308,7 @@ class OrderCapability(CompositeType):
|
|||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.terminalDescriptor = String("\x00" * 16, readLen = UInt8(16))
|
self.terminalDescriptor = String("\x00" * 16, readLen = CallableValue(16))
|
||||||
self.pad4octetsA = UInt32Le(0)
|
self.pad4octetsA = UInt32Le(0)
|
||||||
self.desktopSaveXGranularity = UInt16Le(1)
|
self.desktopSaveXGranularity = UInt16Le(1)
|
||||||
self.desktopSaveYGranularity = UInt16Le(20)
|
self.desktopSaveYGranularity = UInt16Le(20)
|
||||||
@@ -316,7 +316,7 @@ class OrderCapability(CompositeType):
|
|||||||
self.maximumOrderLevel = UInt16Le(1)
|
self.maximumOrderLevel = UInt16Le(1)
|
||||||
self.numberFonts = UInt16Le()
|
self.numberFonts = UInt16Le()
|
||||||
self.orderFlags = UInt16Le(OrderFlag.NEGOTIATEORDERSUPPORT)
|
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.textFlags = UInt16Le()
|
||||||
self.orderSupportExFlags = UInt16Le()
|
self.orderSupportExFlags = UInt16Le()
|
||||||
self.pad4octetsB = UInt32Le()
|
self.pad4octetsB = UInt32Le()
|
||||||
@@ -328,7 +328,7 @@ class OrderCapability(CompositeType):
|
|||||||
|
|
||||||
class BitmapCacheCapability(CompositeType):
|
class BitmapCacheCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
Order use to cache bitmap very useful
|
@summary: Order use to cache bitmap very useful
|
||||||
client -> server
|
client -> server
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240559.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240559.aspx
|
||||||
"""
|
"""
|
||||||
@@ -351,7 +351,7 @@ class BitmapCacheCapability(CompositeType):
|
|||||||
|
|
||||||
class PointerCapability(CompositeType):
|
class PointerCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to indicate pointer handle of client
|
@summary: Use to indicate pointer handle of client
|
||||||
Paint by server or per client
|
Paint by server or per client
|
||||||
client -> server
|
client -> server
|
||||||
server -> client
|
server -> client
|
||||||
@@ -359,15 +359,16 @@ class PointerCapability(CompositeType):
|
|||||||
"""
|
"""
|
||||||
_TYPE_ = CapsType.CAPSTYPE_POINTER
|
_TYPE_ = CapsType.CAPSTYPE_POINTER
|
||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, isServer = False, readLen = None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.colorPointerFlag = UInt16Le()
|
self.colorPointerFlag = UInt16Le()
|
||||||
self.colorPointerCacheSize = UInt16Le()
|
self.colorPointerCacheSize = UInt16Le(20)
|
||||||
self.pointerCacheSize = UInt16Le()
|
#old version of rdp doesn't support ...
|
||||||
|
self.pointerCacheSize = UInt16Le(conditional = lambda:isServer)
|
||||||
|
|
||||||
class InputCapability(CompositeType):
|
class InputCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to indicate input capabilities
|
@summary: Use to indicate input capabilities
|
||||||
client -> server
|
client -> server
|
||||||
server -> client
|
server -> client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240563.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240563.aspx
|
||||||
@@ -387,11 +388,11 @@ class InputCapability(CompositeType):
|
|||||||
#same value as gcc.ClientCoreSettings.keyboardFnKeys
|
#same value as gcc.ClientCoreSettings.keyboardFnKeys
|
||||||
self.keyboardFunctionKey = UInt32Le()
|
self.keyboardFunctionKey = UInt32Le()
|
||||||
#same value as gcc.ClientCoreSettingrrs.imeFileName
|
#same value as gcc.ClientCoreSettingrrs.imeFileName
|
||||||
self.imeFileName = String("\x00" * 64, readLen = UInt8(64))
|
self.imeFileName = String("\x00" * 64, readLen = CallableValue(64))
|
||||||
|
|
||||||
class BrushCapability(CompositeType):
|
class BrushCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to indicate brush capability
|
@summary: Use to indicate brush capability
|
||||||
client -> server
|
client -> server
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240564.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240564.aspx
|
||||||
"""
|
"""
|
||||||
@@ -403,7 +404,7 @@ class BrushCapability(CompositeType):
|
|||||||
|
|
||||||
class GlyphCapability(CompositeType):
|
class GlyphCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use in font order
|
@summary: Use in font order
|
||||||
client -> server
|
client -> server
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240565.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240565.aspx
|
||||||
"""
|
"""
|
||||||
@@ -411,7 +412,7 @@ class GlyphCapability(CompositeType):
|
|||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
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()
|
self.fragCache = UInt32Le()
|
||||||
#all fonts are sent with bitmap format (very expensive)
|
#all fonts are sent with bitmap format (very expensive)
|
||||||
self.glyphSupportLevel = UInt16Le(GlyphSupport.GLYPH_SUPPORT_NONE)
|
self.glyphSupportLevel = UInt16Le(GlyphSupport.GLYPH_SUPPORT_NONE)
|
||||||
@@ -419,7 +420,7 @@ class GlyphCapability(CompositeType):
|
|||||||
|
|
||||||
class OffscreenBitmapCacheCapability(CompositeType):
|
class OffscreenBitmapCacheCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
use to cached bitmap in offscreen area
|
@summary: use to cached bitmap in offscreen area
|
||||||
client -> server
|
client -> server
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240550.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240550.aspx
|
||||||
"""
|
"""
|
||||||
@@ -433,7 +434,7 @@ class OffscreenBitmapCacheCapability(CompositeType):
|
|||||||
|
|
||||||
class VirtualChannelCapability(CompositeType):
|
class VirtualChannelCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
use to determine virtual channel compression
|
@summary: use to determine virtual channel compression
|
||||||
client -> server
|
client -> server
|
||||||
server -> client
|
server -> client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240551.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240551.aspx
|
||||||
@@ -447,7 +448,7 @@ class VirtualChannelCapability(CompositeType):
|
|||||||
|
|
||||||
class SoundCapability(CompositeType):
|
class SoundCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to exchange sound capability
|
@summary: Use to exchange sound capability
|
||||||
client -> server
|
client -> server
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240552.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240552.aspx
|
||||||
"""
|
"""
|
||||||
@@ -460,7 +461,7 @@ class SoundCapability(CompositeType):
|
|||||||
|
|
||||||
class ControlCapability(CompositeType):
|
class ControlCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
client -> server but server ignore contents! Thanks krosoft for brandwidth
|
@summary: client -> server but server ignore contents! Thanks krosoft for brandwidth
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240568.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240568.aspx
|
||||||
"""
|
"""
|
||||||
_TYPE_ = CapsType.CAPSTYPE_CONTROL
|
_TYPE_ = CapsType.CAPSTYPE_CONTROL
|
||||||
@@ -474,7 +475,7 @@ class ControlCapability(CompositeType):
|
|||||||
|
|
||||||
class WindowActivationCapability(CompositeType):
|
class WindowActivationCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
client -> server but server ignore contents! Thanks krosoft for brandwidth
|
@summary: client -> server but server ignore contents! Thanks krosoft for brandwidth
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240569.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240569.aspx
|
||||||
"""
|
"""
|
||||||
_TYPE_ = CapsType.CAPSTYPE_ACTIVATION
|
_TYPE_ = CapsType.CAPSTYPE_ACTIVATION
|
||||||
@@ -488,7 +489,7 @@ class WindowActivationCapability(CompositeType):
|
|||||||
|
|
||||||
class FontCapability(CompositeType):
|
class FontCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to indicate font support
|
@summary: Use to indicate font support
|
||||||
client -> server
|
client -> server
|
||||||
server -> client
|
server -> client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240571.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240571.aspx
|
||||||
@@ -515,7 +516,7 @@ class ColorCacheCapability(CompositeType):
|
|||||||
|
|
||||||
class ShareCapability(CompositeType):
|
class ShareCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to advertise channel id of server
|
@summary: Use to advertise channel id of server
|
||||||
client -> server
|
client -> server
|
||||||
server -> client
|
server -> client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240570.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240570.aspx
|
||||||
@@ -525,4 +526,17 @@ class ShareCapability(CompositeType):
|
|||||||
def __init__(self, readLen = None):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.nodeId = UInt16Le()
|
self.nodeId = UInt16Le()
|
||||||
self.pad2octets = UInt16Le()
|
self.pad2octets = UInt16Le()
|
||||||
|
|
||||||
|
class MultiFragmentUpdate(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Use to advertise fast path max buffer to use
|
||||||
|
client -> server
|
||||||
|
server -> client
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240649.aspx
|
||||||
|
"""
|
||||||
|
_TYPE_ = CapsType.CAPSETTYPE_MULTIFRAGMENTUPDATE
|
||||||
|
|
||||||
|
def __init__(self, readLen = None):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
self.MaxRequestSize = UInt32Le(0)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -22,80 +22,14 @@ Implement the main graphic layer
|
|||||||
|
|
||||||
In this layer are managed all mains bitmap update orders end user inputs
|
In this layer are managed all mains bitmap update orders end user inputs
|
||||||
"""
|
"""
|
||||||
from rdpy.network.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
|
from rdpy.core.type import CompositeType, CallableValue, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
|
||||||
from rdpy.base.error import InvalidExpectedDataException
|
from rdpy.core.error import InvalidExpectedDataException
|
||||||
import rdpy.base.log as log
|
import rdpy.core.log as log
|
||||||
import caps, order
|
import caps, order
|
||||||
|
|
||||||
class SecurityFlag(object):
|
|
||||||
"""
|
|
||||||
Microsoft security flags
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240579.aspx
|
|
||||||
"""
|
|
||||||
SEC_EXCHANGE_PKT = 0x0001
|
|
||||||
SEC_TRANSPORT_REQ = 0x0002
|
|
||||||
RDP_SEC_TRANSPORT_RSP = 0x0004
|
|
||||||
SEC_ENCRYPT = 0x0008
|
|
||||||
SEC_RESET_SEQNO = 0x0010
|
|
||||||
SEC_IGNORE_SEQNO = 0x0020
|
|
||||||
SEC_INFO_PKT = 0x0040
|
|
||||||
SEC_LICENSE_PKT = 0x0080
|
|
||||||
SEC_LICENSE_ENCRYPT_CS = 0x0200
|
|
||||||
SEC_LICENSE_ENCRYPT_SC = 0x0200
|
|
||||||
SEC_REDIRECTION_PKT = 0x0400
|
|
||||||
SEC_SECURE_CHECKSUM = 0x0800
|
|
||||||
SEC_AUTODETECT_REQ = 0x1000
|
|
||||||
SEC_AUTODETECT_RSP = 0x2000
|
|
||||||
SEC_HEARTBEAT = 0x4000
|
|
||||||
SEC_FLAGSHI_VALID = 0x8000
|
|
||||||
|
|
||||||
class InfoFlag(object):
|
|
||||||
"""
|
|
||||||
Client capabilities informations
|
|
||||||
"""
|
|
||||||
INFO_MOUSE = 0x00000001
|
|
||||||
INFO_DISABLECTRLALTDEL = 0x00000002
|
|
||||||
INFO_AUTOLOGON = 0x00000008
|
|
||||||
INFO_UNICODE = 0x00000010
|
|
||||||
INFO_MAXIMIZESHELL = 0x00000020
|
|
||||||
INFO_LOGONNOTIFY = 0x00000040
|
|
||||||
INFO_COMPRESSION = 0x00000080
|
|
||||||
INFO_ENABLEWINDOWSKEY = 0x00000100
|
|
||||||
INFO_REMOTECONSOLEAUDIO = 0x00002000
|
|
||||||
INFO_FORCE_ENCRYPTED_CS_PDU = 0x00004000
|
|
||||||
INFO_RAIL = 0x00008000
|
|
||||||
INFO_LOGONERRORS = 0x00010000
|
|
||||||
INFO_MOUSE_HAS_WHEEL = 0x00020000
|
|
||||||
INFO_PASSWORD_IS_SC_PIN = 0x00040000
|
|
||||||
INFO_NOAUDIOPLAYBACK = 0x00080000
|
|
||||||
INFO_USING_SAVED_CREDS = 0x00100000
|
|
||||||
INFO_AUDIOCAPTURE = 0x00200000
|
|
||||||
INFO_VIDEO_DISABLE = 0x00400000
|
|
||||||
INFO_CompressionTypeMask = 0x00001E00
|
|
||||||
|
|
||||||
class PerfFlag(object):
|
|
||||||
"""
|
|
||||||
Network performances flag
|
|
||||||
"""
|
|
||||||
PERF_DISABLE_WALLPAPER = 0x00000001
|
|
||||||
PERF_DISABLE_FULLWINDOWDRAG = 0x00000002
|
|
||||||
PERF_DISABLE_MENUANIMATIONS = 0x00000004
|
|
||||||
PERF_DISABLE_THEMING = 0x00000008
|
|
||||||
PERF_DISABLE_CURSOR_SHADOW = 0x00000020
|
|
||||||
PERF_DISABLE_CURSORSETTINGS = 0x00000040
|
|
||||||
PERF_ENABLE_FONT_SMOOTHING = 0x00000080
|
|
||||||
PERF_ENABLE_DESKTOP_COMPOSITION = 0x00000100
|
|
||||||
|
|
||||||
class AfInet(object):
|
|
||||||
"""
|
|
||||||
IPv4 or IPv6 adress style
|
|
||||||
"""
|
|
||||||
AF_INET = 0x00002
|
|
||||||
AF_INET6 = 0x0017
|
|
||||||
|
|
||||||
class PDUType(object):
|
class PDUType(object):
|
||||||
"""
|
"""
|
||||||
Data PDU type primary index
|
@summary: Data PDU type primary index
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
|
||||||
"""
|
"""
|
||||||
PDUTYPE_DEMANDACTIVEPDU = 0x11
|
PDUTYPE_DEMANDACTIVEPDU = 0x11
|
||||||
@@ -106,7 +40,7 @@ class PDUType(object):
|
|||||||
|
|
||||||
class PDUType2(object):
|
class PDUType2(object):
|
||||||
"""
|
"""
|
||||||
Data PDU type secondary index
|
@summary: Data PDU type secondary index
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
||||||
"""
|
"""
|
||||||
PDUTYPE2_UPDATE = 0x02
|
PDUTYPE2_UPDATE = 0x02
|
||||||
@@ -136,7 +70,7 @@ class PDUType2(object):
|
|||||||
|
|
||||||
class StreamId(object):
|
class StreamId(object):
|
||||||
"""
|
"""
|
||||||
Stream priority
|
@summary: Stream priority
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
||||||
"""
|
"""
|
||||||
STREAM_UNDEFINED = 0x00
|
STREAM_UNDEFINED = 0x00
|
||||||
@@ -146,7 +80,7 @@ class StreamId(object):
|
|||||||
|
|
||||||
class CompressionOrder(object):
|
class CompressionOrder(object):
|
||||||
"""
|
"""
|
||||||
PDU compression order
|
@summary: PDU compression order
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
||||||
"""
|
"""
|
||||||
CompressionTypeMask = 0x0F
|
CompressionTypeMask = 0x0F
|
||||||
@@ -156,7 +90,7 @@ class CompressionOrder(object):
|
|||||||
|
|
||||||
class CompressionType(object):
|
class CompressionType(object):
|
||||||
"""
|
"""
|
||||||
PDU compression type
|
@summary: PDU compression type
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
||||||
"""
|
"""
|
||||||
PACKET_COMPR_TYPE_8K = 0x0
|
PACKET_COMPR_TYPE_8K = 0x0
|
||||||
@@ -166,7 +100,7 @@ class CompressionType(object):
|
|||||||
|
|
||||||
class Action(object):
|
class Action(object):
|
||||||
"""
|
"""
|
||||||
Action flag use in Control PDU packet
|
@summary: Action flag use in Control PDU packet
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240492.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240492.aspx
|
||||||
"""
|
"""
|
||||||
CTRLACTION_REQUEST_CONTROL = 0x0001
|
CTRLACTION_REQUEST_CONTROL = 0x0001
|
||||||
@@ -176,7 +110,7 @@ class Action(object):
|
|||||||
|
|
||||||
class PersistentKeyListFlag(object):
|
class PersistentKeyListFlag(object):
|
||||||
"""
|
"""
|
||||||
Use to determine the number of persistent key packet
|
@summary: Use to determine the number of persistent key packet
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
|
||||||
"""
|
"""
|
||||||
PERSIST_FIRST_PDU = 0x01
|
PERSIST_FIRST_PDU = 0x01
|
||||||
@@ -184,7 +118,7 @@ class PersistentKeyListFlag(object):
|
|||||||
|
|
||||||
class BitmapFlag(object):
|
class BitmapFlag(object):
|
||||||
"""
|
"""
|
||||||
Use in bitmap update PDU
|
@summary: Use in bitmap update PDU
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240612.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240612.aspx
|
||||||
"""
|
"""
|
||||||
BITMAP_COMPRESSION = 0x0001
|
BITMAP_COMPRESSION = 0x0001
|
||||||
@@ -192,7 +126,7 @@ class BitmapFlag(object):
|
|||||||
|
|
||||||
class UpdateType(object):
|
class UpdateType(object):
|
||||||
"""
|
"""
|
||||||
Use in update PDU to determine which type of update
|
@summary: Use in update PDU to determine which type of update
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
|
||||||
"""
|
"""
|
||||||
UPDATETYPE_ORDERS = 0x0000
|
UPDATETYPE_ORDERS = 0x0000
|
||||||
@@ -202,7 +136,7 @@ class UpdateType(object):
|
|||||||
|
|
||||||
class InputMessageType(object):
|
class InputMessageType(object):
|
||||||
"""
|
"""
|
||||||
Use in slow-path input PDU
|
@summary: Use in slow-path input PDU
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
|
||||||
"""
|
"""
|
||||||
INPUT_EVENT_SYNC = 0x0000
|
INPUT_EVENT_SYNC = 0x0000
|
||||||
@@ -214,7 +148,7 @@ class InputMessageType(object):
|
|||||||
|
|
||||||
class PointerFlag(object):
|
class PointerFlag(object):
|
||||||
"""
|
"""
|
||||||
Use in Pointer event
|
@summary: Use in Pointer event
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
|
||||||
"""
|
"""
|
||||||
PTRFLAGS_HWHEEL = 0x0400
|
PTRFLAGS_HWHEEL = 0x0400
|
||||||
@@ -229,7 +163,7 @@ class PointerFlag(object):
|
|||||||
|
|
||||||
class KeyboardFlag(object):
|
class KeyboardFlag(object):
|
||||||
"""
|
"""
|
||||||
Use in scan code key event
|
@summary: Use in scan code key event
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
|
||||||
"""
|
"""
|
||||||
KBDFLAGS_EXTENDED = 0x0100
|
KBDFLAGS_EXTENDED = 0x0100
|
||||||
@@ -238,7 +172,7 @@ class KeyboardFlag(object):
|
|||||||
|
|
||||||
class FastPathUpdateType(object):
|
class FastPathUpdateType(object):
|
||||||
"""
|
"""
|
||||||
Use in Fast Path update packet
|
@summary: Use in Fast Path update packet
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
|
||||||
"""
|
"""
|
||||||
FASTPATH_UPDATETYPE_ORDERS = 0x0
|
FASTPATH_UPDATETYPE_ORDERS = 0x0
|
||||||
@@ -255,22 +189,32 @@ class FastPathUpdateType(object):
|
|||||||
|
|
||||||
class FastPathOutputCompression(object):
|
class FastPathOutputCompression(object):
|
||||||
"""
|
"""
|
||||||
Flag for compression
|
@summary: Flag for compression
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
|
||||||
"""
|
"""
|
||||||
FASTPATH_OUTPUT_COMPRESSION_USED = 0x2
|
FASTPATH_OUTPUT_COMPRESSION_USED = 0x2
|
||||||
|
|
||||||
class Display(object):
|
class Display(object):
|
||||||
"""
|
"""
|
||||||
Use in supress output PDU
|
@summary: Use in supress output PDU
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240648.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240648.aspx
|
||||||
"""
|
"""
|
||||||
SUPPRESS_DISPLAY_UPDATES = 0x00
|
SUPPRESS_DISPLAY_UPDATES = 0x00
|
||||||
ALLOW_DISPLAY_UPDATES = 0x01
|
ALLOW_DISPLAY_UPDATES = 0x01
|
||||||
|
|
||||||
|
class ToogleFlag(object):
|
||||||
|
"""
|
||||||
|
@summary: Use to known state of keyboard
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240588.aspx
|
||||||
|
"""
|
||||||
|
TS_SYNC_SCROLL_LOCK = 0x00000001
|
||||||
|
TS_SYNC_NUM_LOCK = 0x00000002
|
||||||
|
TS_SYNC_CAPS_LOCK = 0x00000004
|
||||||
|
TS_SYNC_KANA_LOCK = 0x00000008
|
||||||
|
|
||||||
class ErrorInfo(object):
|
class ErrorInfo(object):
|
||||||
"""
|
"""
|
||||||
Error code use in Error info PDU
|
@summary: Error code use in Error info PDU
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
|
||||||
"""
|
"""
|
||||||
ERRINFO_RPC_INITIATED_DISCONNECT = 0x00000001
|
ERRINFO_RPC_INITIATED_DISCONNECT = 0x00000001
|
||||||
@@ -479,68 +423,26 @@ class ErrorInfo(object):
|
|||||||
ERRINFO_VCDATATOOLONG : "The size of a received Virtual Channel PDU (section 2.2.6.1) exceeds the chunking size specified in the Virtual Channel Capability Set (section 2.2.7.1.10).",
|
ERRINFO_VCDATATOOLONG : "The size of a received Virtual Channel PDU (section 2.2.6.1) exceeds the chunking size specified in the Virtual Channel Capability Set (section 2.2.7.1.10).",
|
||||||
}
|
}
|
||||||
|
|
||||||
class RDPInfo(CompositeType):
|
|
||||||
"""
|
|
||||||
Client informations
|
|
||||||
Contains credentials (very important packet)
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240475.aspx
|
|
||||||
"""
|
|
||||||
def __init__(self, extendedInfoConditional):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
#code page
|
|
||||||
self.codePage = UInt32Le()
|
|
||||||
#support flag
|
|
||||||
self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS)
|
|
||||||
self.cbDomain = UInt16Le(lambda:sizeof(self.domain) - 2)
|
|
||||||
self.cbUserName = UInt16Le(lambda:sizeof(self.userName) - 2)
|
|
||||||
self.cbPassword = UInt16Le(lambda:sizeof(self.password) - 2)
|
|
||||||
self.cbAlternateShell = UInt16Le(lambda:sizeof(self.alternateShell) - 2)
|
|
||||||
self.cbWorkingDir = UInt16Le(lambda:sizeof(self.workingDir) - 2)
|
|
||||||
#microsoft domain
|
|
||||||
self.domain = String(readLen = UInt16Le(lambda:self.cbDomain.value + 2), unicode = True)
|
|
||||||
self.userName = String(readLen = UInt16Le(lambda:self.cbUserName.value + 2), unicode = True)
|
|
||||||
self.password = String(readLen = UInt16Le(lambda:self.cbPassword.value + 2), unicode = True)
|
|
||||||
#shell execute at start of session
|
|
||||||
self.alternateShell = String(readLen = UInt16Le(lambda:self.cbAlternateShell.value + 2), unicode = True)
|
|
||||||
#working directory for session
|
|
||||||
self.workingDir = String(readLen = UInt16Le(lambda:self.cbWorkingDir.value + 2), unicode = True)
|
|
||||||
self.extendedInfo = RDPExtendedInfo(conditional = extendedInfoConditional)
|
|
||||||
|
|
||||||
class RDPExtendedInfo(CompositeType):
|
|
||||||
"""
|
|
||||||
Add more client informations
|
|
||||||
"""
|
|
||||||
def __init__(self, conditional):
|
|
||||||
CompositeType.__init__(self, conditional = conditional)
|
|
||||||
self.clientAddressFamily = UInt16Le(AfInet.AF_INET)
|
|
||||||
self.cbClientAddress = UInt16Le(lambda:sizeof(self.clientAddress))
|
|
||||||
self.clientAddress = String(readLen = self.cbClientAddress, unicode = True)
|
|
||||||
self.cbClientDir = UInt16Le(lambda:sizeof(self.clientDir))
|
|
||||||
self.clientDir = String(readLen = self.cbClientDir, unicode = True)
|
|
||||||
#TODO make tiomezone
|
|
||||||
self.clientTimeZone = String("\x00" * 172)
|
|
||||||
self.clientSessionId = UInt32Le()
|
|
||||||
self.performanceFlags = UInt32Le()
|
|
||||||
|
|
||||||
class ShareControlHeader(CompositeType):
|
class ShareControlHeader(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU share control header
|
@summary: PDU share control header
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, totalLength, pduType, userId):
|
def __init__(self, totalLength, pduType, userId):
|
||||||
"""
|
"""
|
||||||
Set pduType as constant
|
@summary: Set pduType as constant
|
||||||
@param totalLength: total length of PDU packet
|
@param totalLength: total length of PDU packet
|
||||||
"""
|
"""
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
#share control header
|
#share control header
|
||||||
self.totalLength = UInt16Le(totalLength)
|
self.totalLength = UInt16Le(totalLength)
|
||||||
self.pduType = UInt16Le(pduType)
|
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):
|
class ShareDataHeader(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU share data header
|
@summary: PDU share data header
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, size, pduType2 = 0, shareId = 0):
|
def __init__(self, size, pduType2 = 0, shareId = 0):
|
||||||
@@ -555,7 +457,7 @@ class ShareDataHeader(CompositeType):
|
|||||||
|
|
||||||
class PDU(CompositeType):
|
class PDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Main PDU message
|
@summary: Main PDU message
|
||||||
"""
|
"""
|
||||||
def __init__(self, userId = 0, pduMessage = None):
|
def __init__(self, userId = 0, pduMessage = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
@@ -563,14 +465,14 @@ class PDU(CompositeType):
|
|||||||
|
|
||||||
def PDUMessageFactory():
|
def PDUMessageFactory():
|
||||||
"""
|
"""
|
||||||
build message in accordance of type self.shareControlHeader.pduType.value
|
@summary: build message in accordance of type self.shareControlHeader.pduType.value
|
||||||
"""
|
"""
|
||||||
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
|
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
|
||||||
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
|
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
|
||||||
return c()
|
return c(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
|
||||||
log.debug("unknown PDU type : %s"%hex(self.shareControlHeader.pduType.value))
|
log.debug("unknown PDU type : %s"%hex(self.shareControlHeader.pduType.value))
|
||||||
#read entire packet
|
#read entire packet
|
||||||
return String()
|
return String(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
|
||||||
|
|
||||||
if pduMessage is None:
|
if pduMessage is None:
|
||||||
pduMessage = FactoryType(PDUMessageFactory)
|
pduMessage = FactoryType(PDUMessageFactory)
|
||||||
@@ -582,13 +484,13 @@ class PDU(CompositeType):
|
|||||||
class DemandActivePDU(CompositeType):
|
class DemandActivePDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240485.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240485.aspx
|
||||||
Main use for capabilities exchange server -> client
|
@summary: Main use for capabilities exchange server -> client
|
||||||
"""
|
"""
|
||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.shareId = UInt32Le()
|
self.shareId = UInt32Le()
|
||||||
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
||||||
self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets)))
|
self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets)))
|
||||||
@@ -601,13 +503,13 @@ class DemandActivePDU(CompositeType):
|
|||||||
class ConfirmActivePDU(CompositeType):
|
class ConfirmActivePDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240488.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240488.aspx
|
||||||
Main use for capabilities confirm client -> sever
|
@summary: Main use for capabilities confirm client -> sever
|
||||||
"""
|
"""
|
||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.shareId = UInt32Le()
|
self.shareId = UInt32Le()
|
||||||
self.originatorId = UInt16Le(0x03EA, constant = True)
|
self.originatorId = UInt16Le(0x03EA, constant = True)
|
||||||
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
||||||
@@ -619,38 +521,40 @@ class ConfirmActivePDU(CompositeType):
|
|||||||
|
|
||||||
class DeactiveAllPDU(CompositeType):
|
class DeactiveAllPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to signal already connected session
|
@summary: Use to signal already connected session
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240536.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240536.aspx
|
||||||
"""
|
"""
|
||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_DEACTIVATEALLPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_DEACTIVATEALLPDU
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
#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.shareId = UInt32Le()
|
||||||
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
||||||
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
|
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
|
||||||
|
|
||||||
class DataPDU(CompositeType):
|
class DataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Generic PDU packet use after connection sequence
|
@summary: Generic PDU packet use after connection sequence
|
||||||
"""
|
"""
|
||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
|
||||||
|
|
||||||
def __init__(self, pduData = None, shareId = 0):
|
def __init__(self, pduData = None, shareId = 0, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), lambda:self.pduData.__class__._PDUTYPE2_, shareId)
|
self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), lambda:self.pduData.__class__._PDUTYPE2_, shareId)
|
||||||
|
|
||||||
def PDUDataFactory():
|
def PDUDataFactory():
|
||||||
"""
|
"""
|
||||||
Create object in accordance self.shareDataHeader.pduType2 value
|
@summary: Create object in accordance self.shareDataHeader.pduType2 value
|
||||||
"""
|
"""
|
||||||
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU]:
|
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU, SaveSessionInfoPDU]:
|
||||||
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
|
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
|
||||||
return c()
|
return c(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
|
||||||
log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value))
|
log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value))
|
||||||
return String()
|
return String(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
|
||||||
|
|
||||||
if pduData is None:
|
if pduData is None:
|
||||||
pduData = FactoryType(PDUDataFactory)
|
pduData = FactoryType(PDUDataFactory)
|
||||||
@@ -691,7 +595,7 @@ class ControlDataPDU(CompositeType):
|
|||||||
|
|
||||||
class ErrorInfoDataPDU(CompositeType):
|
class ErrorInfoDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to inform error in PDU layer
|
@summary: Use to inform error in PDU layer
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU
|
||||||
@@ -707,7 +611,7 @@ class ErrorInfoDataPDU(CompositeType):
|
|||||||
|
|
||||||
class FontListDataPDU(CompositeType):
|
class FontListDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to indicate list of font. Deprecated packet
|
@summary: Use to indicate list of font. Deprecated packet
|
||||||
client -> server
|
client -> server
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
|
||||||
"""
|
"""
|
||||||
@@ -725,7 +629,7 @@ class FontListDataPDU(CompositeType):
|
|||||||
|
|
||||||
class FontMapDataPDU(CompositeType):
|
class FontMapDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to indicate map of font. Deprecated packet (maybe the same as FontListDataPDU)
|
@summary: Use to indicate map of font. Deprecated packet (maybe the same as FontListDataPDU)
|
||||||
server -> client
|
server -> client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
|
||||||
"""
|
"""
|
||||||
@@ -743,7 +647,7 @@ class FontMapDataPDU(CompositeType):
|
|||||||
|
|
||||||
class PersistentListEntry(CompositeType):
|
class PersistentListEntry(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to record persistent key in PersistentListPDU
|
@summary: Use to record persistent key in PersistentListPDU
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240496.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240496.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -753,14 +657,14 @@ class PersistentListEntry(CompositeType):
|
|||||||
|
|
||||||
class PersistentListPDU(CompositeType):
|
class PersistentListPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Use to indicate that bitmap cache was already
|
@summary: Use to indicate that bitmap cache was already
|
||||||
Fill with some keys from previous session
|
Fill with some keys from previous session
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST
|
||||||
|
|
||||||
def __init__(self, userId = 0, shareId = 0):
|
def __init__(self, userId = 0, shareId = 0, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.numEntriesCache0 = UInt16Le()
|
self.numEntriesCache0 = UInt16Le()
|
||||||
self.numEntriesCache1 = UInt16Le()
|
self.numEntriesCache1 = UInt16Le()
|
||||||
self.numEntriesCache2 = UInt16Le()
|
self.numEntriesCache2 = UInt16Le()
|
||||||
@@ -774,38 +678,38 @@ class PersistentListPDU(CompositeType):
|
|||||||
self.bitMask = UInt8()
|
self.bitMask = UInt8()
|
||||||
self.pad2 = UInt8()
|
self.pad2 = UInt8()
|
||||||
self.pad3 = UInt16Le()
|
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):
|
class ClientInputEventPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU use to send client inputs in slow path mode
|
@summary: PDU use to send client inputs in slow path mode
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc746160.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc746160.aspx
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.numEvents = UInt16Le(lambda:len(self.slowPathInputEvents._array))
|
self.numEvents = UInt16Le(lambda:len(self.slowPathInputEvents._array))
|
||||||
self.pad2Octets = UInt16Le()
|
self.pad2Octets = UInt16Le()
|
||||||
self.slowPathInputEvents = ArrayType(SlowPathInputEvent, readLen = self.numEvents)
|
self.slowPathInputEvents = ArrayType(SlowPathInputEvent, readLen = self.numEvents)
|
||||||
|
|
||||||
class ShutdownRequestPDU(CompositeType):
|
class ShutdownRequestPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU use to signal that the session will be closed
|
@summary: PDU use to signal that the session will be closed
|
||||||
client -> server
|
client -> server
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
|
||||||
class ShutdownDeniedPDU(CompositeType):
|
class ShutdownDeniedPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU use to signal which the session will be closed is connected
|
@summary: PDU use to signal which the session will be closed is connected
|
||||||
server -> client
|
server -> client
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
|
||||||
class InclusiveRectangle(CompositeType):
|
class InclusiveRectangle(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -844,7 +748,7 @@ class RefreshRectPDU(CompositeType):
|
|||||||
|
|
||||||
class UpdateDataPDU(CompositeType):
|
class UpdateDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Update data PDU use by server to inform update image or palet
|
@summary: Update data PDU use by server to inform update image or palet
|
||||||
for example
|
for example
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
|
||||||
"""
|
"""
|
||||||
@@ -861,13 +765,13 @@ class UpdateDataPDU(CompositeType):
|
|||||||
|
|
||||||
def UpdateDataFactory():
|
def UpdateDataFactory():
|
||||||
"""
|
"""
|
||||||
Create object in accordance self.updateType value
|
@summary: Create object in accordance self.updateType value
|
||||||
"""
|
"""
|
||||||
for c in [BitmapUpdateDataPDU]:
|
for c in [BitmapUpdateDataPDU]:
|
||||||
if self.updateType.value == c._UPDATE_TYPE_:
|
if self.updateType.value == c._UPDATE_TYPE_:
|
||||||
return c()
|
return c(readLen = CallableValue(readLen.value - 2))
|
||||||
log.debug("unknown PDU update data type : %s"%hex(self.updateType.value))
|
log.debug("unknown PDU update data type : %s"%hex(self.updateType.value))
|
||||||
return String()
|
return String(readLen = CallableValue(readLen.value - 2))
|
||||||
|
|
||||||
if updateData is None:
|
if updateData is None:
|
||||||
updateData = FactoryType(UpdateDataFactory, conditional = lambda:(self.updateType.value != UpdateType.UPDATETYPE_SYNCHRONIZE))
|
updateData = FactoryType(UpdateDataFactory, conditional = lambda:(self.updateType.value != UpdateType.UPDATETYPE_SYNCHRONIZE))
|
||||||
@@ -875,10 +779,22 @@ class UpdateDataPDU(CompositeType):
|
|||||||
raise InvalidExpectedDataException("Try to send an invalid data update PDU")
|
raise InvalidExpectedDataException("Try to send an invalid data update PDU")
|
||||||
|
|
||||||
self.updateData = updateData
|
self.updateData = updateData
|
||||||
|
|
||||||
|
class SaveSessionInfoPDU(CompositeType):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240636.aspx
|
||||||
|
"""
|
||||||
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_SAVE_SESSION_INFO
|
||||||
|
|
||||||
|
def __init__(self, readLen = None):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
self.infoType = UInt32Le()
|
||||||
|
#TODO parse info data
|
||||||
|
self.infoData = String()
|
||||||
|
|
||||||
class FastPathUpdatePDU(CompositeType):
|
class FastPathUpdatePDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Fast path update PDU packet
|
@summary: Fast path update PDU packet
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, updateData = None):
|
def __init__(self, updateData = None):
|
||||||
@@ -889,13 +805,13 @@ class FastPathUpdatePDU(CompositeType):
|
|||||||
|
|
||||||
def UpdateDataFactory():
|
def UpdateDataFactory():
|
||||||
"""
|
"""
|
||||||
Create correct object in accordance to self.updateHeader field
|
@summary: Create correct object in accordance to self.updateHeader field
|
||||||
"""
|
"""
|
||||||
for c in [FastPathBitmapUpdateDataPDU]:
|
for c in [FastPathBitmapUpdateDataPDU]:
|
||||||
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
|
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
|
||||||
return c()
|
return c(readLen = self.size)
|
||||||
log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf))
|
log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf))
|
||||||
return String()
|
return String(readLen = self.size)
|
||||||
|
|
||||||
if updateData is None:
|
if updateData is None:
|
||||||
updateData = FactoryType(UpdateDataFactory)
|
updateData = FactoryType(UpdateDataFactory)
|
||||||
@@ -906,7 +822,7 @@ class FastPathUpdatePDU(CompositeType):
|
|||||||
|
|
||||||
class BitmapUpdateDataPDU(CompositeType):
|
class BitmapUpdateDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU use to send raw bitmap compressed or not
|
@summary: PDU use to send raw bitmap compressed or not
|
||||||
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
|
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
|
||||||
"""
|
"""
|
||||||
_UPDATE_TYPE_ = UpdateType.UPDATETYPE_BITMAP
|
_UPDATE_TYPE_ = UpdateType.UPDATETYPE_BITMAP
|
||||||
@@ -921,20 +837,20 @@ class BitmapUpdateDataPDU(CompositeType):
|
|||||||
|
|
||||||
class OrderUpdateDataPDU(CompositeType):
|
class OrderUpdateDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU type use to communicate Accelerated order (GDI)
|
@summary: PDU type use to communicate Accelerated order (GDI)
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
|
||||||
@todo: not implemented yet but need it
|
@todo: not implemented yet but need it
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.pad2OctetsA = UInt16Le()
|
self.pad2OctetsA = UInt16Le()
|
||||||
self.numberOrders = UInt16Le(lambda:len(self.orderData._array))
|
self.numberOrders = UInt16Le(lambda:len(self.orderData._array))
|
||||||
self.pad2OctetsB = UInt16Le()
|
self.pad2OctetsB = UInt16Le()
|
||||||
self.orderData = ArrayType(order.DrawingOrder, readLen = self.numberOrders)
|
self.orderData = ArrayType(order.PrimaryDrawingOrder, readLen = self.numberOrders)
|
||||||
|
|
||||||
class BitmapCompressedDataHeader(CompositeType):
|
class BitmapCompressedDataHeader(CompositeType):
|
||||||
"""
|
"""
|
||||||
Compressed header of bitmap
|
@summary: Compressed header of bitmap
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240644.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240644.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, bodySize = 0, scanWidth = 0, uncompressedSize = 0, conditional = lambda:True):
|
def __init__(self, bodySize = 0, scanWidth = 0, uncompressedSize = 0, conditional = lambda:True):
|
||||||
@@ -953,7 +869,7 @@ class BitmapCompressedDataHeader(CompositeType):
|
|||||||
|
|
||||||
class BitmapData(CompositeType):
|
class BitmapData(CompositeType):
|
||||||
"""
|
"""
|
||||||
Bitmap data here the screen capture
|
@summary: Bitmap data here the screen capture
|
||||||
"""
|
"""
|
||||||
def __init__(self, destLeft = 0, destTop = 0, destRight = 0, destBottom = 0, width = 0, height = 0, bitsPerPixel = 0, bitmapDataStream = ""):
|
def __init__(self, destLeft = 0, destTop = 0, destRight = 0, destBottom = 0, width = 0, height = 0, bitsPerPixel = 0, bitmapDataStream = ""):
|
||||||
"""
|
"""
|
||||||
@@ -977,24 +893,24 @@ class BitmapData(CompositeType):
|
|||||||
self.flags = UInt16Le()
|
self.flags = UInt16Le()
|
||||||
self.bitmapLength = UInt16Le(lambda:(sizeof(self.bitmapComprHdr) + sizeof(self.bitmapDataStream)))
|
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.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 = String(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):
|
class FastPathBitmapUpdateDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
Fast path version of bitmap update PDU
|
@summary: Fast path version of bitmap update PDU
|
||||||
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
|
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
|
||||||
"""
|
"""
|
||||||
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
|
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.header = UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True)
|
self.header = UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True)
|
||||||
self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array))
|
self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array))
|
||||||
self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles)
|
self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles)
|
||||||
|
|
||||||
class SlowPathInputEvent(CompositeType):
|
class SlowPathInputEvent(CompositeType):
|
||||||
"""
|
"""
|
||||||
PDU use in slow-path sending client inputs
|
@summary: PDU use in slow-path sending client inputs
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, messageData = None):
|
def __init__(self, messageData = None):
|
||||||
@@ -1003,11 +919,10 @@ class SlowPathInputEvent(CompositeType):
|
|||||||
self.messageType = UInt16Le(lambda:self.slowPathInputData.__class__._INPUT_MESSAGE_TYPE_)
|
self.messageType = UInt16Le(lambda:self.slowPathInputData.__class__._INPUT_MESSAGE_TYPE_)
|
||||||
|
|
||||||
def SlowPathInputDataFactory():
|
def SlowPathInputDataFactory():
|
||||||
for c in [PointerEvent, ScancodeKeyEvent, UnicodeKeyEvent]:
|
for c in [PointerEvent, ScancodeKeyEvent, UnicodeKeyEvent, SynchronizeEvent]:
|
||||||
if self.messageType.value == c._INPUT_MESSAGE_TYPE_:
|
if self.messageType.value == c._INPUT_MESSAGE_TYPE_:
|
||||||
return c()
|
return c()
|
||||||
log.debug("unknown slow path input : %s"%hex(self.messageType.value))
|
raise InvalidExpectedDataException("unknown slow path input : %s"%hex(self.messageType.value))
|
||||||
return String()
|
|
||||||
|
|
||||||
if messageData is None:
|
if messageData is None:
|
||||||
messageData = FactoryType(SlowPathInputDataFactory)
|
messageData = FactoryType(SlowPathInputDataFactory)
|
||||||
@@ -1015,10 +930,22 @@ class SlowPathInputEvent(CompositeType):
|
|||||||
raise InvalidExpectedDataException("try to send an invalid Slow Path Input Event")
|
raise InvalidExpectedDataException("try to send an invalid Slow Path Input Event")
|
||||||
|
|
||||||
self.slowPathInputData = messageData
|
self.slowPathInputData = messageData
|
||||||
|
|
||||||
|
class SynchronizeEvent(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Synchronize keyboard
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240588.aspx
|
||||||
|
"""
|
||||||
|
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SYNC
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.pad2Octets = UInt16Le()
|
||||||
|
self.toggleFlags = UInt32Le()
|
||||||
|
|
||||||
class PointerEvent(CompositeType):
|
class PointerEvent(CompositeType):
|
||||||
"""
|
"""
|
||||||
Event use to communicate mouse position
|
@summary: Event use to communicate mouse position
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
|
||||||
"""
|
"""
|
||||||
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_MOUSE
|
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_MOUSE
|
||||||
@@ -1031,7 +958,7 @@ class PointerEvent(CompositeType):
|
|||||||
|
|
||||||
class ScancodeKeyEvent(CompositeType):
|
class ScancodeKeyEvent(CompositeType):
|
||||||
"""
|
"""
|
||||||
Event use to communicate keyboard informations
|
@summary: Event use to communicate keyboard informations
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
|
||||||
"""
|
"""
|
||||||
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SCANCODE
|
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SCANCODE
|
||||||
@@ -1044,7 +971,7 @@ class ScancodeKeyEvent(CompositeType):
|
|||||||
|
|
||||||
class UnicodeKeyEvent(CompositeType):
|
class UnicodeKeyEvent(CompositeType):
|
||||||
"""
|
"""
|
||||||
Event use to communicate keyboard informations
|
@summary: Event use to communicate keyboard informations
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240585.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240585.aspx
|
||||||
"""
|
"""
|
||||||
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_UNICODE
|
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_UNICODE
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -23,27 +23,33 @@ Implement the main graphic layer
|
|||||||
In this layer are managed all mains bitmap update orders end user inputs
|
In this layer are managed all mains bitmap update orders end user inputs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.network.layer import LayerAutomata
|
from rdpy.core.layer import LayerAutomata
|
||||||
from rdpy.base.error import InvalidExpectedDataException, CallPureVirtualFuntion
|
from rdpy.core.error import CallPureVirtualFuntion
|
||||||
from rdpy.network.type import UInt16Le
|
from rdpy.core.type import ArrayType
|
||||||
import rdpy.base.log as log
|
import rdpy.core.log as log
|
||||||
import rdpy.protocol.rdp.gcc as gcc
|
|
||||||
import rdpy.protocol.rdp.tpkt as tpkt
|
import rdpy.protocol.rdp.tpkt as tpkt
|
||||||
import lic, data, caps
|
import data, caps
|
||||||
|
|
||||||
class PDUClientListener(object):
|
class PDUClientListener(object):
|
||||||
"""
|
"""
|
||||||
Interface for PDU client automata listener
|
@summary: Interface for PDU client automata listener
|
||||||
"""
|
"""
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
Event call when PDU layer is ready to send events
|
@summary: Event call when PDU layer is ready to send events
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener"))
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Event call when Windows session is ready
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "PDUClientListener"))
|
||||||
|
|
||||||
|
|
||||||
def onUpdate(self, rectangles):
|
def onUpdate(self, rectangles):
|
||||||
"""
|
"""
|
||||||
call when a bitmap data is received from update PDU
|
@summary: call when a bitmap data is received from update PDU
|
||||||
@param rectangles: [pdu.BitmapData] struct
|
@param rectangles: [pdu.BitmapData] struct
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onUpdate", "PDUClientListener"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onUpdate", "PDUClientListener"))
|
||||||
@@ -56,37 +62,34 @@ class PDUClientListener(object):
|
|||||||
|
|
||||||
class PDUServerListener(object):
|
class PDUServerListener(object):
|
||||||
"""
|
"""
|
||||||
Interface for PDU server automata listener
|
@summary: Interface for PDU server automata listener
|
||||||
"""
|
"""
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
Event call when PDU layer is ready to send update
|
@summary: Event call when PDU layer is ready to send update
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUServerListener"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUServerListener"))
|
||||||
|
|
||||||
def onSlowPathInput(self, slowPathInputEvents):
|
def onSlowPathInput(self, slowPathInputEvents):
|
||||||
"""
|
"""
|
||||||
Event call when slow path input are available
|
@summary: Event call when slow path input are available
|
||||||
@param slowPathInputEvents: [data.SlowPathInputEvent]
|
@param slowPathInputEvents: [data.SlowPathInputEvent]
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSlowPathInput", "PDUServerListener"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSlowPathInput", "PDUServerListener"))
|
||||||
|
|
||||||
class PDULayer(LayerAutomata):
|
class PDULayer(LayerAutomata, tpkt.IFastPathListener):
|
||||||
"""
|
"""
|
||||||
Global channel for MCS that handle session
|
@summary: Global channel for MCS that handle session
|
||||||
identification user, licensing management, and capabilities exchange
|
identification user, licensing management, and capabilities exchange
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
LayerAutomata.__init__(self, None)
|
LayerAutomata.__init__(self, None)
|
||||||
|
|
||||||
#logon info send from client to server
|
|
||||||
self._info = data.RDPInfo(extendedInfoConditional = lambda:(self._transport.getGCCServerSettings().getBlock(gcc.MessageType.SC_CORE).rdpVersion.value == gcc.Version.RDP_VERSION_5_PLUS))
|
|
||||||
#server capabilities
|
#server capabilities
|
||||||
self._serverCapabilities = {
|
self._serverCapabilities = {
|
||||||
caps.CapsType.CAPSTYPE_GENERAL : caps.Capability(caps.GeneralCapability()),
|
caps.CapsType.CAPSTYPE_GENERAL : caps.Capability(caps.GeneralCapability()),
|
||||||
caps.CapsType.CAPSTYPE_BITMAP : caps.Capability(caps.BitmapCapability()),
|
caps.CapsType.CAPSTYPE_BITMAP : caps.Capability(caps.BitmapCapability()),
|
||||||
caps.CapsType.CAPSTYPE_ORDER : caps.Capability(caps.OrderCapability()),
|
caps.CapsType.CAPSTYPE_ORDER : caps.Capability(caps.OrderCapability()),
|
||||||
caps.CapsType.CAPSTYPE_POINTER : caps.Capability(caps.PointerCapability()),
|
caps.CapsType.CAPSTYPE_POINTER : caps.Capability(caps.PointerCapability(isServer = True)),
|
||||||
caps.CapsType.CAPSTYPE_INPUT : caps.Capability(caps.InputCapability()),
|
caps.CapsType.CAPSTYPE_INPUT : caps.Capability(caps.InputCapability()),
|
||||||
caps.CapsType.CAPSTYPE_VIRTUALCHANNEL : caps.Capability(caps.VirtualChannelCapability()),
|
caps.CapsType.CAPSTYPE_VIRTUALCHANNEL : caps.Capability(caps.VirtualChannelCapability()),
|
||||||
caps.CapsType.CAPSTYPE_FONT : caps.Capability(caps.FontCapability()),
|
caps.CapsType.CAPSTYPE_FONT : caps.Capability(caps.FontCapability()),
|
||||||
@@ -105,28 +108,38 @@ class PDULayer(LayerAutomata):
|
|||||||
caps.CapsType.CAPSTYPE_GLYPHCACHE : caps.Capability(caps.GlyphCapability()),
|
caps.CapsType.CAPSTYPE_GLYPHCACHE : caps.Capability(caps.GlyphCapability()),
|
||||||
caps.CapsType.CAPSTYPE_OFFSCREENCACHE : caps.Capability(caps.OffscreenBitmapCacheCapability()),
|
caps.CapsType.CAPSTYPE_OFFSCREENCACHE : caps.Capability(caps.OffscreenBitmapCacheCapability()),
|
||||||
caps.CapsType.CAPSTYPE_VIRTUALCHANNEL : caps.Capability(caps.VirtualChannelCapability()),
|
caps.CapsType.CAPSTYPE_VIRTUALCHANNEL : caps.Capability(caps.VirtualChannelCapability()),
|
||||||
caps.CapsType.CAPSTYPE_SOUND : caps.Capability(caps.SoundCapability())
|
caps.CapsType.CAPSTYPE_SOUND : caps.Capability(caps.SoundCapability()),
|
||||||
|
caps.CapsType.CAPSETTYPE_MULTIFRAGMENTUPDATE : caps.Capability(caps.MultiFragmentUpdate())
|
||||||
}
|
}
|
||||||
#share id between client and server
|
#share id between client and server
|
||||||
self._shareId = 0x103EA
|
self._shareId = 0x103EA
|
||||||
|
#enable or not fast path
|
||||||
|
self._fastPathSender = None
|
||||||
|
|
||||||
|
def setFastPathSender(self, fastPathSender):
|
||||||
|
"""
|
||||||
|
@param fastPathSender: {tpkt.FastPathSender}
|
||||||
|
@note: implement tpkt.IFastPathListener
|
||||||
|
"""
|
||||||
|
self._fastPathSender = fastPathSender
|
||||||
|
|
||||||
def sendPDU(self, pduMessage):
|
def sendPDU(self, pduMessage):
|
||||||
"""
|
"""
|
||||||
Send a PDU data to transport layer
|
@summary: Send a PDU data to transport layer
|
||||||
@param pduMessage: PDU message
|
@param pduMessage: PDU message
|
||||||
"""
|
"""
|
||||||
self._transport.send(data.PDU(self._transport.getUserId(), pduMessage))
|
self._transport.send(data.PDU(self._transport.getUserId(), pduMessage))
|
||||||
|
|
||||||
def sendDataPDU(self, pduData):
|
def sendDataPDU(self, pduData):
|
||||||
"""
|
"""
|
||||||
Send an PDUData to transport layer
|
@summary: Send an PDUData to transport layer
|
||||||
@param pduData: PDU data message
|
@param pduData: PDU data message
|
||||||
"""
|
"""
|
||||||
self.sendPDU(data.DataPDU(pduData, self._shareId))
|
self.sendPDU(data.DataPDU(pduData, self._shareId))
|
||||||
|
|
||||||
class Client(PDULayer, tpkt.IFastPathListener):
|
class Client(PDULayer):
|
||||||
"""
|
"""
|
||||||
Client automata of PDU layer
|
@summary: Client automata of PDU layer
|
||||||
"""
|
"""
|
||||||
def __init__(self, listener):
|
def __init__(self, listener):
|
||||||
"""
|
"""
|
||||||
@@ -134,64 +147,26 @@ class Client(PDULayer, tpkt.IFastPathListener):
|
|||||||
"""
|
"""
|
||||||
PDULayer.__init__(self)
|
PDULayer.__init__(self)
|
||||||
self._listener = listener
|
self._listener = listener
|
||||||
#enable or not fast path
|
|
||||||
self._fastPathSender = None
|
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""
|
"""
|
||||||
Connect message in client automata
|
@summary: Connect message in client automata
|
||||||
Send INfo packet (credentials)
|
|
||||||
Wait License info
|
|
||||||
"""
|
"""
|
||||||
self.sendInfoPkt()
|
self._gccCore = self._transport.getGCCClientSettings().CS_CORE
|
||||||
#next state is license info PDU
|
self.setNextState(self.recvDemandActivePDU)
|
||||||
self.setNextState(self.recvLicenceInfo)
|
|
||||||
#check if client support fast path message
|
#check if client support fast path message
|
||||||
self._clientFastPathSupported = False
|
self._clientFastPathSupported = False
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Send PDU close packet and call close method on transport method
|
@summary: Send PDU close packet and call close method on transport method
|
||||||
"""
|
"""
|
||||||
self._transport.close()
|
self._transport.close()
|
||||||
#self.sendDataPDU(data.ShutdownRequestPDU())
|
#self.sendDataPDU(data.ShutdownRequestPDU())
|
||||||
|
|
||||||
def setFastPathSender(self, fastPathSender):
|
|
||||||
"""
|
|
||||||
@param fastPathSender: tpkt.FastPathSender
|
|
||||||
@note: implement tpkt.IFastPathListener
|
|
||||||
"""
|
|
||||||
self._fastPathSender = fastPathSender
|
|
||||||
|
|
||||||
def recvLicenceInfo(self, s):
|
|
||||||
"""
|
|
||||||
Read license info packet and check if is a valid client info
|
|
||||||
Wait Demand Active PDU
|
|
||||||
@param s: Stream
|
|
||||||
"""
|
|
||||||
#packet preambule
|
|
||||||
securityFlag = UInt16Le()
|
|
||||||
securityFlagHi = UInt16Le()
|
|
||||||
s.readType((securityFlag, securityFlagHi))
|
|
||||||
|
|
||||||
if not (securityFlag.value & data.SecurityFlag.SEC_LICENSE_PKT):
|
|
||||||
raise InvalidExpectedDataException("Waiting license packet")
|
|
||||||
|
|
||||||
validClientPdu = lic.LicPacket()
|
|
||||||
s.readType(validClientPdu)
|
|
||||||
|
|
||||||
if validClientPdu.bMsgtype.value == lic.MessageType.ERROR_ALERT and validClientPdu.licensingMessage.dwErrorCode.value == lic.ErrorCode.STATUS_VALID_CLIENT and validClientPdu.licensingMessage.dwStateTransition.value == lic.StateTransition.ST_NO_TRANSITION:
|
|
||||||
self.setNextState(self.recvDemandActivePDU)
|
|
||||||
#not tested because i can't buy RDP license server
|
|
||||||
elif validClientPdu.bMsgtype.value == lic.MessageType.LICENSE_REQUEST:
|
|
||||||
newLicenseReq = lic.createNewLicenseRequest(validClientPdu.licensingMessage)
|
|
||||||
self._transport.send((UInt16Le(data.SecurityFlag.SEC_LICENSE_PKT), UInt16Le(), newLicenseReq))
|
|
||||||
else:
|
|
||||||
raise InvalidExpectedDataException("Not a valid license packet")
|
|
||||||
|
|
||||||
def recvDemandActivePDU(self, s):
|
def recvDemandActivePDU(self, s):
|
||||||
"""
|
"""
|
||||||
Receive demand active PDU which contains
|
@summary: Receive demand active PDU which contains
|
||||||
Server capabilities. In this version of RDPY only
|
Server capabilities. In this version of RDPY only
|
||||||
Restricted group of capabilities are used.
|
Restricted group of capabilities are used.
|
||||||
Send Confirm Active PDU
|
Send Confirm Active PDU
|
||||||
@@ -212,6 +187,9 @@ class Client(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
for cap in pdu.pduMessage.capabilitySets._array:
|
for cap in pdu.pduMessage.capabilitySets._array:
|
||||||
self._serverCapabilities[cap.capabilitySetType] = cap
|
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()
|
self.sendConfirmActivePDU()
|
||||||
#send synchronize
|
#send synchronize
|
||||||
@@ -220,7 +198,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def recvServerSynchronizePDU(self, s):
|
def recvServerSynchronizePDU(self, s):
|
||||||
"""
|
"""
|
||||||
Receive from server
|
@summary: Receive from server
|
||||||
Wait Control Cooperate PDU
|
Wait Control Cooperate PDU
|
||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
@@ -236,7 +214,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def recvServerControlCooperatePDU(self, s):
|
def recvServerControlCooperatePDU(self, s):
|
||||||
"""
|
"""
|
||||||
Receive control cooperate PDU from server
|
@summary: Receive control cooperate PDU from server
|
||||||
Wait Control Granted PDU
|
Wait Control Granted PDU
|
||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
@@ -252,7 +230,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def recvServerControlGrantedPDU(self, s):
|
def recvServerControlGrantedPDU(self, s):
|
||||||
"""
|
"""
|
||||||
Receive last control PDU the granted control PDU
|
@summary: Receive last control PDU the granted control PDU
|
||||||
Wait Font map PDU
|
Wait Font map PDU
|
||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
@@ -268,7 +246,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def recvServerFontMapPDU(self, s):
|
def recvServerFontMapPDU(self, s):
|
||||||
"""
|
"""
|
||||||
Last useless connection packet from server to client
|
@summary: Last useless connection packet from server to client
|
||||||
Wait any PDU
|
Wait any PDU
|
||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
@@ -286,80 +264,82 @@ class Client(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def recvPDU(self, s):
|
def recvPDU(self, s):
|
||||||
"""
|
"""
|
||||||
Main receive function after connection sequence
|
@summary: Main receive function after connection sequence
|
||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdus = ArrayType(data.PDU)
|
||||||
s.readType(pdu)
|
s.readType(pdus)
|
||||||
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
|
for pdu in pdus:
|
||||||
self.readDataPDU(pdu.pduMessage)
|
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
|
||||||
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
|
self.readDataPDU(pdu.pduMessage)
|
||||||
#use in deactivation-reactivation sequence
|
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
|
||||||
#next state is either a capabilities re exchange or disconnection
|
#use in deactivation-reactivation sequence
|
||||||
#http://msdn.microsoft.com/en-us/library/cc240454.aspx
|
#next state is either a capabilities re exchange or disconnection
|
||||||
self.setNextState(self.recvDemandActivePDU)
|
#http://msdn.microsoft.com/en-us/library/cc240454.aspx
|
||||||
|
self.setNextState(self.recvDemandActivePDU)
|
||||||
|
|
||||||
def recvFastPath(self, fastPathS):
|
def recvFastPath(self, secFlag, fastPathS):
|
||||||
"""
|
"""
|
||||||
Implement IFastPathListener interface
|
@summary: Implement IFastPathListener interface
|
||||||
Fast path is needed by RDP 8.0
|
Fast path is needed by RDP 8.0
|
||||||
@param fastPathS: Stream that contain fast path data
|
@param fastPathS: {Stream} that contain fast path data
|
||||||
|
@param secFlag: {SecFlags}
|
||||||
"""
|
"""
|
||||||
fastPathPDU = data.FastPathUpdatePDU()
|
updates = ArrayType(data.FastPathUpdatePDU)
|
||||||
fastPathS.readType(fastPathPDU)
|
fastPathS.readType(updates)
|
||||||
if fastPathPDU.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
|
for update in updates:
|
||||||
self._listener.onUpdate(fastPathPDU.updateData.rectangles._array)
|
if update.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
|
||||||
|
self._listener.onUpdate(update.updateData.rectangles._array)
|
||||||
|
|
||||||
def readDataPDU(self, dataPDU):
|
def readDataPDU(self, dataPDU):
|
||||||
"""
|
"""
|
||||||
read a data PDU object
|
@summary: read a data PDU object
|
||||||
@param dataPDU: DataPDU object
|
@param dataPDU: DataPDU object
|
||||||
"""
|
"""
|
||||||
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
|
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
|
||||||
|
#ignore 0 error code because is not an error code
|
||||||
|
if dataPDU.pduData.errorInfo.value == 0:
|
||||||
|
return
|
||||||
errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value)
|
errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value)
|
||||||
if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo):
|
if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo):
|
||||||
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
|
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
|
||||||
|
|
||||||
log.error("INFO PDU : %s"%errorMessage)
|
log.error("INFO PDU : %s"%errorMessage)
|
||||||
|
|
||||||
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_DENIED:
|
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_DENIED:
|
||||||
#may be an event to ask to user
|
#may be an event to ask to user
|
||||||
self._transport.close()
|
self._transport.close()
|
||||||
|
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SAVE_SESSION_INFO:
|
||||||
|
#handle session event
|
||||||
|
self._listener.onSessionReady()
|
||||||
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_UPDATE:
|
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_UPDATE:
|
||||||
self.readUpdateDataPDU(dataPDU.pduData)
|
self.readUpdateDataPDU(dataPDU.pduData)
|
||||||
|
|
||||||
def readUpdateDataPDU(self, updateDataPDU):
|
def readUpdateDataPDU(self, updateDataPDU):
|
||||||
"""
|
"""
|
||||||
Read an update data PDU data
|
@summary: Read an update data PDU data
|
||||||
dispatch update data
|
dispatch update data
|
||||||
@param: UpdateDataPDU object
|
@param: {UpdateDataPDU} object
|
||||||
"""
|
"""
|
||||||
if updateDataPDU.updateType.value == data.UpdateType.UPDATETYPE_BITMAP:
|
if updateDataPDU.updateType.value == data.UpdateType.UPDATETYPE_BITMAP:
|
||||||
self._listener.onUpdate(updateDataPDU.updateData.rectangles._array)
|
self._listener.onUpdate(updateDataPDU.updateData.rectangles._array)
|
||||||
|
|
||||||
def sendInfoPkt(self):
|
|
||||||
"""
|
|
||||||
Send a logon info packet
|
|
||||||
client automata data
|
|
||||||
"""
|
|
||||||
self._transport.send((UInt16Le(data.SecurityFlag.SEC_INFO_PKT), UInt16Le(), self._info))
|
|
||||||
|
|
||||||
def sendConfirmActivePDU(self):
|
def sendConfirmActivePDU(self):
|
||||||
"""
|
"""
|
||||||
Send all client capabilities
|
@summary: Send all client capabilities
|
||||||
"""
|
"""
|
||||||
#init general capability
|
#init general capability
|
||||||
generalCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability
|
generalCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability
|
||||||
generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS
|
generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS
|
||||||
generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT
|
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:
|
if not self._fastPathSender is None:
|
||||||
generalCapability.extraFlags.value |= caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED
|
generalCapability.extraFlags.value |= caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED
|
||||||
|
|
||||||
#init bitmap capability
|
#init bitmap capability
|
||||||
bitmapCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_BITMAP].capability
|
bitmapCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_BITMAP].capability
|
||||||
bitmapCapability.preferredBitsPerPixel = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).highColorDepth
|
bitmapCapability.preferredBitsPerPixel = self._gccCore.highColorDepth
|
||||||
bitmapCapability.desktopWidth = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).desktopWidth
|
bitmapCapability.desktopWidth = self._gccCore.desktopWidth
|
||||||
bitmapCapability.desktopHeight = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).desktopHeight
|
bitmapCapability.desktopHeight = self._gccCore.desktopHeight
|
||||||
|
|
||||||
#init order capability
|
#init order capability
|
||||||
orderCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_ORDER].capability
|
orderCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_ORDER].capability
|
||||||
@@ -368,11 +348,11 @@ class Client(PDULayer, tpkt.IFastPathListener):
|
|||||||
#init input capability
|
#init input capability
|
||||||
inputCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability
|
inputCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability
|
||||||
inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX | caps.InputFlags.INPUT_FLAG_UNICODE
|
inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX | caps.InputFlags.INPUT_FLAG_UNICODE
|
||||||
inputCapability.keyboardLayout = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).kbdLayout
|
inputCapability.keyboardLayout = self._gccCore.kbdLayout
|
||||||
inputCapability.keyboardType = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).keyboardType
|
inputCapability.keyboardType = self._gccCore.keyboardType
|
||||||
inputCapability.keyboardSubType = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).keyboardSubType
|
inputCapability.keyboardSubType = self._gccCore.keyboardSubType
|
||||||
inputCapability.keyboardrFunctionKey = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).keyboardFnKeys
|
inputCapability.keyboardrFunctionKey = self._gccCore.keyboardFnKeys
|
||||||
inputCapability.imeFileName = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).imeFileName
|
inputCapability.imeFileName = self._gccCore.imeFileName
|
||||||
|
|
||||||
#make active PDU packet
|
#make active PDU packet
|
||||||
confirmActivePDU = data.ConfirmActivePDU()
|
confirmActivePDU = data.ConfirmActivePDU()
|
||||||
@@ -382,7 +362,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def sendClientFinalizeSynchronizePDU(self):
|
def sendClientFinalizeSynchronizePDU(self):
|
||||||
"""
|
"""
|
||||||
send a synchronize PDU from client to server
|
@summary: send a synchronize PDU from client to server
|
||||||
"""
|
"""
|
||||||
synchronizePDU = data.SynchronizeDataPDU(self._transport.getChannelId())
|
synchronizePDU = data.SynchronizeDataPDU(self._transport.getChannelId())
|
||||||
self.sendDataPDU(synchronizePDU)
|
self.sendDataPDU(synchronizePDU)
|
||||||
@@ -403,16 +383,16 @@ class Client(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def sendInputEvents(self, pointerEvents):
|
def sendInputEvents(self, pointerEvents):
|
||||||
"""
|
"""
|
||||||
send client input events
|
@summary: send client input events
|
||||||
@param pointerEvents: list of pointer events
|
@param pointerEvents: list of pointer events
|
||||||
"""
|
"""
|
||||||
pdu = data.ClientInputEventPDU()
|
pdu = data.ClientInputEventPDU()
|
||||||
pdu.slowPathInputEvents._array = [data.SlowPathInputEvent(x) for x in pointerEvents]
|
pdu.slowPathInputEvents._array = [data.SlowPathInputEvent(x) for x in pointerEvents]
|
||||||
self.sendDataPDU(pdu)
|
self.sendDataPDU(pdu)
|
||||||
|
|
||||||
class Server(PDULayer, tpkt.IFastPathListener):
|
class Server(PDULayer):
|
||||||
"""
|
"""
|
||||||
Server Automata of PDU layer
|
@summary: Server Automata of PDU layer
|
||||||
"""
|
"""
|
||||||
def __init__(self, listener):
|
def __init__(self, listener):
|
||||||
"""
|
"""
|
||||||
@@ -425,43 +405,14 @@ class Server(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""
|
"""
|
||||||
Connect message for server automata
|
@summary: Connect message for server automata
|
||||||
Wait Info Packet
|
|
||||||
"""
|
"""
|
||||||
self.setNextState(self.recvInfoPkt)
|
|
||||||
|
|
||||||
def setFastPathSender(self, fastPathSender):
|
|
||||||
"""
|
|
||||||
@param fastPathSender: tpkt.FastPathSender
|
|
||||||
@note: implement tpkt.IFastPathListener
|
|
||||||
"""
|
|
||||||
self._fastPathSender = fastPathSender
|
|
||||||
|
|
||||||
def recvInfoPkt(self, s):
|
|
||||||
"""
|
|
||||||
Receive info packet from client
|
|
||||||
Client credentials
|
|
||||||
Send License valid error message
|
|
||||||
Send Demand Active PDU
|
|
||||||
Wait Confirm Active PDU
|
|
||||||
@param s: Stream
|
|
||||||
"""
|
|
||||||
securityFlag = UInt16Le()
|
|
||||||
securityFlagHi = UInt16Le()
|
|
||||||
s.readType((securityFlag, securityFlagHi))
|
|
||||||
|
|
||||||
if not (securityFlag.value & data.SecurityFlag.SEC_INFO_PKT):
|
|
||||||
raise InvalidExpectedDataException("Waiting info packet")
|
|
||||||
|
|
||||||
s.readType(self._info)
|
|
||||||
#next state send error license
|
|
||||||
self.sendLicensingErrorMessage()
|
|
||||||
self.sendDemandActivePDU()
|
self.sendDemandActivePDU()
|
||||||
self.setNextState(self.recvConfirmActivePDU)
|
self.setNextState(self.recvConfirmActivePDU)
|
||||||
|
|
||||||
def recvConfirmActivePDU(self, s):
|
def recvConfirmActivePDU(self, s):
|
||||||
"""
|
"""
|
||||||
Receive confirm active PDU from client
|
@summary: Receive confirm active PDU from client
|
||||||
Capabilities exchange
|
Capabilities exchange
|
||||||
Wait Client Synchronize PDU
|
Wait Client Synchronize PDU
|
||||||
@param s: Stream
|
@param s: Stream
|
||||||
@@ -479,13 +430,16 @@ class Server(PDULayer, tpkt.IFastPathListener):
|
|||||||
self._clientCapabilities[cap.capabilitySetType] = cap
|
self._clientCapabilities[cap.capabilitySetType] = cap
|
||||||
|
|
||||||
#find use full flag
|
#find use full flag
|
||||||
self._clientFastPathSupported = self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED
|
self._clientFastPathSupported = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED)
|
||||||
|
|
||||||
|
#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)
|
self.setNextState(self.recvClientSynchronizePDU)
|
||||||
|
|
||||||
def recvClientSynchronizePDU(self, s):
|
def recvClientSynchronizePDU(self, s):
|
||||||
"""
|
"""
|
||||||
Receive from client
|
@summary: Receive from client
|
||||||
Wait Control Cooperate PDU
|
Wait Control Cooperate PDU
|
||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
@@ -500,7 +454,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def recvClientControlCooperatePDU(self, s):
|
def recvClientControlCooperatePDU(self, s):
|
||||||
"""
|
"""
|
||||||
Receive control cooperate PDU from client
|
@summary: Receive control cooperate PDU from client
|
||||||
Wait Control Request PDU
|
Wait Control Request PDU
|
||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
@@ -515,7 +469,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def recvClientControlRequestPDU(self, s):
|
def recvClientControlRequestPDU(self, s):
|
||||||
"""
|
"""
|
||||||
Receive last control PDU the request control PDU from client
|
@summary: Receive last control PDU the request control PDU from client
|
||||||
Wait Font List PDU
|
Wait Font List PDU
|
||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
@@ -530,7 +484,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def recvClientFontListPDU(self, s):
|
def recvClientFontListPDU(self, s):
|
||||||
"""
|
"""
|
||||||
Last synchronize packet from client to server
|
@summary: Last synchronize packet from client to server
|
||||||
Send Server Finalize PDUs
|
Send Server Finalize PDUs
|
||||||
Wait any PDU
|
Wait any PDU
|
||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
@@ -551,7 +505,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def recvPDU(self, s):
|
def recvPDU(self, s):
|
||||||
"""
|
"""
|
||||||
Main receive function after connection sequence
|
@summary: Main receive function after connection sequence
|
||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdu = data.PDU()
|
||||||
@@ -561,45 +515,39 @@ class Server(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def readDataPDU(self, dataPDU):
|
def readDataPDU(self, dataPDU):
|
||||||
"""
|
"""
|
||||||
read a data PDU object
|
@summary: read a data PDU object
|
||||||
@param dataPDU: DataPDU object
|
@param dataPDU: DataPDU object
|
||||||
"""
|
"""
|
||||||
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
|
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
|
||||||
errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value)
|
errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value)
|
||||||
if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo):
|
if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo):
|
||||||
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
|
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
|
||||||
|
|
||||||
log.error("INFO PDU : %s"%errorMessage)
|
log.error("INFO PDU : %s"%errorMessage)
|
||||||
|
|
||||||
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_INPUT:
|
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_INPUT:
|
||||||
self._listener.onSlowPathInput(dataPDU.pduData.slowPathInputEvents._array)
|
self._listener.onSlowPathInput(dataPDU.pduData.slowPathInputEvents._array)
|
||||||
|
|
||||||
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_REQUEST:
|
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_REQUEST:
|
||||||
log.debug("Receive Shutdown Request")
|
log.debug("Receive Shutdown Request")
|
||||||
self._transport.close()
|
self._transport.close()
|
||||||
|
|
||||||
def recvFastPath(self, fastPathS):
|
def recvFastPath(self, fastPathS):
|
||||||
"""
|
"""
|
||||||
Implement IFastPathListener interface
|
@summary: Implement IFastPathListener interface
|
||||||
Fast path is needed by RDP 8.0
|
Fast path is needed by RDP 8.0
|
||||||
@param fastPathS: Stream that contain fast path data
|
@param fastPathS: Stream that contain fast path data
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def sendLicensingErrorMessage(self):
|
|
||||||
"""
|
|
||||||
Send a licensing error data
|
|
||||||
"""
|
|
||||||
self._transport.send((UInt16Le(data.SecurityFlag.SEC_LICENSE_PKT), UInt16Le(), lic.createValidClientLicensingErrorMessage()))
|
|
||||||
|
|
||||||
def sendDemandActivePDU(self):
|
def sendDemandActivePDU(self):
|
||||||
"""
|
"""
|
||||||
Send server capabilities
|
@summary: Send server capabilities server automata PDU
|
||||||
server automata PDU
|
|
||||||
"""
|
"""
|
||||||
#init general capability
|
#init general capability
|
||||||
generalCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability
|
generalCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability
|
||||||
generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS
|
generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS
|
||||||
generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT
|
generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT
|
||||||
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR
|
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED | caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM
|
||||||
|
|
||||||
inputCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability
|
inputCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability
|
||||||
inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX
|
inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX
|
||||||
@@ -611,7 +559,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def sendServerFinalizeSynchronizePDU(self):
|
def sendServerFinalizeSynchronizePDU(self):
|
||||||
"""
|
"""
|
||||||
Send last synchronize packet from server to client
|
@summary: Send last synchronize packet from server to client
|
||||||
"""
|
"""
|
||||||
synchronizePDU = data.SynchronizeDataPDU(self._transport.getChannelId())
|
synchronizePDU = data.SynchronizeDataPDU(self._transport.getChannelId())
|
||||||
self.sendDataPDU(synchronizePDU)
|
self.sendDataPDU(synchronizePDU)
|
||||||
@@ -632,7 +580,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def sendPDU(self, pduMessage):
|
def sendPDU(self, pduMessage):
|
||||||
"""
|
"""
|
||||||
Send a PDU data to transport layer
|
@summary: Send a PDU data to transport layer
|
||||||
@param pduMessage: PDU message
|
@param pduMessage: PDU message
|
||||||
"""
|
"""
|
||||||
PDULayer.sendPDU(self, pduMessage)
|
PDULayer.sendPDU(self, pduMessage)
|
||||||
@@ -643,7 +591,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
|
|||||||
|
|
||||||
def sendBitmapUpdatePDU(self, bitmapDatas):
|
def sendBitmapUpdatePDU(self, bitmapDatas):
|
||||||
"""
|
"""
|
||||||
Send bitmap update data
|
@summary: Send bitmap update data
|
||||||
@param bitmapDatas: List of data.BitmapData
|
@param bitmapDatas: List of data.BitmapData
|
||||||
"""
|
"""
|
||||||
#check bitmap header for client that want it (very old client)
|
#check bitmap header for client that want it (very old client)
|
||||||
@@ -656,7 +604,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
|
|||||||
#fast path case
|
#fast path case
|
||||||
fastPathUpdateDataPDU = data.FastPathBitmapUpdateDataPDU()
|
fastPathUpdateDataPDU = data.FastPathBitmapUpdateDataPDU()
|
||||||
fastPathUpdateDataPDU.rectangles._array = bitmapDatas
|
fastPathUpdateDataPDU.rectangles._array = bitmapDatas
|
||||||
self._fastPathSender.sendFastPath(data.FastPathUpdatePDU(fastPathUpdateDataPDU))
|
self._fastPathSender.sendFastPath(0, data.FastPathUpdatePDU(fastPathUpdateDataPDU))
|
||||||
else:
|
else:
|
||||||
#slow path case
|
#slow path case
|
||||||
updateDataPDU = data.BitmapUpdateDataPDU()
|
updateDataPDU = data.BitmapUpdateDataPDU()
|
||||||
|
|||||||
@@ -1,231 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
|
||||||
#
|
|
||||||
# This file is part of rdpy.
|
|
||||||
#
|
|
||||||
# rdpy is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
RDP extended license
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241880.aspx
|
|
||||||
"""
|
|
||||||
|
|
||||||
from rdpy.network.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType
|
|
||||||
from rdpy.base.error import InvalidExpectedDataException
|
|
||||||
import rdpy.base.log as log
|
|
||||||
|
|
||||||
class MessageType(object):
|
|
||||||
"""
|
|
||||||
License packet message type
|
|
||||||
"""
|
|
||||||
LICENSE_REQUEST = 0x01
|
|
||||||
PLATFORM_CHALLENGE = 0x02
|
|
||||||
NEW_LICENSE = 0x03
|
|
||||||
UPGRADE_LICENSE = 0x04
|
|
||||||
LICENSE_INFO = 0x12
|
|
||||||
NEW_LICENSE_REQUEST = 0x13
|
|
||||||
PLATFORM_CHALLENGE_RESPONSE = 0x15
|
|
||||||
ERROR_ALERT = 0xFF
|
|
||||||
|
|
||||||
|
|
||||||
class ErrorCode(object):
|
|
||||||
"""
|
|
||||||
License error message code
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
|
|
||||||
"""
|
|
||||||
ERR_INVALID_SERVER_CERTIFICATE = 0x00000001
|
|
||||||
ERR_NO_LICENSE = 0x00000002
|
|
||||||
ERR_INVALID_SCOPE = 0x00000004
|
|
||||||
ERR_NO_LICENSE_SERVER = 0x00000006
|
|
||||||
STATUS_VALID_CLIENT = 0x00000007
|
|
||||||
ERR_INVALID_CLIENT = 0x00000008
|
|
||||||
ERR_INVALID_PRODUCTID = 0x0000000B
|
|
||||||
ERR_INVALID_MESSAGE_LEN = 0x0000000C
|
|
||||||
ERR_INVALID_MAC = 0x00000003
|
|
||||||
|
|
||||||
class StateTransition(object):
|
|
||||||
"""
|
|
||||||
Automata state transition
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
|
|
||||||
"""
|
|
||||||
ST_TOTAL_ABORT = 0x00000001
|
|
||||||
ST_NO_TRANSITION = 0x00000002
|
|
||||||
ST_RESET_PHASE_TO_START = 0x00000003
|
|
||||||
ST_RESEND_LAST_MESSAGE = 0x00000004
|
|
||||||
|
|
||||||
class BinaryBlobType(object):
|
|
||||||
"""
|
|
||||||
Binary blob data type
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
|
|
||||||
"""
|
|
||||||
BB_DATA_BLOB = 0x0001
|
|
||||||
BB_RANDOM_BLOB = 0x0002
|
|
||||||
BB_CERTIFICATE_BLOB = 0x0003
|
|
||||||
BB_ERROR_BLOB = 0x0004
|
|
||||||
BB_ENCRYPTED_DATA_BLOB = 0x0009
|
|
||||||
BB_KEY_EXCHG_ALG_BLOB = 0x000D
|
|
||||||
BB_SCOPE_BLOB = 0x000E
|
|
||||||
BB_CLIENT_USER_NAME_BLOB = 0x000F
|
|
||||||
BB_CLIENT_MACHINE_NAME_BLOB = 0x0010
|
|
||||||
|
|
||||||
class Preambule(object):
|
|
||||||
"""
|
|
||||||
Preambule version
|
|
||||||
"""
|
|
||||||
PREAMBLE_VERSION_2_0 = 0x2
|
|
||||||
PREAMBLE_VERSION_3_0 = 0x3
|
|
||||||
|
|
||||||
class LicenseBinaryBlob(CompositeType):
|
|
||||||
"""
|
|
||||||
Blob use by license manager to exchange security data
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
|
|
||||||
"""
|
|
||||||
def __init__(self, blobType = 0):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.wBlobType = UInt16Le(blobType, constant = True)
|
|
||||||
self.wBlobLen = UInt16Le(lambda:sizeof(self.blobData))
|
|
||||||
self.blobData = String(readLen = self.wBlobLen)
|
|
||||||
|
|
||||||
class LicensingErrorMessage(CompositeType):
|
|
||||||
"""
|
|
||||||
License error message
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
|
|
||||||
"""
|
|
||||||
_MESSAGE_TYPE_ = MessageType.ERROR_ALERT
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.dwErrorCode = UInt32Le()
|
|
||||||
self.dwStateTransition = UInt32Le()
|
|
||||||
self.blob = LicenseBinaryBlob(BinaryBlobType.BB_ERROR_BLOB)
|
|
||||||
|
|
||||||
class ProductInformation(CompositeType):
|
|
||||||
"""
|
|
||||||
License server product information
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241915.aspx
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.dwVersion = UInt32Le()
|
|
||||||
self.cbCompanyName = UInt32Le(lambda:sizeof(self.pbCompanyName))
|
|
||||||
#may contain "Microsoft Corporation" from server microsoft
|
|
||||||
self.pbCompanyName = String(readLen = self.cbCompanyName)
|
|
||||||
self.cbProductId = UInt32Le(lambda:sizeof(self.pbProductId))
|
|
||||||
#may contain "A02" from microsoft license server
|
|
||||||
self.pbProductId = String(readLen = self.cbProductId)
|
|
||||||
|
|
||||||
|
|
||||||
class Scope(CompositeType):
|
|
||||||
"""
|
|
||||||
Use in license nego
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241917.aspx
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.scope = LicenseBinaryBlob(BinaryBlobType.BB_SCOPE_BLOB)
|
|
||||||
|
|
||||||
class ScopeList(CompositeType):
|
|
||||||
"""
|
|
||||||
Use in license nego
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241916.aspx
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.scopeCount = UInt32Le(lambda:sizeof(self.scopeArray))
|
|
||||||
self.scopeArray = ArrayType(Scope, readLen = self.scopeCount)
|
|
||||||
|
|
||||||
class ServerLicenseRequest(CompositeType):
|
|
||||||
"""
|
|
||||||
Send by server to signal license request
|
|
||||||
server -> client
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241914.aspx
|
|
||||||
"""
|
|
||||||
_MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.serverRandom = String(readLen = UInt8(32))
|
|
||||||
self.productInfo = ProductInformation()
|
|
||||||
self.keyExchangeList = LicenseBinaryBlob(BinaryBlobType.BB_KEY_EXCHG_ALG_BLOB)
|
|
||||||
self.serverCertificate = LicenseBinaryBlob(BinaryBlobType.BB_CERTIFICATE_BLOB)
|
|
||||||
self.scopeList = ScopeList()
|
|
||||||
|
|
||||||
class ClientNewLicenseRequest(CompositeType):
|
|
||||||
"""
|
|
||||||
Send by client to ask new license for client.
|
|
||||||
RDPY doesn'support license reuse, need it in futur version
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
|
|
||||||
"""
|
|
||||||
_MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
#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)
|
|
||||||
self.encryptedPreMasterSecret = LicenseBinaryBlob(BinaryBlobType.BB_RANDOM_BLOB)
|
|
||||||
self.ClientUserName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_USER_NAME_BLOB)
|
|
||||||
self.ClientMachineName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_MACHINE_NAME_BLOB)
|
|
||||||
|
|
||||||
class LicPacket(CompositeType):
|
|
||||||
"""
|
|
||||||
A license packet
|
|
||||||
"""
|
|
||||||
def __init__(self, message = None):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
#preambule
|
|
||||||
self.bMsgtype = UInt8(lambda:self.licensingMessage.__class__._MESSAGE_TYPE_)
|
|
||||||
self.flag = UInt8(Preambule.PREAMBLE_VERSION_3_0)
|
|
||||||
self.wMsgSize = UInt16Le(lambda: sizeof(self))
|
|
||||||
|
|
||||||
def LicensingMessageFactory():
|
|
||||||
"""
|
|
||||||
factory for message nego
|
|
||||||
Use in read mode
|
|
||||||
"""
|
|
||||||
for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest]:
|
|
||||||
if self.bMsgtype.value == c._MESSAGE_TYPE_:
|
|
||||||
return c()
|
|
||||||
log.debug("unknown license message : %s"%self.bMsgtype.value)
|
|
||||||
return String()
|
|
||||||
|
|
||||||
if message is None:
|
|
||||||
message = FactoryType(LicensingMessageFactory)
|
|
||||||
elif not "_MESSAGE_TYPE_" in message.__class__.__dict__:
|
|
||||||
raise InvalidExpectedDataException("Try to send an invalid license message")
|
|
||||||
|
|
||||||
self.licensingMessage = message
|
|
||||||
|
|
||||||
def createValidClientLicensingErrorMessage():
|
|
||||||
"""
|
|
||||||
Create a licensing error message that accept client
|
|
||||||
server automata message
|
|
||||||
"""
|
|
||||||
message = LicensingErrorMessage()
|
|
||||||
message.dwErrorCode.value = ErrorCode.STATUS_VALID_CLIENT
|
|
||||||
message.dwStateTransition.value = StateTransition.ST_NO_TRANSITION
|
|
||||||
return LicPacket(message = message)
|
|
||||||
|
|
||||||
def createNewLicenseRequest(serverLicenseRequest):
|
|
||||||
"""
|
|
||||||
Create new license request in response to server license request
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241989.aspx
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
|
|
||||||
@todo: need RDP license server
|
|
||||||
"""
|
|
||||||
return LicPacket(message = ClientNewLicenseRequest())
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -21,9 +21,9 @@
|
|||||||
GDI order structure
|
GDI order structure
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.base import log
|
from rdpy.core import log
|
||||||
from rdpy.base.error import InvalidExpectedDataException
|
from rdpy.core.error import InvalidExpectedDataException
|
||||||
from rdpy.network.type import CompositeType, UInt8, String, FactoryType, SInt8, SInt16Le
|
from rdpy.core.type import CompositeType, UInt8, String, FactoryType, SInt8, SInt16Le
|
||||||
|
|
||||||
class ControlFlag(object):
|
class ControlFlag(object):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -21,13 +21,23 @@
|
|||||||
Use to manage RDP stack in twisted
|
Use to manage RDP stack in twisted
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.network import layer
|
from rdpy.core import layer
|
||||||
from rdpy.base.error import CallPureVirtualFuntion, InvalidValue
|
from rdpy.core.error import CallPureVirtualFuntion, InvalidValue
|
||||||
import pdu.layer
|
import pdu.layer
|
||||||
import pdu.data
|
import pdu.data
|
||||||
import pdu.caps
|
import pdu.caps
|
||||||
import rdpy.base.log as log
|
import rdpy.core.log as log
|
||||||
import tpkt, x224, mcs, gcc
|
import tpkt, x224, sec
|
||||||
|
from t125 import mcs, gcc
|
||||||
|
from nla import cssp, ntlm
|
||||||
|
|
||||||
|
class SecurityLevel(object):
|
||||||
|
"""
|
||||||
|
@summary: RDP security level
|
||||||
|
"""
|
||||||
|
RDP_LEVEL_RDP = 0
|
||||||
|
RDP_LEVEL_SSL = 1
|
||||||
|
RDP_LEVEL_NLA = 2
|
||||||
|
|
||||||
class RDPClientController(pdu.layer.PDUClientListener):
|
class RDPClientController(pdu.layer.PDUClientListener):
|
||||||
"""
|
"""
|
||||||
@@ -38,12 +48,17 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
self._clientObserver = []
|
self._clientObserver = []
|
||||||
#PDU layer
|
#PDU layer
|
||||||
self._pduLayer = pdu.layer.Client(self)
|
self._pduLayer = pdu.layer.Client(self)
|
||||||
|
#secure layer
|
||||||
|
self._secLayer = sec.Client(self._pduLayer)
|
||||||
#multi channel service
|
#multi channel service
|
||||||
self._mcsLayer = mcs.Client(self._pduLayer)
|
self._mcsLayer = mcs.Client(self._secLayer)
|
||||||
#transport pdu layer
|
#transport pdu layer
|
||||||
self._x224Layer = x224.Client(self._mcsLayer)
|
self._x224Layer = x224.Client(self._mcsLayer)
|
||||||
#transport packet (protocol layer)
|
#transport packet (protocol layer)
|
||||||
self._tpktLayer = tpkt.TPKT(self._x224Layer, self._pduLayer)
|
self._tpktLayer = tpkt.TPKT(self._x224Layer)
|
||||||
|
#fastpath stack
|
||||||
|
self._pduLayer.initFastPath(self._secLayer)
|
||||||
|
self._secLayer.initFastPath(self._tpktLayer)
|
||||||
#is pdu layer is ready to send
|
#is pdu layer is ready to send
|
||||||
self._isReady = False
|
self._isReady = False
|
||||||
|
|
||||||
@@ -52,7 +67,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
@return: return Protocol layer for twisted
|
@return: return Protocol layer for twisted
|
||||||
In case of RDP TPKT is the Raw layer
|
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):
|
def getColorDepth(self):
|
||||||
"""
|
"""
|
||||||
@@ -68,13 +83,13 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
|
|
||||||
def setPerformanceSession(self):
|
def setPerformanceSession(self):
|
||||||
"""
|
"""
|
||||||
Set particular flag in RDP stack to avoid wall-paper, theme, menu animation etc...
|
@summary: Set particular flag in RDP stack to avoid wall-paper, theme, menu animation etc...
|
||||||
"""
|
"""
|
||||||
self._pduLayer._info.extendedInfo.performanceFlags.value = pdu.data.PerfFlag.PERF_DISABLE_WALLPAPER | pdu.data.PerfFlag.PERF_DISABLE_MENUANIMATIONS | pdu.data.PerfFlag.PERF_DISABLE_CURSOR_SHADOW | pdu.data.PerfFlag.PERF_DISABLE_THEMING | pdu.data.PerfFlag.PERF_DISABLE_FULLWINDOWDRAG
|
self._secLayer._info.extendedInfo.performanceFlags.value = sec.PerfFlag.PERF_DISABLE_WALLPAPER | sec.PerfFlag.PERF_DISABLE_MENUANIMATIONS | sec.PerfFlag.PERF_DISABLE_CURSOR_SHADOW | sec.PerfFlag.PERF_DISABLE_THEMING | sec.PerfFlag.PERF_DISABLE_FULLWINDOWDRAG
|
||||||
|
|
||||||
def setScreen(self, width, height):
|
def setScreen(self, width, height):
|
||||||
"""
|
"""
|
||||||
Set screen dim of session
|
@summary: Set screen dim of session
|
||||||
@param width: width in pixel of screen
|
@param width: width in pixel of screen
|
||||||
@param height: height in pixel of screen
|
@param height: height in pixel of screen
|
||||||
"""
|
"""
|
||||||
@@ -84,43 +99,80 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
|
|
||||||
def setUsername(self, username):
|
def setUsername(self, username):
|
||||||
"""
|
"""
|
||||||
Set the username for session
|
@summary: Set the username for session
|
||||||
@param username: username of session
|
@param username: {string} username of session
|
||||||
"""
|
"""
|
||||||
#username in PDU info packet
|
#username in PDU info packet
|
||||||
self._pduLayer._info.userName.value = username
|
self._secLayer._info.userName.value = username
|
||||||
|
self._secLayer._licenceManager._username = username
|
||||||
|
|
||||||
def setPassword(self, password):
|
def setPassword(self, password):
|
||||||
"""
|
"""
|
||||||
Set password for session
|
@summary: Set password for session
|
||||||
@param password: password of session
|
@param password: {string} password of session
|
||||||
"""
|
"""
|
||||||
self.setAutologon()
|
self.setAutologon()
|
||||||
self._pduLayer._info.password.value = password
|
self._secLayer._info.password.value = password
|
||||||
|
|
||||||
def setDomain(self, domain):
|
def setDomain(self, domain):
|
||||||
"""
|
"""
|
||||||
Set the windows domain of session
|
@summary: Set the windows domain of session
|
||||||
@param domain: domain of session
|
@param domain: {string} domain of session
|
||||||
"""
|
"""
|
||||||
self._pduLayer._info.domain.value = domain
|
self._secLayer._info.domain.value = domain
|
||||||
|
|
||||||
def setAutologon(self):
|
def setAutologon(self):
|
||||||
"""
|
"""
|
||||||
@summary: enable autologon
|
@summary: enable autologon
|
||||||
"""
|
"""
|
||||||
self._pduLayer._info.flag |= pdu.data.InfoFlag.INFO_AUTOLOGON
|
self._secLayer._info.flag |= sec.InfoFlag.INFO_AUTOLOGON
|
||||||
|
|
||||||
|
def setAlternateShell(self, appName):
|
||||||
|
"""
|
||||||
|
@summary: set application name of app which start at the begining of session
|
||||||
|
@param appName: {string} application name
|
||||||
|
"""
|
||||||
|
self._secLayer._info.alternateShell.value = appName
|
||||||
|
|
||||||
|
def setKeyboardLayout(self, layout):
|
||||||
|
"""
|
||||||
|
@summary: keyboard layout
|
||||||
|
@param layout: us | fr
|
||||||
|
"""
|
||||||
|
if layout == "fr":
|
||||||
|
self._mcsLayer._clientSettings.CS_CORE.kbdLayout.value = gcc.KeyboardLayout.FRENCH
|
||||||
|
elif layout == "us":
|
||||||
|
self._mcsLayer._clientSettings.CS_CORE.kbdLayout.value = gcc.KeyboardLayout.US
|
||||||
|
|
||||||
|
def setHostname(self, hostname):
|
||||||
|
"""
|
||||||
|
@summary: set hostname of machine
|
||||||
|
"""
|
||||||
|
self._mcsLayer._clientSettings.CS_CORE.clientName.value = hostname[:15] + "\x00" * (15 - len(hostname))
|
||||||
|
self._secLayer._licenceManager._hostname = hostname
|
||||||
|
|
||||||
|
def setSecurityLevel(self, level):
|
||||||
|
"""
|
||||||
|
@summary: Request basic security
|
||||||
|
@param level: {SecurityLevel}
|
||||||
|
"""
|
||||||
|
if level == SecurityLevel.RDP_LEVEL_RDP:
|
||||||
|
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP
|
||||||
|
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):
|
def addClientObserver(self, observer):
|
||||||
"""
|
"""
|
||||||
Add observer to RDP protocol
|
@summary: Add observer to RDP protocol
|
||||||
@param observer: new observer to add
|
@param observer: new observer to add
|
||||||
"""
|
"""
|
||||||
self._clientObserver.append(observer)
|
self._clientObserver.append(observer)
|
||||||
|
|
||||||
def removeClientObserver(self, observer):
|
def removeClientObserver(self, observer):
|
||||||
"""
|
"""
|
||||||
Remove observer to RDP protocol stack
|
@summary: Remove observer to RDP protocol stack
|
||||||
@param observer: observer to remove
|
@param observer: observer to remove
|
||||||
"""
|
"""
|
||||||
for i in range(0, len(self._clientObserver)):
|
for i in range(0, len(self._clientObserver)):
|
||||||
@@ -130,7 +182,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
|
|
||||||
def onUpdate(self, rectangles):
|
def onUpdate(self, rectangles):
|
||||||
"""
|
"""
|
||||||
Call when a bitmap data is received from update PDU
|
@summary: Call when a bitmap data is received from update PDU
|
||||||
@param rectangles: [pdu.BitmapData] struct
|
@param rectangles: [pdu.BitmapData] struct
|
||||||
"""
|
"""
|
||||||
for observer in self._clientObserver:
|
for observer in self._clientObserver:
|
||||||
@@ -140,16 +192,25 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
Call when PDU layer is connected
|
@summary: Call when PDU layer is connected
|
||||||
"""
|
"""
|
||||||
self._isReady = True
|
self._isReady = True
|
||||||
#signal all listener
|
#signal all listener
|
||||||
for observer in self._clientObserver:
|
for observer in self._clientObserver:
|
||||||
observer.onReady()
|
observer.onReady()
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Call when Windows session is ready (connected)
|
||||||
|
"""
|
||||||
|
self._isReady = True
|
||||||
|
#signal all listener
|
||||||
|
for observer in self._clientObserver:
|
||||||
|
observer.onSessionReady()
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
Event call when RDP stack is closed
|
@summary: Event call when RDP stack is closed
|
||||||
"""
|
"""
|
||||||
self._isReady = False
|
self._isReady = False
|
||||||
for observer in self._clientObserver:
|
for observer in self._clientObserver:
|
||||||
@@ -157,7 +218,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
|
|
||||||
def sendPointerEvent(self, x, y, button, isPressed):
|
def sendPointerEvent(self, x, y, button, isPressed):
|
||||||
"""
|
"""
|
||||||
send pointer events
|
@summary: send pointer events
|
||||||
@param x: x position of pointer
|
@param x: x position of pointer
|
||||||
@param y: y position of pointer
|
@param y: y position of pointer
|
||||||
@param button: 1 or 2 or 3
|
@param button: 1 or 2 or 3
|
||||||
@@ -189,12 +250,47 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
|
|
||||||
except InvalidValue:
|
except InvalidValue:
|
||||||
log.info("try send pointer event with incorrect position")
|
log.info("try send pointer event with incorrect position")
|
||||||
|
|
||||||
def sendKeyEventScancode(self, code, isPressed):
|
def sendWheelEvent(self, x, y, step, isNegative = False, isHorizontal = False):
|
||||||
"""
|
"""
|
||||||
Send a scan code to RDP stack
|
@summary: Send a mouse wheel event
|
||||||
|
@param x: x position of pointer
|
||||||
|
@param y: y position of pointer
|
||||||
|
@param step: number of step rolled
|
||||||
|
@param isHorizontal: horizontal wheel (default is vertical)
|
||||||
|
@param isNegative: is upper (default down)
|
||||||
|
"""
|
||||||
|
if not self._isReady:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
event = pdu.data.PointerEvent()
|
||||||
|
if isHorizontal:
|
||||||
|
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_HWHEEL
|
||||||
|
else:
|
||||||
|
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL
|
||||||
|
|
||||||
|
if isNegative:
|
||||||
|
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL_NEGATIVE
|
||||||
|
|
||||||
|
event.pointerFlags.value |= (step & pdu.data.PointerFlag.WheelRotationMask)
|
||||||
|
|
||||||
|
#position
|
||||||
|
event.xPos.value = x
|
||||||
|
event.yPos.value = y
|
||||||
|
|
||||||
|
#send proper event
|
||||||
|
self._pduLayer.sendInputEvents([event])
|
||||||
|
|
||||||
|
except InvalidValue:
|
||||||
|
log.info("try send wheel event with incorrect position")
|
||||||
|
|
||||||
|
def sendKeyEventScancode(self, code, isPressed, extended = False):
|
||||||
|
"""
|
||||||
|
@summary: Send a scan code to RDP stack
|
||||||
@param code: scan code
|
@param code: scan code
|
||||||
@param isPressed: True if key is pressed and false if it's released
|
@param isPressed: True if key is pressed and false if it's released
|
||||||
|
@param extended: {boolean} extended scancode like ctr or win button
|
||||||
"""
|
"""
|
||||||
if not self._isReady:
|
if not self._isReady:
|
||||||
return
|
return
|
||||||
@@ -202,11 +298,12 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
try:
|
try:
|
||||||
event = pdu.data.ScancodeKeyEvent()
|
event = pdu.data.ScancodeKeyEvent()
|
||||||
event.keyCode.value = code
|
event.keyCode.value = code
|
||||||
if isPressed:
|
if not isPressed:
|
||||||
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_DOWN
|
|
||||||
else:
|
|
||||||
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE
|
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE
|
||||||
|
|
||||||
|
if extended:
|
||||||
|
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED
|
||||||
|
|
||||||
#send event
|
#send event
|
||||||
self._pduLayer.sendInputEvents([event])
|
self._pduLayer.sendInputEvents([event])
|
||||||
|
|
||||||
@@ -215,7 +312,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
|
|
||||||
def sendKeyEventUnicode(self, code, isPressed):
|
def sendKeyEventUnicode(self, code, isPressed):
|
||||||
"""
|
"""
|
||||||
Send a scan code to RDP stack
|
@summary: Send a scan code to RDP stack
|
||||||
@param code: unicode
|
@param code: unicode
|
||||||
@param isPressed: True if key is pressed and false if it's released
|
@param isPressed: True if key is pressed and false if it's released
|
||||||
"""
|
"""
|
||||||
@@ -236,7 +333,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
|
|
||||||
def sendRefreshOrder(self, left, top, right, bottom):
|
def sendRefreshOrder(self, left, top, right, bottom):
|
||||||
"""
|
"""
|
||||||
Force server to resend a particular zone
|
@summary: Force server to resend a particular zone
|
||||||
@param left: left coordinate
|
@param left: left coordinate
|
||||||
@param top: top coordinate
|
@param top: top coordinate
|
||||||
@param right: right coordinate
|
@param right: right coordinate
|
||||||
@@ -253,15 +350,15 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Close protocol stack
|
@summary: Close protocol stack
|
||||||
"""
|
"""
|
||||||
self._pduLayer.close()
|
self._pduLayer.close()
|
||||||
|
|
||||||
class RDPServerController(pdu.layer.PDUServerListener):
|
class RDPServerController(pdu.layer.PDUServerListener):
|
||||||
"""
|
"""
|
||||||
Controller use in server side mode
|
@summary: Controller use in server side mode
|
||||||
"""
|
"""
|
||||||
def __init__(self, privateKeyFileName, certificateFileName, colorDepth):
|
def __init__(self, colorDepth, privateKeyFileName = None, certificateFileName = None):
|
||||||
"""
|
"""
|
||||||
@param privateKeyFileName: file contain server private key
|
@param privateKeyFileName: file contain server private key
|
||||||
@param certficiateFileName: file that contain public key
|
@param certficiateFileName: file that contain public key
|
||||||
@@ -272,18 +369,24 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
self._serverObserver = []
|
self._serverObserver = []
|
||||||
#build RDP protocol stack
|
#build RDP protocol stack
|
||||||
self._pduLayer = pdu.layer.Server(self)
|
self._pduLayer = pdu.layer.Server(self)
|
||||||
|
#secure layer
|
||||||
|
self._secLayer = sec.Server(self._pduLayer)
|
||||||
#multi channel service
|
#multi channel service
|
||||||
self._mcsLayer = mcs.Server(self._pduLayer)
|
self._mcsLayer = mcs.Server(self._secLayer)
|
||||||
#transport pdu layer
|
#transport pdu layer
|
||||||
self._x224Layer = x224.Server(self._mcsLayer, privateKeyFileName, certificateFileName)
|
self._x224Layer = x224.Server(self._mcsLayer, privateKeyFileName, certificateFileName, False)
|
||||||
#transport packet (protocol layer)
|
#transport packet (protocol layer)
|
||||||
self._tpktLayer = tpkt.TPKT(self._x224Layer, self._pduLayer)
|
self._tpktLayer = tpkt.TPKT(self._x224Layer)
|
||||||
|
|
||||||
|
#fastpath stack
|
||||||
|
self._pduLayer.initFastPath(self._secLayer)
|
||||||
|
self._secLayer.initFastPath(self._tpktLayer)
|
||||||
#set color depth of session
|
#set color depth of session
|
||||||
self.setColorDepth(colorDepth)
|
self.setColorDepth(colorDepth)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Close protocol stack
|
@summary: Close protocol stack
|
||||||
"""
|
"""
|
||||||
self._pduLayer.close()
|
self._pduLayer.close()
|
||||||
|
|
||||||
@@ -294,30 +397,36 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
"""
|
"""
|
||||||
return self._tpktLayer
|
return self._tpktLayer
|
||||||
|
|
||||||
|
def getHostname(self):
|
||||||
|
"""
|
||||||
|
@return: name of client (information done by RDP)
|
||||||
|
"""
|
||||||
|
return self._mcsLayer._clientSettings.CS_CORE.clientName.value.strip('\x00')
|
||||||
|
|
||||||
def getUsername(self):
|
def getUsername(self):
|
||||||
"""
|
"""
|
||||||
Must be call after on ready event else always empty string
|
@summary: Must be call after on ready event else always empty string
|
||||||
@return: username send by client may be an empty string
|
@return: username send by client may be an empty string
|
||||||
"""
|
"""
|
||||||
return self._pduLayer._info.userName.value
|
return self._secLayer._info.userName.value
|
||||||
|
|
||||||
def getPassword(self):
|
def getPassword(self):
|
||||||
"""
|
"""
|
||||||
Must be call after on ready event else always empty string
|
@summary: Must be call after on ready event else always empty string
|
||||||
@return: password send by client may be an empty string
|
@return: password send by client may be an empty string
|
||||||
"""
|
"""
|
||||||
return self._pduLayer._info.password.value
|
return self._secLayer._info.password.value
|
||||||
|
|
||||||
def getDomain(self):
|
def getDomain(self):
|
||||||
"""
|
"""
|
||||||
Must be call after on ready event else always empty string
|
@summary: Must be call after on ready event else always empty string
|
||||||
@return: domain send by client may be an empty string
|
@return: domain send by client may be an empty string
|
||||||
"""
|
"""
|
||||||
return self._pduLayer._info.domain.value
|
return self._secLayer._info.domain.value
|
||||||
|
|
||||||
def getCredentials(self):
|
def getCredentials(self):
|
||||||
"""
|
"""
|
||||||
Must be call after on ready event else always empty string
|
@summary: Must be call after on ready event else always empty string
|
||||||
@return: tuple(domain, username, password)
|
@return: tuple(domain, username, password)
|
||||||
"""
|
"""
|
||||||
return (self.getDomain(), self.getUsername(), self.getPassword())
|
return (self.getDomain(), self.getUsername(), self.getPassword())
|
||||||
@@ -337,16 +446,17 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
|
|
||||||
def addServerObserver(self, observer):
|
def addServerObserver(self, observer):
|
||||||
"""
|
"""
|
||||||
Add observer to RDP protocol
|
@summary: Add observer to RDP protocol
|
||||||
@param observer: new observer to add
|
@param observer: new observer to add
|
||||||
"""
|
"""
|
||||||
self._serverObserver.append(observer)
|
self._serverObserver.append(observer)
|
||||||
|
|
||||||
def setColorDepth(self, colorDepth):
|
def setColorDepth(self, colorDepth):
|
||||||
"""
|
"""
|
||||||
Set color depth of session
|
@summary: Set color depth of session
|
||||||
if PDU stack is already connected send a deactive-reactive sequence
|
if PDU stack is already connected send a deactive-reactive sequence
|
||||||
@param colorDepth: depth of session (15, 16, 24)
|
and an onReady message is re send when client is ready
|
||||||
|
@param colorDepth: {integer} depth of session (15, 16, 24)
|
||||||
"""
|
"""
|
||||||
self._colorDepth = colorDepth
|
self._colorDepth = colorDepth
|
||||||
self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_BITMAP].capability.preferredBitsPerPixel.value = colorDepth
|
self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_BITMAP].capability.preferredBitsPerPixel.value = colorDepth
|
||||||
@@ -357,13 +467,13 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
|
|
||||||
def setKeyEventUnicodeSupport(self):
|
def setKeyEventUnicodeSupport(self):
|
||||||
"""
|
"""
|
||||||
Enable key event in unicode format
|
@summary: Enable key event in unicode format
|
||||||
"""
|
"""
|
||||||
self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_INPUT].capability.inputFlags.value |= pdu.caps.InputFlags.INPUT_FLAG_UNICODE
|
self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_INPUT].capability.inputFlags.value |= pdu.caps.InputFlags.INPUT_FLAG_UNICODE
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
RDP stack is now ready
|
@summary: RDP stack is now ready
|
||||||
"""
|
"""
|
||||||
self._isReady = True
|
self._isReady = True
|
||||||
for observer in self._serverObserver:
|
for observer in self._serverObserver:
|
||||||
@@ -371,7 +481,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
Event call when RDP stack is closed
|
@summary: Event call when RDP stack is closed
|
||||||
"""
|
"""
|
||||||
self._isReady = False
|
self._isReady = False
|
||||||
for observer in self._serverObserver:
|
for observer in self._serverObserver:
|
||||||
@@ -379,14 +489,14 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
|
|
||||||
def onSlowPathInput(self, slowPathInputEvents):
|
def onSlowPathInput(self, slowPathInputEvents):
|
||||||
"""
|
"""
|
||||||
Event call when slow path input are available
|
@summary: Event call when slow path input are available
|
||||||
@param slowPathInputEvents: [data.SlowPathInputEvent]
|
@param slowPathInputEvents: [data.SlowPathInputEvent]
|
||||||
"""
|
"""
|
||||||
for observer in self._serverObserver:
|
for observer in self._serverObserver:
|
||||||
for event in slowPathInputEvents:
|
for event in slowPathInputEvents:
|
||||||
#scan code
|
#scan code
|
||||||
if event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_SCANCODE:
|
if event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_SCANCODE:
|
||||||
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
|
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE), bool(event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED))
|
||||||
#unicode
|
#unicode
|
||||||
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_UNICODE:
|
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_UNICODE:
|
||||||
observer.onKeyEventUnicode(event.slowPathInputData.unicode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
|
observer.onKeyEventUnicode(event.slowPathInputData.unicode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
|
||||||
@@ -404,7 +514,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
|
|
||||||
def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||||
"""
|
"""
|
||||||
send bitmap update
|
@summary: send bitmap update
|
||||||
@param destLeft: xmin position
|
@param destLeft: xmin position
|
||||||
@param destTop: ymin position
|
@param destTop: ymin position
|
||||||
@param destRight: xmax position because RDP can send bitmap with padding
|
@param destRight: xmax position because RDP can send bitmap with padding
|
||||||
@@ -426,12 +536,15 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
class ClientFactory(layer.RawLayerClientFactory):
|
class ClientFactory(layer.RawLayerClientFactory):
|
||||||
"""
|
"""
|
||||||
@summary: Factory of Client RDP protocol
|
@summary: Factory of Client RDP protocol
|
||||||
|
@param reason: twisted reason
|
||||||
"""
|
"""
|
||||||
def connectionLost(self, tpktLayer):
|
def connectionLost(self, csspLayer, reason):
|
||||||
#retrieve controller
|
#retrieve controller
|
||||||
|
tpktLayer = csspLayer._layer
|
||||||
x224Layer = tpktLayer._presentation
|
x224Layer = tpktLayer._presentation
|
||||||
mcsLayer = x224Layer._presentation
|
mcsLayer = x224Layer._presentation
|
||||||
pduLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
|
secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
|
||||||
|
pduLayer = secLayer._presentation
|
||||||
controller = pduLayer._listener
|
controller = pduLayer._listener
|
||||||
controller.onClose()
|
controller.onClose()
|
||||||
|
|
||||||
@@ -454,38 +567,42 @@ class ClientFactory(layer.RawLayerClientFactory):
|
|||||||
|
|
||||||
class ServerFactory(layer.RawLayerServerFactory):
|
class ServerFactory(layer.RawLayerServerFactory):
|
||||||
"""
|
"""
|
||||||
Factory of Server RDP protocol
|
@summary: Factory of Server RDP protocol
|
||||||
"""
|
"""
|
||||||
def __init__(self, privateKeyFileName, certificateFileName, colorDepth):
|
def __init__(self, colorDepth, privateKeyFileName = None, certificateFileName = None):
|
||||||
"""
|
"""
|
||||||
@param privateKeyFileName: file contain server private key
|
|
||||||
@param certficiateFileName: file that contain public key
|
|
||||||
@param colorDepth: color depth of session
|
@param 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._privateKeyFileName = privateKeyFileName
|
||||||
self._certificateFileName = certificateFileName
|
self._certificateFileName = certificateFileName
|
||||||
self._colorDepth = colorDepth
|
|
||||||
|
|
||||||
def connectionLost(self, tpktLayer):
|
def connectionLost(self, tpktLayer, reason):
|
||||||
|
"""
|
||||||
|
@param reason: twisted reason
|
||||||
|
"""
|
||||||
#retrieve controller
|
#retrieve controller
|
||||||
x224Layer = tpktLayer._presentation
|
x224Layer = tpktLayer._presentation
|
||||||
mcsLayer = x224Layer._presentation
|
mcsLayer = x224Layer._presentation
|
||||||
pduLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
|
secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
|
||||||
|
pduLayer = secLayer._presentation
|
||||||
controller = pduLayer._listener
|
controller = pduLayer._listener
|
||||||
controller.onClose()
|
controller.onClose()
|
||||||
|
|
||||||
def buildRawLayer(self, addr):
|
def buildRawLayer(self, addr):
|
||||||
"""
|
"""
|
||||||
Function call from twisted and build rdp protocol stack
|
@summary: Function call from twisted and build rdp protocol stack
|
||||||
@param addr: destination address
|
@param addr: destination address
|
||||||
"""
|
"""
|
||||||
controller = RDPServerController(self._privateKeyFileName, self._certificateFileName, self._colorDepth)
|
controller = RDPServerController(self._colorDepth, self._privateKeyFileName, self._certificateFileName)
|
||||||
self.buildObserver(controller, addr)
|
self.buildObserver(controller, addr)
|
||||||
return controller.getProtocol()
|
return controller.getProtocol()
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
"""
|
"""
|
||||||
Build observer use for connection
|
@summary: Build observer use for connection
|
||||||
@param controller: RDP stack controller
|
@param controller: RDP stack controller
|
||||||
@param addr: destination address
|
@param addr: destination address
|
||||||
"""
|
"""
|
||||||
@@ -493,7 +610,7 @@ class ServerFactory(layer.RawLayerServerFactory):
|
|||||||
|
|
||||||
class RDPClientObserver(object):
|
class RDPClientObserver(object):
|
||||||
"""
|
"""
|
||||||
Class use to inform all RDP event handle by RDPY
|
@summary: Class use to inform all RDP event handle by RDPY
|
||||||
"""
|
"""
|
||||||
def __init__(self, controller):
|
def __init__(self, controller):
|
||||||
"""
|
"""
|
||||||
@@ -504,19 +621,25 @@ class RDPClientObserver(object):
|
|||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
Stack is ready and connected
|
@summary: Stack is ready and connected
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver"))
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Windows session is ready
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "RDPClientObserver"))
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
Stack is closes
|
@summary: Stack is closes
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
|
||||||
|
|
||||||
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||||
"""
|
"""
|
||||||
Notify bitmap update
|
@summary: Notify bitmap update
|
||||||
@param destLeft: xmin position
|
@param destLeft: xmin position
|
||||||
@param destTop: ymin position
|
@param destTop: ymin position
|
||||||
@param destRight: xmax position because RDP can send bitmap with padding
|
@param destRight: xmax position because RDP can send bitmap with padding
|
||||||
@@ -531,7 +654,7 @@ class RDPClientObserver(object):
|
|||||||
|
|
||||||
class RDPServerObserver(object):
|
class RDPServerObserver(object):
|
||||||
"""
|
"""
|
||||||
Class use to inform all RDP event handle by RDPY
|
@summary: Class use to inform all RDP event handle by RDPY
|
||||||
"""
|
"""
|
||||||
def __init__(self, controller):
|
def __init__(self, controller):
|
||||||
"""
|
"""
|
||||||
@@ -542,28 +665,29 @@ class RDPServerObserver(object):
|
|||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
Stack is ready and connected
|
@summary: Stack is ready and connected
|
||||||
May be called after an setColorDepth too
|
May be called after an setColorDepth too
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPServerObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPServerObserver"))
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
Stack is closes
|
@summary: Stack is closes
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
|
||||||
|
|
||||||
def onKeyEventScancode(self, code, isPressed):
|
def onKeyEventScancode(self, code, isPressed, isExtended):
|
||||||
"""
|
"""
|
||||||
Event call when a keyboard event is catch in scan code format
|
@summary: Event call when a keyboard event is catch in scan code format
|
||||||
@param code: scan code of key
|
@param code: {integer} scan code of key
|
||||||
@param isPressed: True if key is down
|
@param isPressed: {boolean} True if key is down
|
||||||
|
@param isExtended: {boolean} True if a special key
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventScanCode", "RDPServerObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventScanCode", "RDPServerObserver"))
|
||||||
|
|
||||||
def onKeyEventUnicode(self, code, isPressed):
|
def onKeyEventUnicode(self, code, isPressed):
|
||||||
"""
|
"""
|
||||||
Event call when a keyboard event is catch in unicode format
|
@summary: Event call when a keyboard event is catch in unicode format
|
||||||
@param code: unicode of key
|
@param code: unicode of key
|
||||||
@param isPressed: True if key is down
|
@param isPressed: True if key is down
|
||||||
"""
|
"""
|
||||||
@@ -571,7 +695,7 @@ class RDPServerObserver(object):
|
|||||||
|
|
||||||
def onPointerEvent(self, x, y, button, isPressed):
|
def onPointerEvent(self, x, y, button, isPressed):
|
||||||
"""
|
"""
|
||||||
Event call on mouse event
|
@summary: Event call on mouse event
|
||||||
@param x: x position
|
@param x: x position
|
||||||
@param y: y position
|
@param y: y position
|
||||||
@param button: 1, 2 or 3 button
|
@param button: 1, 2 or 3 button
|
||||||
|
|||||||
734
rdpy/protocol/rdp/sec.py
Normal file
734
rdpy/protocol/rdp/sec.py
Normal file
@@ -0,0 +1,734 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
RDP Standard security layer
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sha, md5
|
||||||
|
import lic, tpkt
|
||||||
|
from t125 import gcc, mcs
|
||||||
|
from rdpy.core.type import CompositeType, CallableValue, Stream, UInt32Le, UInt16Le, String, sizeof
|
||||||
|
from rdpy.core.layer import LayerAutomata, IStreamSender
|
||||||
|
from rdpy.core.error import InvalidExpectedDataException
|
||||||
|
from rdpy.core import log
|
||||||
|
from rdpy.security import rc4
|
||||||
|
import rdpy.security.rsa_wrapper as rsa
|
||||||
|
|
||||||
|
class SecurityFlag(object):
|
||||||
|
"""
|
||||||
|
@summary: Microsoft security flags
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240579.aspx
|
||||||
|
"""
|
||||||
|
SEC_EXCHANGE_PKT = 0x0001
|
||||||
|
SEC_TRANSPORT_REQ = 0x0002
|
||||||
|
RDP_SEC_TRANSPORT_RSP = 0x0004
|
||||||
|
SEC_ENCRYPT = 0x0008
|
||||||
|
SEC_RESET_SEQNO = 0x0010
|
||||||
|
SEC_IGNORE_SEQNO = 0x0020
|
||||||
|
SEC_INFO_PKT = 0x0040
|
||||||
|
SEC_LICENSE_PKT = 0x0080
|
||||||
|
SEC_LICENSE_ENCRYPT_CS = 0x0200
|
||||||
|
SEC_LICENSE_ENCRYPT_SC = 0x0200
|
||||||
|
SEC_REDIRECTION_PKT = 0x0400
|
||||||
|
SEC_SECURE_CHECKSUM = 0x0800
|
||||||
|
SEC_AUTODETECT_REQ = 0x1000
|
||||||
|
SEC_AUTODETECT_RSP = 0x2000
|
||||||
|
SEC_HEARTBEAT = 0x4000
|
||||||
|
SEC_FLAGSHI_VALID = 0x8000
|
||||||
|
|
||||||
|
class InfoFlag(object):
|
||||||
|
"""
|
||||||
|
Client capabilities informations
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240475.aspx
|
||||||
|
"""
|
||||||
|
INFO_MOUSE = 0x00000001
|
||||||
|
INFO_DISABLECTRLALTDEL = 0x00000002
|
||||||
|
INFO_AUTOLOGON = 0x00000008
|
||||||
|
INFO_UNICODE = 0x00000010
|
||||||
|
INFO_MAXIMIZESHELL = 0x00000020
|
||||||
|
INFO_LOGONNOTIFY = 0x00000040
|
||||||
|
INFO_COMPRESSION = 0x00000080
|
||||||
|
INFO_ENABLEWINDOWSKEY = 0x00000100
|
||||||
|
INFO_REMOTECONSOLEAUDIO = 0x00002000
|
||||||
|
INFO_FORCE_ENCRYPTED_CS_PDU = 0x00004000
|
||||||
|
INFO_RAIL = 0x00008000
|
||||||
|
INFO_LOGONERRORS = 0x00010000
|
||||||
|
INFO_MOUSE_HAS_WHEEL = 0x00020000
|
||||||
|
INFO_PASSWORD_IS_SC_PIN = 0x00040000
|
||||||
|
INFO_NOAUDIOPLAYBACK = 0x00080000
|
||||||
|
INFO_USING_SAVED_CREDS = 0x00100000
|
||||||
|
INFO_AUDIOCAPTURE = 0x00200000
|
||||||
|
INFO_VIDEO_DISABLE = 0x00400000
|
||||||
|
INFO_CompressionTypeMask = 0x00001E00
|
||||||
|
|
||||||
|
class PerfFlag(object):
|
||||||
|
"""
|
||||||
|
Network performances flag
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240476.aspx
|
||||||
|
"""
|
||||||
|
PERF_DISABLE_WALLPAPER = 0x00000001
|
||||||
|
PERF_DISABLE_FULLWINDOWDRAG = 0x00000002
|
||||||
|
PERF_DISABLE_MENUANIMATIONS = 0x00000004
|
||||||
|
PERF_DISABLE_THEMING = 0x00000008
|
||||||
|
PERF_DISABLE_CURSOR_SHADOW = 0x00000020
|
||||||
|
PERF_DISABLE_CURSORSETTINGS = 0x00000040
|
||||||
|
PERF_ENABLE_FONT_SMOOTHING = 0x00000080
|
||||||
|
PERF_ENABLE_DESKTOP_COMPOSITION = 0x00000100
|
||||||
|
|
||||||
|
class AfInet(object):
|
||||||
|
"""
|
||||||
|
IPv4 or IPv6 address style
|
||||||
|
"""
|
||||||
|
AF_INET = 0x00002
|
||||||
|
AF_INET6 = 0x0017
|
||||||
|
|
||||||
|
def saltedHash(inputData, salt, salt1, salt2):
|
||||||
|
"""
|
||||||
|
@summary: Generate particular signature from combination of sha1 and md5
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241992.aspx
|
||||||
|
@param inputData: strange input (see doc)
|
||||||
|
@param salt: salt for context call
|
||||||
|
@param salt1: another salt (ex : client random)
|
||||||
|
@param salt2: another another salt (ex: server random)
|
||||||
|
@return : MD5(Salt + SHA1(Input + Salt + Salt1 + Salt2))
|
||||||
|
"""
|
||||||
|
sha1Digest = sha.new()
|
||||||
|
md5Digest = md5.new()
|
||||||
|
|
||||||
|
sha1Digest.update(inputData)
|
||||||
|
sha1Digest.update(salt[:48])
|
||||||
|
sha1Digest.update(salt1)
|
||||||
|
sha1Digest.update(salt2)
|
||||||
|
sha1Sig = sha1Digest.digest()
|
||||||
|
|
||||||
|
md5Digest.update(salt[:48])
|
||||||
|
md5Digest.update(sha1Sig)
|
||||||
|
|
||||||
|
return md5Digest.digest()
|
||||||
|
|
||||||
|
def finalHash(key, random1, random2):
|
||||||
|
"""
|
||||||
|
@summary: MD5(in0[:16] + in1[:32] + in2[:32])
|
||||||
|
@param key: in 16
|
||||||
|
@param random1: in 32
|
||||||
|
@param random2: in 32
|
||||||
|
@return MD5(in0[:16] + in1[:32] + in2[:32])
|
||||||
|
"""
|
||||||
|
md5Digest = md5.new()
|
||||||
|
md5Digest.update(key)
|
||||||
|
md5Digest.update(random1)
|
||||||
|
md5Digest.update(random2)
|
||||||
|
return md5Digest.digest()
|
||||||
|
|
||||||
|
def masterSecret(secret, random1, random2):
|
||||||
|
"""
|
||||||
|
@summary: Generate master secret
|
||||||
|
@param secret: {str} secret
|
||||||
|
@param clientRandom : {str} client random
|
||||||
|
@param serverRandom : {str} server random
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241992.aspx
|
||||||
|
"""
|
||||||
|
return saltedHash("A", secret, random1, random2) + saltedHash("BB", secret, random1, random2) + saltedHash("CCC", secret, random1, random2)
|
||||||
|
|
||||||
|
def sessionKeyBlob(secret, random1, random2):
|
||||||
|
"""
|
||||||
|
@summary: Generate master secret
|
||||||
|
@param secret: secret
|
||||||
|
@param clientRandom : client random
|
||||||
|
@param serverRandom : server random
|
||||||
|
"""
|
||||||
|
return saltedHash("X", secret, random1, random2) + saltedHash("YY", secret, random1, random2) + saltedHash("ZZZ", secret, random1, random2)
|
||||||
|
|
||||||
|
def macData(macSaltKey, data):
|
||||||
|
"""
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241995.aspx
|
||||||
|
@param macSaltKey: {str} mac key
|
||||||
|
@param data: {str} data to sign
|
||||||
|
@return: {str} signature
|
||||||
|
"""
|
||||||
|
sha1Digest = sha.new()
|
||||||
|
md5Digest = md5.new()
|
||||||
|
|
||||||
|
#encode length
|
||||||
|
dataLength = Stream()
|
||||||
|
dataLength.writeType(UInt32Le(len(data)))
|
||||||
|
|
||||||
|
sha1Digest.update(macSaltKey)
|
||||||
|
sha1Digest.update("\x36" * 40)
|
||||||
|
sha1Digest.update(dataLength.getvalue())
|
||||||
|
sha1Digest.update(data)
|
||||||
|
|
||||||
|
sha1Sig = sha1Digest.digest()
|
||||||
|
|
||||||
|
md5Digest.update(macSaltKey)
|
||||||
|
md5Digest.update("\x5c" * 48)
|
||||||
|
md5Digest.update(sha1Sig)
|
||||||
|
|
||||||
|
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.writeType(UInt32Le(len(data)))
|
||||||
|
|
||||||
|
encryptionCountS = Stream()
|
||||||
|
encryptionCountS.writeType(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
|
||||||
|
@param initialKey: {str} key computed first time
|
||||||
|
@param currentKey: {str} key actually used
|
||||||
|
@return: {str} temp key
|
||||||
|
"""
|
||||||
|
sha1Digest = sha.new()
|
||||||
|
md5Digest = md5.new()
|
||||||
|
|
||||||
|
sha1Digest.update(initialKey)
|
||||||
|
sha1Digest.update("\x36" * 40)
|
||||||
|
sha1Digest.update(currentKey)
|
||||||
|
|
||||||
|
sha1Sig = sha1Digest.digest()
|
||||||
|
|
||||||
|
md5Digest.update(initialKey)
|
||||||
|
md5Digest.update("\x5c" * 48)
|
||||||
|
md5Digest.update(sha1Sig)
|
||||||
|
|
||||||
|
return md5Digest.digest()
|
||||||
|
|
||||||
|
def gen40bits(data):
|
||||||
|
"""
|
||||||
|
@summary: generate 40 bits data from 128 bits data
|
||||||
|
@param data: {str} 128 bits data
|
||||||
|
@return: {str} 40 bits data
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240785.aspx
|
||||||
|
"""
|
||||||
|
return "\xd1\x26\x9e" + data[:8][-5:]
|
||||||
|
|
||||||
|
def gen56bits(data):
|
||||||
|
"""
|
||||||
|
@summary: generate 56 bits data from 128 bits data
|
||||||
|
@param data: {str} 128 bits data
|
||||||
|
@return: {str} 56 bits data
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240785.aspx
|
||||||
|
"""
|
||||||
|
return "\xd1" + data[:8][-7:]
|
||||||
|
|
||||||
|
def generateKeys(clientRandom, serverRandom, method):
|
||||||
|
"""
|
||||||
|
@param method: {gcc.Encryption}
|
||||||
|
@param clientRandom: {str[32]} client random
|
||||||
|
@param serverRandom: {str[32]} server random
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240785.aspx
|
||||||
|
@return: MACKey, initialFirstKey128(ClientdecryptKey, serverEncryptKey), initialSecondKey128(ServerDecryptKey, ClientEncryptKey)
|
||||||
|
"""
|
||||||
|
preMasterHash = clientRandom[:24] + serverRandom[:24]
|
||||||
|
masterHash = masterSecret(preMasterHash, clientRandom, serverRandom)
|
||||||
|
sessionKey = sessionKeyBlob(masterHash, clientRandom, serverRandom)
|
||||||
|
macKey128 = sessionKey[:16]
|
||||||
|
initialFirstKey128 = finalHash(sessionKey[16:32], clientRandom, serverRandom)
|
||||||
|
initialSecondKey128 = finalHash(sessionKey[32:48], clientRandom, serverRandom)
|
||||||
|
|
||||||
|
#generate valid key
|
||||||
|
if method == gcc.EncryptionMethod.ENCRYPTION_FLAG_40BIT:
|
||||||
|
return gen40bits(macKey128), gen40bits(initialFirstKey128), gen40bits(initialSecondKey128)
|
||||||
|
|
||||||
|
elif method == gcc.EncryptionMethod.ENCRYPTION_FLAG_56BIT:
|
||||||
|
return gen56bits(macKey128), gen56bits(initialFirstKey128), gen56bits(initialSecondKey128)
|
||||||
|
|
||||||
|
elif method == gcc.EncryptionMethod.ENCRYPTION_FLAG_128BIT:
|
||||||
|
return macKey128, initialFirstKey128, initialSecondKey128
|
||||||
|
|
||||||
|
raise InvalidExpectedDataException("Bad encryption method")
|
||||||
|
|
||||||
|
def updateKey(initialKey, currentKey, method):
|
||||||
|
"""
|
||||||
|
@summary: update session key
|
||||||
|
@param initialKey: {str} Initial key
|
||||||
|
@param currentKey: {str} Current key
|
||||||
|
@return newKey: {str} key to use
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240792.aspx
|
||||||
|
"""
|
||||||
|
#generate valid key
|
||||||
|
if method == gcc.EncryptionMethod.ENCRYPTION_FLAG_40BIT:
|
||||||
|
tempKey128 = tempKey(initialKey[:8], currentKey[:8])
|
||||||
|
return gen40bits(rc4.crypt(rc4.RC4Key(tempKey128[:8]), tempKey128[:8]))
|
||||||
|
|
||||||
|
elif method == gcc.EncryptionMethod.ENCRYPTION_FLAG_56BIT:
|
||||||
|
tempKey128 = tempKey(initialKey[:8], currentKey[:8])
|
||||||
|
return gen56bits(rc4.crypt(rc4.RC4Key(tempKey128[:8]), tempKey128[:8]))
|
||||||
|
|
||||||
|
elif method == gcc.EncryptionMethod.ENCRYPTION_FLAG_128BIT:
|
||||||
|
tempKey128 = tempKey(initialKey, currentKey)
|
||||||
|
return rc4.crypt(rc4.RC4Key(tempKey128), tempKey128)
|
||||||
|
|
||||||
|
class ClientSecurityExchangePDU(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: contain client random for basic security
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240472.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.length = UInt32Le(lambda:(sizeof(self) - 4))
|
||||||
|
self.encryptedClientRandom = String(readLen = CallableValue(lambda:(self.length.value - 8)))
|
||||||
|
self.padding = String("\x00" * 8, readLen = CallableValue(8))
|
||||||
|
|
||||||
|
class RDPInfo(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Client informations
|
||||||
|
Contains credentials (very important packet)
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240475.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self, extendedInfoConditional):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
#code page
|
||||||
|
self.codePage = UInt32Le()
|
||||||
|
#support flag
|
||||||
|
self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL | 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 = CallableValue(lambda:self.cbDomain.value + 2), unicode = True)
|
||||||
|
self.userName = String(readLen = CallableValue(lambda:self.cbUserName.value + 2), unicode = True)
|
||||||
|
self.password = String(readLen = CallableValue(lambda:self.cbPassword.value + 2), unicode = True)
|
||||||
|
#shell execute at start of session
|
||||||
|
self.alternateShell = String(readLen = CallableValue(lambda:self.cbAlternateShell.value + 2), unicode = True)
|
||||||
|
#working directory for session
|
||||||
|
self.workingDir = String(readLen = CallableValue(lambda:self.cbWorkingDir.value + 2), unicode = True)
|
||||||
|
self.extendedInfo = RDPExtendedInfo(conditional = extendedInfoConditional)
|
||||||
|
|
||||||
|
class RDPExtendedInfo(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Add more client informations
|
||||||
|
"""
|
||||||
|
def __init__(self, conditional):
|
||||||
|
CompositeType.__init__(self, conditional = conditional)
|
||||||
|
self.clientAddressFamily = UInt16Le(AfInet.AF_INET)
|
||||||
|
self.cbClientAddress = UInt16Le(lambda:sizeof(self.clientAddress))
|
||||||
|
self.clientAddress = String(readLen = self.cbClientAddress, unicode = True)
|
||||||
|
self.cbClientDir = UInt16Le(lambda:sizeof(self.clientDir))
|
||||||
|
self.clientDir = String(readLen = self.cbClientDir, unicode = True)
|
||||||
|
#TODO make tiomezone
|
||||||
|
self.clientTimeZone = String("\x00" * 172)
|
||||||
|
self.clientSessionId = UInt32Le()
|
||||||
|
self.performanceFlags = UInt32Le()
|
||||||
|
|
||||||
|
class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastPathSender, mcs.IGCCConfig):
|
||||||
|
"""
|
||||||
|
@summary: Standard RDP security layer
|
||||||
|
This layer is Transparent as possible for upper layer
|
||||||
|
"""
|
||||||
|
def __init__(self, presentation):
|
||||||
|
"""
|
||||||
|
@param presentation: Layer (generally pdu layer)
|
||||||
|
"""
|
||||||
|
LayerAutomata.__init__(self, presentation)
|
||||||
|
#thios layer is like a fastpath proxy
|
||||||
|
self._fastPathTransport = None
|
||||||
|
self._fastPathPresentation = None
|
||||||
|
|
||||||
|
#credentials
|
||||||
|
self._info = RDPInfo(extendedInfoConditional = lambda:(self.getGCCServerSettings().SC_CORE.rdpVersion.value == gcc.Version.RDP_VERSION_5_PLUS))
|
||||||
|
|
||||||
|
#True if classic encryption is enable
|
||||||
|
self._enableEncryption = False
|
||||||
|
|
||||||
|
#Enable Secure Mac generation
|
||||||
|
self._enableSecureCheckSum = False
|
||||||
|
|
||||||
|
#initialise decrypt and encrypt keys
|
||||||
|
self._macKey = None
|
||||||
|
self._initialDecrytKey = None
|
||||||
|
self._initialEncryptKey = None
|
||||||
|
self._currentDecrytKey = None
|
||||||
|
self._currentEncryptKey = None
|
||||||
|
|
||||||
|
#counter before update
|
||||||
|
self._nbEncryptedPacket = 0
|
||||||
|
self._nbDecryptedPacket = 0
|
||||||
|
|
||||||
|
#current rc4 tab
|
||||||
|
self._decryptRc4 = None
|
||||||
|
self._encryptRc4 = None
|
||||||
|
|
||||||
|
|
||||||
|
def readEncryptedPayload(self, s, 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
|
||||||
|
if self._nbDecryptedPacket == 4096:
|
||||||
|
log.debug("update decrypt key")
|
||||||
|
self._currentDecrytKey = updateKey( self._initialDecrytKey, self._currentDecrytKey,
|
||||||
|
self.getGCCServerSettings().SC_SECURITY.encryptionMethod.value)
|
||||||
|
self._decryptRc4 = rc4.RC4Key(self._currentDecrytKey)
|
||||||
|
self._nbDecryptedPacket = 0
|
||||||
|
|
||||||
|
signature = String(readLen = CallableValue(8))
|
||||||
|
encryptedPayload = String()
|
||||||
|
s.readType((signature, encryptedPayload))
|
||||||
|
decrypted = rc4.crypt(self._decryptRc4, encryptedPayload.value)
|
||||||
|
|
||||||
|
#ckeck 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, saltedMacGeneration):
|
||||||
|
"""
|
||||||
|
@summary: sign and crypt data
|
||||||
|
@param data: {Type} raw stream
|
||||||
|
@param saltedMacGeneration: {bool} use salted mac generation
|
||||||
|
@return: {Tuple} (signature, encryptedData)
|
||||||
|
"""
|
||||||
|
if self._nbEncryptedPacket == 4096:
|
||||||
|
log.debug("update encrypt key")
|
||||||
|
self._currentEncryptKey = updateKey( self._initialEncryptKey, self._currentEncryptKey,
|
||||||
|
self.getGCCServerSettings().SC_SECURITY.encryptionMethod.value)
|
||||||
|
self._encryptRc4 = rc4.RC4Key(self._currentEncryptKey)
|
||||||
|
self._nbEncryptedPacket = 0
|
||||||
|
|
||||||
|
self._nbEncryptedPacket += 1
|
||||||
|
|
||||||
|
s = Stream()
|
||||||
|
s.writeType(data)
|
||||||
|
|
||||||
|
if saltedMacGeneration:
|
||||||
|
return (String(macSaltedData(self._macKey, s.getvalue(), self._nbEncryptedPacket - 1)[:8]), String(rc4.crypt(self._encryptRc4, s.getvalue())))
|
||||||
|
else:
|
||||||
|
return (String(macData(self._macKey, s.getvalue())[:8]), String(rc4.crypt(self._encryptRc4, s.getvalue())))
|
||||||
|
|
||||||
|
def recv(self, data):
|
||||||
|
"""
|
||||||
|
@summary: if basic RDP security layer is activate decrypt
|
||||||
|
else pass to upper layer
|
||||||
|
@param data : {Stream} input Stream
|
||||||
|
"""
|
||||||
|
if not self._enableEncryption:
|
||||||
|
self._presentation.recv(data)
|
||||||
|
return
|
||||||
|
|
||||||
|
securityFlag = UInt16Le()
|
||||||
|
securityFlagHi = UInt16Le()
|
||||||
|
data.readType((securityFlag, securityFlagHi))
|
||||||
|
|
||||||
|
if securityFlag.value & SecurityFlag.SEC_ENCRYPT:
|
||||||
|
data = self.readEncryptedPayload(data, securityFlag.value & SecurityFlag.SEC_SECURE_CHECKSUM)
|
||||||
|
|
||||||
|
self._presentation.recv(data)
|
||||||
|
|
||||||
|
def send(self, data):
|
||||||
|
"""
|
||||||
|
@summary: if basic RDP security layer is activate encrypt
|
||||||
|
else pass to upper layer
|
||||||
|
@param data: {Type | Tuple}
|
||||||
|
"""
|
||||||
|
if not self._enableEncryption:
|
||||||
|
self._transport.send(data)
|
||||||
|
return
|
||||||
|
|
||||||
|
flag = SecurityFlag.SEC_ENCRYPT
|
||||||
|
|
||||||
|
if self._enableSecureCheckSum:
|
||||||
|
flag |= SecurityFlag.SEC_SECURE_CHECKSUM
|
||||||
|
|
||||||
|
self.sendFlagged(flag, data)
|
||||||
|
|
||||||
|
def sendFlagged(self, flag, data):
|
||||||
|
"""
|
||||||
|
@summary: explicit send flag method for particular packet
|
||||||
|
(info packet or license packet)
|
||||||
|
If encryption is enable apply it
|
||||||
|
@param flag: {integer} security flag
|
||||||
|
@param data: {Type | Tuple}
|
||||||
|
"""
|
||||||
|
if flag & SecurityFlag.SEC_ENCRYPT:
|
||||||
|
data = self.writeEncryptedPayload(data, flag & SecurityFlag.SEC_SECURE_CHECKSUM)
|
||||||
|
self._transport.send((UInt16Le(flag), UInt16Le(), data))
|
||||||
|
|
||||||
|
def recvFastPath(self, secFlag, fastPathS):
|
||||||
|
"""
|
||||||
|
@summary: Call when fast path packet is received
|
||||||
|
@param secFlag: {SecFlags}
|
||||||
|
@param fastPathS: {Stream}
|
||||||
|
"""
|
||||||
|
if self._enableEncryption and secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED:
|
||||||
|
fastPathS = self.readEncryptedPayload(fastPathS, secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_SECURE_CHECKSUM)
|
||||||
|
|
||||||
|
self._fastPathPresentation.recvFastPath(secFlag, fastPathS)
|
||||||
|
|
||||||
|
def setFastPathListener(self, fastPathListener):
|
||||||
|
"""
|
||||||
|
@param fastPathListener : {IFastPathListener}
|
||||||
|
"""
|
||||||
|
self._fastPathPresentation = fastPathListener
|
||||||
|
|
||||||
|
def sendFastPath(self, secFlag, fastPathS):
|
||||||
|
"""
|
||||||
|
@summary: Send fastPathS Type as fast path packet
|
||||||
|
@param secFlag: {SecFlags}
|
||||||
|
@param fastPathS: {Stream} type transform to stream and send as fastpath
|
||||||
|
"""
|
||||||
|
if self._enableEncryption:
|
||||||
|
secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED
|
||||||
|
|
||||||
|
if self._enableSecureCheckSum:
|
||||||
|
secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_SECURE_CHECKSUM
|
||||||
|
|
||||||
|
fastPathS = self.writeEncryptedPayload(fastPathS, self._enableSecureCheckSum)
|
||||||
|
|
||||||
|
self._fastPathTransport.sendFastPath(secFlag, fastPathS)
|
||||||
|
|
||||||
|
def setFastPathSender(self, fastPathSender):
|
||||||
|
"""
|
||||||
|
@param fastPathSender: {tpkt.FastPathSender}
|
||||||
|
"""
|
||||||
|
self._fastPathTransport = fastPathSender
|
||||||
|
|
||||||
|
def getUserId(self):
|
||||||
|
"""
|
||||||
|
@return: {integer} mcs user id
|
||||||
|
@see: mcs.IGCCConfig
|
||||||
|
"""
|
||||||
|
return self._transport.getUserId()
|
||||||
|
|
||||||
|
def getChannelId(self):
|
||||||
|
"""
|
||||||
|
@return: {integer} return channel id of proxy
|
||||||
|
@see: mcs.IGCCConfig
|
||||||
|
"""
|
||||||
|
return self._transport.getChannelId()
|
||||||
|
|
||||||
|
def getGCCClientSettings(self):
|
||||||
|
"""
|
||||||
|
@return: {gcc.Settings} mcs layer gcc client settings
|
||||||
|
@see: mcs.IGCCConfig
|
||||||
|
"""
|
||||||
|
return self._transport.getGCCClientSettings()
|
||||||
|
|
||||||
|
def getGCCServerSettings(self):
|
||||||
|
"""
|
||||||
|
@return: {gcc.Settings} mcs layer gcc server settings
|
||||||
|
@see: mcs.IGCCConfig
|
||||||
|
"""
|
||||||
|
return self._transport.getGCCServerSettings()
|
||||||
|
|
||||||
|
class Client(SecLayer):
|
||||||
|
"""
|
||||||
|
@summary: Client side of security layer
|
||||||
|
"""
|
||||||
|
def __init__(self, presentation):
|
||||||
|
SecLayer.__init__(self, presentation)
|
||||||
|
self._licenceManager = lic.LicenseManager(self)
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""
|
||||||
|
@summary: send client random if needed and send info packet
|
||||||
|
"""
|
||||||
|
self._enableEncryption = self.getGCCClientSettings().CS_CORE.serverSelectedProtocol == 0
|
||||||
|
|
||||||
|
if self._enableEncryption:
|
||||||
|
self.sendClientRandom()
|
||||||
|
|
||||||
|
self.sendInfoPkt()
|
||||||
|
|
||||||
|
def sendInfoPkt(self):
|
||||||
|
"""
|
||||||
|
@summary: send information packet (with credentials)
|
||||||
|
next state -> recvLicenceInfo
|
||||||
|
"""
|
||||||
|
secFlag = SecurityFlag.SEC_INFO_PKT
|
||||||
|
if self._enableEncryption:
|
||||||
|
secFlag |= SecurityFlag.SEC_ENCRYPT
|
||||||
|
self.sendFlagged(secFlag, self._info)
|
||||||
|
|
||||||
|
self.setNextState(self.recvLicenceInfo)
|
||||||
|
|
||||||
|
def sendClientRandom(self):
|
||||||
|
"""
|
||||||
|
@summary: generate and send client random and init session keys
|
||||||
|
"""
|
||||||
|
#generate client random
|
||||||
|
clientRandom = rsa.random(256)
|
||||||
|
self._macKey, self._initialDecrytKey, self._initialEncryptKey = generateKeys( clientRandom,
|
||||||
|
self.getGCCServerSettings().SC_SECURITY.serverRandom.value,
|
||||||
|
self.getGCCServerSettings().SC_SECURITY.encryptionMethod.value)
|
||||||
|
#initialize keys
|
||||||
|
self._currentDecrytKey = self._initialDecrytKey
|
||||||
|
self._currentEncryptKey = self._initialEncryptKey
|
||||||
|
self._decryptRc4 = rc4.RC4Key(self._currentDecrytKey)
|
||||||
|
self._encryptRc4 = rc4.RC4Key(self._currentEncryptKey)
|
||||||
|
|
||||||
|
#verify certificate
|
||||||
|
if not self.getGCCServerSettings().SC_SECURITY.serverCertificate.certData.verify():
|
||||||
|
log.warning("cannot verify server identity")
|
||||||
|
#send client random encrypted with
|
||||||
|
serverPublicKey = self.getGCCServerSettings().SC_SECURITY.serverCertificate.certData.getPublicKey()
|
||||||
|
message = ClientSecurityExchangePDU()
|
||||||
|
#reverse because bignum in little endian
|
||||||
|
message.encryptedClientRandom.value = rsa.encrypt(clientRandom[::-1], serverPublicKey)[::-1]
|
||||||
|
self.sendFlagged(SecurityFlag.SEC_EXCHANGE_PKT, message)
|
||||||
|
|
||||||
|
def recvLicenceInfo(self, s):
|
||||||
|
"""
|
||||||
|
@summary: Read license info packet and check if is a valid client info
|
||||||
|
Wait Demand Active PDU
|
||||||
|
@param s: Stream
|
||||||
|
"""
|
||||||
|
#packet preambule
|
||||||
|
securityFlag = UInt16Le()
|
||||||
|
securityFlagHi = UInt16Le()
|
||||||
|
s.readType((securityFlag, securityFlagHi))
|
||||||
|
|
||||||
|
if not (securityFlag.value & SecurityFlag.SEC_LICENSE_PKT):
|
||||||
|
raise InvalidExpectedDataException("waiting license packet")
|
||||||
|
|
||||||
|
if self._licenceManager.recv(s):
|
||||||
|
self.setNextState()
|
||||||
|
#end of connection step of
|
||||||
|
self._presentation.connect()
|
||||||
|
|
||||||
|
class Server(SecLayer):
|
||||||
|
"""
|
||||||
|
@summary: Client side of security layer
|
||||||
|
"""
|
||||||
|
def __init__(self, presentation):
|
||||||
|
"""
|
||||||
|
@param presentation: {Layer}
|
||||||
|
"""
|
||||||
|
SecLayer.__init__(self, presentation)
|
||||||
|
self._rsaPublicKey, self._rsaPrivateKey = rsa.newkeys(512)
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""
|
||||||
|
@summary: init automata to wait info packet
|
||||||
|
"""
|
||||||
|
self._enableEncryption = self.getGCCClientSettings().CS_CORE.serverSelectedProtocol == 0
|
||||||
|
if self._enableEncryption:
|
||||||
|
self.setNextState(self.recvClientRandom)
|
||||||
|
else:
|
||||||
|
self.setNextState(self.recvInfoPkt)
|
||||||
|
|
||||||
|
def getCertificate(self):
|
||||||
|
"""
|
||||||
|
@summary: generate proprietary certificate from rsa public key
|
||||||
|
"""
|
||||||
|
certificate = gcc.ProprietaryServerCertificate()
|
||||||
|
certificate.PublicKeyBlob.modulus.value = rsa.int2bytes(self._rsaPublicKey.n)[::-1]
|
||||||
|
certificate.PublicKeyBlob.pubExp.value = self._rsaPublicKey.e
|
||||||
|
certificate.sign()
|
||||||
|
return gcc.ServerCertificate(certificate)
|
||||||
|
|
||||||
|
def recvClientRandom(self, s):
|
||||||
|
"""
|
||||||
|
@summary: receive client random and generate session keys
|
||||||
|
@param s: {Stream}
|
||||||
|
"""
|
||||||
|
#packet preambule
|
||||||
|
securityFlag = UInt16Le()
|
||||||
|
securityFlagHi = UInt16Le()
|
||||||
|
s.readType((securityFlag, securityFlagHi))
|
||||||
|
|
||||||
|
if not (securityFlag.value & SecurityFlag.SEC_EXCHANGE_PKT):
|
||||||
|
raise InvalidExpectedDataException("waiting client random")
|
||||||
|
|
||||||
|
message = ClientSecurityExchangePDU()
|
||||||
|
s.readType(message)
|
||||||
|
clientRandom = rsa.decrypt(message.encryptedClientRandom.value[::-1], self._rsaPrivateKey)[::-1]
|
||||||
|
|
||||||
|
self._macKey, self._initialEncryptKey, self._initialDecrytKey = generateKeys( clientRandom,
|
||||||
|
self.getGCCServerSettings().SC_SECURITY.serverRandom.value,
|
||||||
|
self.getGCCServerSettings().SC_SECURITY.encryptionMethod.value)
|
||||||
|
#initialize keys
|
||||||
|
self._currentDecrytKey = self._initialDecrytKey
|
||||||
|
self._currentEncryptKey = self._initialEncryptKey
|
||||||
|
self._decryptRc4 = rc4.RC4Key(self._currentDecrytKey)
|
||||||
|
self._encryptRc4 = rc4.RC4Key(self._currentEncryptKey)
|
||||||
|
|
||||||
|
self.setNextState(self.recvInfoPkt)
|
||||||
|
|
||||||
|
def recvInfoPkt(self, s):
|
||||||
|
"""
|
||||||
|
@summary: receive info packet from client
|
||||||
|
Client credentials
|
||||||
|
Send License valid error message
|
||||||
|
Send Demand Active PDU
|
||||||
|
Wait Confirm Active PDU
|
||||||
|
@param s: {Stream}
|
||||||
|
"""
|
||||||
|
securityFlag = UInt16Le()
|
||||||
|
securityFlagHi = UInt16Le()
|
||||||
|
s.readType((securityFlag, securityFlagHi))
|
||||||
|
|
||||||
|
if not (securityFlag.value & SecurityFlag.SEC_INFO_PKT):
|
||||||
|
raise InvalidExpectedDataException("Waiting info packet")
|
||||||
|
|
||||||
|
if securityFlag.value & SecurityFlag.SEC_ENCRYPT:
|
||||||
|
s = self.readEncryptedPayload(s, securityFlag.value & SecurityFlag.SEC_SECURE_CHECKSUM)
|
||||||
|
|
||||||
|
s.readType(self._info)
|
||||||
|
#next state send error license
|
||||||
|
self.sendLicensingErrorMessage()
|
||||||
|
#reinit state
|
||||||
|
self.setNextState()
|
||||||
|
self._presentation.connect()
|
||||||
|
|
||||||
|
def sendLicensingErrorMessage(self):
|
||||||
|
"""
|
||||||
|
@summary: Send a licensing error data
|
||||||
|
"""
|
||||||
|
self.sendFlagged(SecurityFlag.SEC_LICENSE_PKT, lic.createValidClientLicensingErrorMessage())
|
||||||
0
rdpy/protocol/rdp/t125/__init__.py
Normal file
0
rdpy/protocol/rdp/t125/__init__.py
Normal file
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -22,8 +22,8 @@ Basic Encoding Rules use in RDP.
|
|||||||
ASN.1 standard
|
ASN.1 standard
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.network.type import UInt8, UInt16Be, UInt32Be, String
|
from rdpy.core.type import UInt8, UInt16Be, UInt32Be, String
|
||||||
from rdpy.base.error import InvalidExpectedDataException, InvalidSize
|
from rdpy.core.error import InvalidExpectedDataException, InvalidSize
|
||||||
|
|
||||||
class BerPc(object):
|
class BerPc(object):
|
||||||
BER_PC_MASK = 0x20
|
BER_PC_MASK = 0x20
|
||||||
@@ -50,7 +50,7 @@ class Tag(object):
|
|||||||
|
|
||||||
def berPC(pc):
|
def berPC(pc):
|
||||||
"""
|
"""
|
||||||
Return BER_CONSTRUCT if true
|
@summary: Return BER_CONSTRUCT if true
|
||||||
BER_PRIMITIVE if false
|
BER_PRIMITIVE if false
|
||||||
@param pc: boolean
|
@param pc: boolean
|
||||||
@return: BerPc value
|
@return: BerPc value
|
||||||
@@ -62,7 +62,7 @@ def berPC(pc):
|
|||||||
|
|
||||||
def readLength(s):
|
def readLength(s):
|
||||||
"""
|
"""
|
||||||
Read length of BER structure
|
@summary: Read length of BER structure
|
||||||
length be on 1 2 or 3 bytes
|
length be on 1 2 or 3 bytes
|
||||||
@param s: stream
|
@param s: stream
|
||||||
@return: int or Python long
|
@return: int or Python long
|
||||||
@@ -86,7 +86,7 @@ def readLength(s):
|
|||||||
|
|
||||||
def writeLength(size):
|
def writeLength(size):
|
||||||
"""
|
"""
|
||||||
Return structure length as expected in BER specification
|
@summary: Return structure length as expected in BER specification
|
||||||
@param size: int or python long
|
@param size: int or python long
|
||||||
@return: UInt8 or (UInt8(0x82), UInt16Be)
|
@return: UInt8 or (UInt8(0x82), UInt16Be)
|
||||||
"""
|
"""
|
||||||
@@ -97,7 +97,7 @@ def writeLength(size):
|
|||||||
|
|
||||||
def readUniversalTag(s, tag, pc):
|
def readUniversalTag(s, tag, pc):
|
||||||
"""
|
"""
|
||||||
Read tag of BER packet
|
@summary: Read tag of BER packet
|
||||||
@param tag: Tag class attributes
|
@param tag: Tag class attributes
|
||||||
@param pc: boolean
|
@param pc: boolean
|
||||||
@return: true if tag is correctly read
|
@return: true if tag is correctly read
|
||||||
@@ -108,7 +108,7 @@ def readUniversalTag(s, tag, pc):
|
|||||||
|
|
||||||
def writeUniversalTag(tag, pc):
|
def writeUniversalTag(tag, pc):
|
||||||
"""
|
"""
|
||||||
Return universal tag byte
|
@summary: Return universal tag byte
|
||||||
@param tag: tag class attributes
|
@param tag: tag class attributes
|
||||||
@param pc: boolean
|
@param pc: boolean
|
||||||
@return: UInt8
|
@return: UInt8
|
||||||
@@ -117,7 +117,7 @@ def writeUniversalTag(tag, pc):
|
|||||||
|
|
||||||
def readApplicationTag(s, tag):
|
def readApplicationTag(s, tag):
|
||||||
"""
|
"""
|
||||||
Read application tag
|
@summary: Read application tag
|
||||||
@param s: stream
|
@param s: stream
|
||||||
@param tag: tag class attributes
|
@param tag: tag class attributes
|
||||||
@return: length of application packet
|
@return: length of application packet
|
||||||
@@ -138,7 +138,7 @@ def readApplicationTag(s, tag):
|
|||||||
|
|
||||||
def writeApplicationTag(tag, size):
|
def writeApplicationTag(tag, size):
|
||||||
"""
|
"""
|
||||||
Return structure that represent BER application tag
|
@summary: Return structure that represent BER application tag
|
||||||
@param tag: int python that match an uint8(0xff)
|
@param tag: int python that match an uint8(0xff)
|
||||||
@param size: size to rest of packet
|
@param size: size to rest of packet
|
||||||
"""
|
"""
|
||||||
@@ -149,7 +149,7 @@ def writeApplicationTag(tag, size):
|
|||||||
|
|
||||||
def readBoolean(s):
|
def readBoolean(s):
|
||||||
"""
|
"""
|
||||||
Return boolean
|
@summary: Return boolean
|
||||||
@param s: stream
|
@param s: stream
|
||||||
@return: boolean
|
@return: boolean
|
||||||
"""
|
"""
|
||||||
@@ -164,7 +164,7 @@ def readBoolean(s):
|
|||||||
|
|
||||||
def writeBoolean(b):
|
def writeBoolean(b):
|
||||||
"""
|
"""
|
||||||
Return structure that represent boolean in BER specification
|
@summary: Return structure that represent boolean in BER specification
|
||||||
@param b: boolean
|
@param b: boolean
|
||||||
@return: BER boolean block
|
@return: BER boolean block
|
||||||
"""
|
"""
|
||||||
@@ -175,7 +175,7 @@ def writeBoolean(b):
|
|||||||
|
|
||||||
def readInteger(s):
|
def readInteger(s):
|
||||||
"""
|
"""
|
||||||
Read integer structure from stream
|
@summary: Read integer structure from stream
|
||||||
@param s: stream
|
@param s: stream
|
||||||
@return: int or long python
|
@return: int or long python
|
||||||
"""
|
"""
|
||||||
@@ -207,7 +207,7 @@ def readInteger(s):
|
|||||||
|
|
||||||
def writeInteger(value):
|
def writeInteger(value):
|
||||||
"""
|
"""
|
||||||
Write integer value
|
@summary: Write integer value
|
||||||
@param param: INT or Python long
|
@param param: INT or Python long
|
||||||
@return: BER integer block
|
@return: BER integer block
|
||||||
"""
|
"""
|
||||||
@@ -220,7 +220,7 @@ def writeInteger(value):
|
|||||||
|
|
||||||
def readOctetString(s):
|
def readOctetString(s):
|
||||||
"""
|
"""
|
||||||
Read BER string structure
|
@summary: Read BER string structure
|
||||||
@param s: stream
|
@param s: stream
|
||||||
@return: string python
|
@return: string python
|
||||||
"""
|
"""
|
||||||
@@ -231,7 +231,7 @@ def readOctetString(s):
|
|||||||
|
|
||||||
def writeOctetstring(value):
|
def writeOctetstring(value):
|
||||||
"""
|
"""
|
||||||
Write string in BER representation
|
@summary: Write string in BER representation
|
||||||
@param value: string
|
@param value: string
|
||||||
@return: BER octet string block
|
@return: BER octet string block
|
||||||
"""
|
"""
|
||||||
@@ -239,7 +239,7 @@ def writeOctetstring(value):
|
|||||||
|
|
||||||
def readEnumerated(s):
|
def readEnumerated(s):
|
||||||
"""
|
"""
|
||||||
Read enumerated structure
|
@summary: Read enumerated structure
|
||||||
@param s: Stream
|
@param s: Stream
|
||||||
@return: int or long
|
@return: int or long
|
||||||
"""
|
"""
|
||||||
@@ -253,7 +253,7 @@ def readEnumerated(s):
|
|||||||
|
|
||||||
def writeEnumerated(enumerated):
|
def writeEnumerated(enumerated):
|
||||||
"""
|
"""
|
||||||
Write enumerated structure
|
@summary: Write enumerated structure
|
||||||
@param s: Stream
|
@param s: Stream
|
||||||
@return: BER enumerated block
|
@return: BER enumerated block
|
||||||
"""
|
"""
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -22,10 +22,13 @@ Implement GCC structure use in RDP protocol
|
|||||||
http://msdn.microsoft.com/en-us/library/cc240508.aspx
|
http://msdn.microsoft.com/en-us/library/cc240508.aspx
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.network.type import UInt8, UInt16Le, UInt32Le, CompositeType, String, Stream, sizeof, FactoryType, ArrayType
|
import md5
|
||||||
|
from rdpy.core.type import UInt8, UInt16Le, UInt32Le, CompositeType, CallableValue, String, Stream, sizeof, FactoryType, ArrayType
|
||||||
import per, mcs
|
import per, mcs
|
||||||
from rdpy.base.error import InvalidExpectedDataException
|
from rdpy.core.error import InvalidExpectedDataException
|
||||||
import rdpy.base.log as log
|
from rdpy.core import log
|
||||||
|
from rdpy.security import x509
|
||||||
|
import rdpy.security.rsa_wrapper as rsa
|
||||||
|
|
||||||
t124_02_98_oid = ( 0, 0, 20, 124, 0, 1 )
|
t124_02_98_oid = ( 0, 0, 20, 124, 0, 1 )
|
||||||
|
|
||||||
@@ -34,7 +37,7 @@ h221_sc_key = "McDn";
|
|||||||
|
|
||||||
class MessageType(object):
|
class MessageType(object):
|
||||||
"""
|
"""
|
||||||
Server to Client block
|
@summary: Server to Client block
|
||||||
GCC conference messages
|
GCC conference messages
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240509.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240509.aspx
|
||||||
"""
|
"""
|
||||||
@@ -52,7 +55,7 @@ class MessageType(object):
|
|||||||
|
|
||||||
class ColorDepth(object):
|
class ColorDepth(object):
|
||||||
"""
|
"""
|
||||||
Depth color
|
@summary: Depth color
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
||||||
"""
|
"""
|
||||||
RNS_UD_COLOR_8BPP = 0xCA01
|
RNS_UD_COLOR_8BPP = 0xCA01
|
||||||
@@ -62,7 +65,7 @@ class ColorDepth(object):
|
|||||||
|
|
||||||
class HighColor(object):
|
class HighColor(object):
|
||||||
"""
|
"""
|
||||||
High color of client
|
@summary: High color of client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
||||||
"""
|
"""
|
||||||
HIGH_COLOR_4BPP = 0x0004
|
HIGH_COLOR_4BPP = 0x0004
|
||||||
@@ -73,7 +76,7 @@ class HighColor(object):
|
|||||||
|
|
||||||
class Support(object):
|
class Support(object):
|
||||||
"""
|
"""
|
||||||
Supported depth flag
|
@summary: Supported depth flag
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
||||||
"""
|
"""
|
||||||
RNS_UD_24BPP_SUPPORT = 0x0001
|
RNS_UD_24BPP_SUPPORT = 0x0001
|
||||||
@@ -83,7 +86,7 @@ class Support(object):
|
|||||||
|
|
||||||
class CapabilityFlags(object):
|
class CapabilityFlags(object):
|
||||||
"""
|
"""
|
||||||
For more details on each flags click above
|
@summary: For more details on each flags click above
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
||||||
"""
|
"""
|
||||||
RNS_UD_CS_SUPPORT_ERRINFO_PDU = 0x0001
|
RNS_UD_CS_SUPPORT_ERRINFO_PDU = 0x0001
|
||||||
@@ -100,7 +103,7 @@ class CapabilityFlags(object):
|
|||||||
|
|
||||||
class ConnectionType(object):
|
class ConnectionType(object):
|
||||||
"""
|
"""
|
||||||
This information is correct if
|
@summary: This information is correct if
|
||||||
RNS_UD_CS_VALID_CONNECTION_TYPE flag is set on capabilityFlag
|
RNS_UD_CS_VALID_CONNECTION_TYPE flag is set on capabilityFlag
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
||||||
"""
|
"""
|
||||||
@@ -114,7 +117,7 @@ class ConnectionType(object):
|
|||||||
|
|
||||||
class Version(object):
|
class Version(object):
|
||||||
"""
|
"""
|
||||||
Supported version of RDP
|
@summary: Supported version of RDP
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
||||||
"""
|
"""
|
||||||
RDP_VERSION_4 = 0x00080001
|
RDP_VERSION_4 = 0x00080001
|
||||||
@@ -123,20 +126,30 @@ class Version(object):
|
|||||||
class Sequence(object):
|
class Sequence(object):
|
||||||
RNS_UD_SAS_DEL = 0xAA03
|
RNS_UD_SAS_DEL = 0xAA03
|
||||||
|
|
||||||
class Encryption(object):
|
class EncryptionMethod(object):
|
||||||
"""
|
"""
|
||||||
Encryption methods supported
|
@summary: Encryption methods supported
|
||||||
@deprecated: because rdpy use SSL but need to send to server...
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240511.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240511.aspx
|
||||||
"""
|
"""
|
||||||
ENCRYPTION_FLAG_40BIT = 0x00000001
|
ENCRYPTION_FLAG_40BIT = 0x00000001
|
||||||
ENCRYPTION_FLAG_128BIT = 0x00000002
|
ENCRYPTION_FLAG_128BIT = 0x00000002
|
||||||
ENCRYPTION_FLAG_56BIT = 0x00000008
|
ENCRYPTION_FLAG_56BIT = 0x00000008
|
||||||
FIPS_ENCRYPTION_FLAG = 0x00000010
|
FIPS_ENCRYPTION_FLAG = 0x00000010
|
||||||
|
|
||||||
|
class EncryptionLevel(object):
|
||||||
|
"""
|
||||||
|
@summary: level of 'security'
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240518.aspx
|
||||||
|
"""
|
||||||
|
ENCRYPTION_LEVEL_NONE = 0x00000000
|
||||||
|
ENCRYPTION_LEVEL_LOW = 0x00000001
|
||||||
|
ENCRYPTION_LEVEL_CLIENT_COMPATIBLE = 0x00000002
|
||||||
|
ENCRYPTION_LEVEL_HIGH = 0x00000003
|
||||||
|
ENCRYPTION_LEVEL_FIPS = 0x00000004
|
||||||
|
|
||||||
class ChannelOptions(object):
|
class ChannelOptions(object):
|
||||||
"""
|
"""
|
||||||
Channel options
|
@summary: Channel options
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240513.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240513.aspx
|
||||||
"""
|
"""
|
||||||
CHANNEL_OPTION_INITIALIZED = 0x80000000
|
CHANNEL_OPTION_INITIALIZED = 0x80000000
|
||||||
@@ -153,8 +166,8 @@ class ChannelOptions(object):
|
|||||||
|
|
||||||
class KeyboardType(object):
|
class KeyboardType(object):
|
||||||
"""
|
"""
|
||||||
Keyboard type
|
@summary: Keyboard type
|
||||||
IBM_101_102_KEYS is the most common keyboard type
|
@see: IBM_101_102_KEYS is the most common keyboard type
|
||||||
"""
|
"""
|
||||||
IBM_PC_XT_83_KEY = 0x00000001
|
IBM_PC_XT_83_KEY = 0x00000001
|
||||||
OLIVETTI = 0x00000002
|
OLIVETTI = 0x00000002
|
||||||
@@ -166,7 +179,7 @@ class KeyboardType(object):
|
|||||||
|
|
||||||
class KeyboardLayout(object):
|
class KeyboardLayout(object):
|
||||||
"""
|
"""
|
||||||
Keyboard layout definition
|
@summary: Keyboard layout definition
|
||||||
@see: http://technet.microsoft.com/en-us/library/cc766503%28WS.10%29.aspx
|
@see: http://technet.microsoft.com/en-us/library/cc766503%28WS.10%29.aspx
|
||||||
"""
|
"""
|
||||||
ARABIC = 0x00000401
|
ARABIC = 0x00000401
|
||||||
@@ -189,9 +202,16 @@ class KeyboardLayout(object):
|
|||||||
DUTCH = 0x00000413
|
DUTCH = 0x00000413
|
||||||
NORWEGIAN = 0x00000414
|
NORWEGIAN = 0x00000414
|
||||||
|
|
||||||
|
class CertificateType(object):
|
||||||
|
"""
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240521.aspx
|
||||||
|
"""
|
||||||
|
CERT_CHAIN_VERSION_1 = 0x00000001
|
||||||
|
CERT_CHAIN_VERSION_2 = 0x00000002
|
||||||
|
|
||||||
class DataBlock(CompositeType):
|
class DataBlock(CompositeType):
|
||||||
"""
|
"""
|
||||||
Block settings
|
@summary: Block settings
|
||||||
"""
|
"""
|
||||||
def __init__(self, dataBlock = None):
|
def __init__(self, dataBlock = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
@@ -200,7 +220,7 @@ class DataBlock(CompositeType):
|
|||||||
|
|
||||||
def DataBlockFactory():
|
def DataBlockFactory():
|
||||||
"""
|
"""
|
||||||
build settings in accordance of type self.type.value
|
@summary: build settings in accordance of type self.type.value
|
||||||
"""
|
"""
|
||||||
for c in [ClientCoreData, ClientSecurityData, ClientNetworkData, ServerCoreData, ServerNetworkData, ServerSecurityData]:
|
for c in [ClientCoreData, ClientSecurityData, ClientNetworkData, ServerCoreData, ServerNetworkData, ServerSecurityData]:
|
||||||
if self.type.value == c._TYPE_:
|
if self.type.value == c._TYPE_:
|
||||||
@@ -218,7 +238,7 @@ class DataBlock(CompositeType):
|
|||||||
|
|
||||||
class ClientCoreData(CompositeType):
|
class ClientCoreData(CompositeType):
|
||||||
"""
|
"""
|
||||||
Class that represent core setting of client
|
@summary: Class that represent core setting of client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
||||||
"""
|
"""
|
||||||
_TYPE_ = MessageType.CS_CORE
|
_TYPE_ = MessageType.CS_CORE
|
||||||
@@ -230,27 +250,27 @@ class ClientCoreData(CompositeType):
|
|||||||
self.desktopHeight = UInt16Le(800)
|
self.desktopHeight = UInt16Le(800)
|
||||||
self.colorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP)
|
self.colorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP)
|
||||||
self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL)
|
self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL)
|
||||||
self.kbdLayout = UInt32Le(KeyboardLayout.FRENCH)
|
self.kbdLayout = UInt32Le(KeyboardLayout.US)
|
||||||
self.clientBuild = UInt32Le(3790)
|
self.clientBuild = UInt32Le(3790)
|
||||||
self.clientName = String("rdpy" + "\x00"*11, readLen = UInt8(32), unicode = True)
|
self.clientName = String("rdpy" + "\x00"*11, readLen = CallableValue(32), unicode = True)
|
||||||
self.keyboardType = UInt32Le(KeyboardType.IBM_101_102_KEYS)
|
self.keyboardType = UInt32Le(KeyboardType.IBM_101_102_KEYS)
|
||||||
self.keyboardSubType = UInt32Le(0)
|
self.keyboardSubType = UInt32Le(0)
|
||||||
self.keyboardFnKeys = UInt32Le(12)
|
self.keyboardFnKeys = UInt32Le(12)
|
||||||
self.imeFileName = String("\x00"*64, readLen = UInt8(64))
|
self.imeFileName = String("\x00"*64, readLen = CallableValue(64), optional = True)
|
||||||
self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP)
|
self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP, optional = True)
|
||||||
self.clientProductId = UInt16Le(1)
|
self.clientProductId = UInt16Le(1, optional = True)
|
||||||
self.serialNumber = UInt32Le(0)
|
self.serialNumber = UInt32Le(0, optional = True)
|
||||||
self.highColorDepth = UInt16Le(HighColor.HIGH_COLOR_24BPP)
|
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)
|
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)
|
self.earlyCapabilityFlags = UInt16Le(CapabilityFlags.RNS_UD_CS_SUPPORT_ERRINFO_PDU, optional = True)
|
||||||
self.clientDigProductId = String("\x00"*64, readLen = UInt8(64))
|
self.clientDigProductId = String("\x00"*64, readLen = CallableValue(64), optional = True)
|
||||||
self.connectionType = UInt8()
|
self.connectionType = UInt8(optional = True)
|
||||||
self.pad1octet = UInt8()
|
self.pad1octet = UInt8(optional = True)
|
||||||
self.serverSelectedProtocol = UInt32Le()
|
self.serverSelectedProtocol = UInt32Le(optional = True)
|
||||||
|
|
||||||
class ServerCoreData(CompositeType):
|
class ServerCoreData(CompositeType):
|
||||||
"""
|
"""
|
||||||
Server side core settings structure
|
@summary: Server side core settings structure
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240517.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240517.aspx
|
||||||
"""
|
"""
|
||||||
_TYPE_ = MessageType.SC_CORE
|
_TYPE_ = MessageType.SC_CORE
|
||||||
@@ -258,27 +278,24 @@ class ServerCoreData(CompositeType):
|
|||||||
def __init__(self, readLen = None):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS)
|
self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS)
|
||||||
self.clientRequestedProtocol = UInt32Le()
|
self.clientRequestedProtocol = UInt32Le(optional = True)
|
||||||
|
self.earlyCapabilityFlags = UInt32Le(optional = True)
|
||||||
|
|
||||||
class ClientSecurityData(CompositeType):
|
class ClientSecurityData(CompositeType):
|
||||||
"""
|
"""
|
||||||
Client security setting
|
@summary: Client security setting
|
||||||
@deprecated: because we use ssl
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240511.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240511.aspx
|
||||||
"""
|
"""
|
||||||
_TYPE_ = MessageType.CS_SECURITY
|
_TYPE_ = MessageType.CS_SECURITY
|
||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.encryptionMethods = UInt32Le()
|
self.encryptionMethods = UInt32Le(EncryptionMethod.ENCRYPTION_FLAG_40BIT | EncryptionMethod.ENCRYPTION_FLAG_56BIT | EncryptionMethod.ENCRYPTION_FLAG_128BIT)
|
||||||
self.extEncryptionMethods = UInt32Le()
|
self.extEncryptionMethods = UInt32Le()
|
||||||
|
|
||||||
class ServerSecurityData(CompositeType):
|
class ServerSecurityData(CompositeType):
|
||||||
"""
|
"""
|
||||||
Server security settings
|
@summary: Server security settings
|
||||||
May be ignored because rdpy don't use
|
|
||||||
RDP security level
|
|
||||||
@deprecated: because we use SSL
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240518.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240518.aspx
|
||||||
"""
|
"""
|
||||||
_TYPE_ = MessageType.SC_SECURITY
|
_TYPE_ = MessageType.SC_SECURITY
|
||||||
@@ -287,6 +304,151 @@ class ServerSecurityData(CompositeType):
|
|||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.encryptionMethod = UInt32Le()
|
self.encryptionMethod = UInt32Le()
|
||||||
self.encryptionLevel = UInt32Le()
|
self.encryptionLevel = UInt32Le()
|
||||||
|
self.serverRandomLen = UInt32Le(0x00000020, constant = True, conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0))
|
||||||
|
self.serverCertLen = UInt32Le(lambda:sizeof(self.serverCertificate), conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0))
|
||||||
|
self.serverRandom = String(readLen = self.serverRandomLen, conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0))
|
||||||
|
self.serverCertificate = ServerCertificate(readLen = self.serverCertLen, conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0))
|
||||||
|
|
||||||
|
class ServerCertificate(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Server certificate structure
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240521.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self, certData = None, readLen = None, conditional = lambda:True):
|
||||||
|
CompositeType.__init__(self, readLen = readLen, conditional = conditional)
|
||||||
|
self.dwVersion = UInt32Le(lambda:(self.certData.__class__._TYPE_))
|
||||||
|
|
||||||
|
def CertificateFactory():
|
||||||
|
"""
|
||||||
|
Closure for capability factory
|
||||||
|
"""
|
||||||
|
for c in [ProprietaryServerCertificate, X509CertificateChain]:
|
||||||
|
if self.dwVersion.value & 0x7fffffff == c._TYPE_:
|
||||||
|
return c()
|
||||||
|
raise InvalidExpectedDataException("unknown certificate type : %s "%hex(self.dwVersion.value))
|
||||||
|
|
||||||
|
if certData is None:
|
||||||
|
certData = FactoryType(CertificateFactory)
|
||||||
|
elif not "_TYPE_" in certData.__class__.__dict__:
|
||||||
|
raise InvalidExpectedDataException("Try to send an invalid Certificate")
|
||||||
|
|
||||||
|
self.certData = certData
|
||||||
|
|
||||||
|
class ProprietaryServerCertificate(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: microsoft proprietary certificate
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240519.aspx
|
||||||
|
"""
|
||||||
|
_TYPE_ = CertificateType.CERT_CHAIN_VERSION_1
|
||||||
|
|
||||||
|
#http://msdn.microsoft.com/en-us/library/cc240776.aspx
|
||||||
|
_TERMINAL_SERVICES_MODULUS_ = "\x3d\x3a\x5e\xbd\x72\x43\x3e\xc9\x4d\xbb\xc1\x1e\x4a\xba\x5f\xcb\x3e\x88\x20\x87\xef\xf5\xc1\xe2\xd7\xb7\x6b\x9a\xf2\x52\x45\x95\xce\x63\x65\x6b\x58\x3a\xfe\xef\x7c\xe7\xbf\xfe\x3d\xf6\x5c\x7d\x6c\x5e\x06\x09\x1a\xf5\x61\xbb\x20\x93\x09\x5f\x05\x6d\xea\x87"
|
||||||
|
_TERMINAL_SERVICES_PRIVATE_EXPONENT_ = "\x87\xa7\x19\x32\xda\x11\x87\x55\x58\x00\x16\x16\x25\x65\x68\xf8\x24\x3e\xe6\xfa\xe9\x67\x49\x94\xcf\x92\xcc\x33\x99\xe8\x08\x60\x17\x9a\x12\x9f\x24\xdd\xb1\x24\x99\xc7\x3a\xb8\x0a\x7b\x0d\xdd\x35\x07\x79\x17\x0b\x51\x9b\xb3\xc7\x10\x01\x13\xe7\x3f\xf3\x5f"
|
||||||
|
_TERMINAL_SERVICES_PUBLIC_EXPONENT_ = "\x5b\x7b\x88\xc0"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.dwSigAlgId = UInt32Le(0x00000001, constant = True)
|
||||||
|
self.dwKeyAlgId = UInt32Le(0x00000001, constant = True)
|
||||||
|
self.wPublicKeyBlobType = UInt16Le(0x0006, constant = True)
|
||||||
|
self.wPublicKeyBlobLen = UInt16Le(lambda:sizeof(self.PublicKeyBlob))
|
||||||
|
self.PublicKeyBlob = RSAPublicKey(readLen = self.wPublicKeyBlobLen)
|
||||||
|
self.wSignatureBlobType = UInt16Le(0x0008, constant = True)
|
||||||
|
self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) + sizeof(self.padding)))
|
||||||
|
self.SignatureBlob = String(readLen = CallableValue(lambda:(self.wSignatureBlobLen.value - sizeof(self.padding))))
|
||||||
|
self.padding = String(b"\x00" * 8, readLen = CallableValue(8))
|
||||||
|
|
||||||
|
def getPublicKey(self):
|
||||||
|
"""
|
||||||
|
@return: {Tuple} (modulus, publicExponent)
|
||||||
|
"""
|
||||||
|
log.debug("read RSA public key from proprietary certificate")
|
||||||
|
#reverse because bignum in little endian
|
||||||
|
return rsa.PublicKey(self.PublicKeyBlob.pubExp.value, self.PublicKeyBlob.modulus.value[::-1])
|
||||||
|
|
||||||
|
def computeSignatureHash(self):
|
||||||
|
"""
|
||||||
|
@summary: compute hash
|
||||||
|
"""
|
||||||
|
s = Stream()
|
||||||
|
s.writeType(UInt32Le(self.__class__._TYPE_))
|
||||||
|
s.writeType(self.dwSigAlgId)
|
||||||
|
s.writeType(self.dwKeyAlgId)
|
||||||
|
s.writeType(self.wPublicKeyBlobType)
|
||||||
|
s.writeType(self.wPublicKeyBlobLen)
|
||||||
|
s.writeType(self.PublicKeyBlob)
|
||||||
|
|
||||||
|
md5Digest = md5.new()
|
||||||
|
md5Digest.update(s.getvalue())
|
||||||
|
|
||||||
|
return md5Digest.digest() + "\x00" + "\xff" * 45 + "\x01"
|
||||||
|
|
||||||
|
def sign(self):
|
||||||
|
"""
|
||||||
|
@summary: sign proprietary certificate
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240778.aspx
|
||||||
|
"""
|
||||||
|
self.SignatureBlob.value = rsa.sign(self.computeSignatureHash()[::-1], rsa.PrivateKey(d = ProprietaryServerCertificate._TERMINAL_SERVICES_PRIVATE_EXPONENT_[::-1], n = ProprietaryServerCertificate._TERMINAL_SERVICES_MODULUS_[::-1]))[::-1]
|
||||||
|
|
||||||
|
def verify(self):
|
||||||
|
"""
|
||||||
|
@summary: verify certificate signature
|
||||||
|
"""
|
||||||
|
return rsa.verify(self.SignatureBlob.value[::-1], rsa.PublicKey(e = ProprietaryServerCertificate._TERMINAL_SERVICES_PUBLIC_EXPONENT_[::-1], n = ProprietaryServerCertificate._TERMINAL_SERVICES_MODULUS_[::-1]))[::-1] == self.computeSignatureHash()
|
||||||
|
|
||||||
|
|
||||||
|
class CertBlob(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: certificate blob, contain x509 data
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241911.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.cbCert = UInt32Le(lambda:sizeof(self.abCert))
|
||||||
|
self.abCert = String(readLen = self.cbCert)
|
||||||
|
|
||||||
|
class X509CertificateChain(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: X509 certificate chain
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc241910.aspx
|
||||||
|
"""
|
||||||
|
_TYPE_ = CertificateType.CERT_CHAIN_VERSION_2
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.NumCertBlobs = UInt32Le()
|
||||||
|
self.CertBlobArray = ArrayType(CertBlob, readLen = self.NumCertBlobs)
|
||||||
|
self.padding = String(readLen = CallableValue(lambda:(8 + 4 * self.NumCertBlobs.value)))
|
||||||
|
|
||||||
|
def getPublicKey(self):
|
||||||
|
"""
|
||||||
|
@return: {Tuple} (modulus, publicExponent)
|
||||||
|
"""
|
||||||
|
log.debug("read RSA public key from x509 certificate")
|
||||||
|
#last certifcate contain public key
|
||||||
|
n, e = x509.extractRSAKey(x509.load(self.CertBlobArray[-1].abCert.value))
|
||||||
|
return rsa.PublicKey(e, n)
|
||||||
|
|
||||||
|
def verify(self):
|
||||||
|
"""
|
||||||
|
@todo: verify x509 signature
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
class RSAPublicKey(CompositeType):
|
||||||
|
"""
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240520.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self, readLen):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
#magic is RSA1(0x31415352)
|
||||||
|
self.magic = UInt32Le(0x31415352, constant = True)
|
||||||
|
self.keylen = UInt32Le(lambda:(sizeof(self.modulus) + sizeof(self.padding)))
|
||||||
|
self.bitlen = UInt32Le(lambda:((self.keylen.value - 8) * 8))
|
||||||
|
self.datalen = UInt32Le(lambda:((self.bitlen.value / 8) - 1))
|
||||||
|
self.pubExp = UInt32Le()
|
||||||
|
self.modulus = String(readLen = CallableValue(lambda:(self.keylen.value - 8)))
|
||||||
|
self.padding = String("\x00" * 8, readLen = CallableValue(8))
|
||||||
|
|
||||||
class ChannelDef(CompositeType):
|
class ChannelDef(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -296,13 +458,13 @@ class ChannelDef(CompositeType):
|
|||||||
def __init__(self, name = "", options = 0):
|
def __init__(self, name = "", options = 0):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
#name of channel
|
#name of channel
|
||||||
self.name = String(name[0:8] + "\x00" * (8 - len(name)), readLen = UInt8(8))
|
self.name = String(name[0:8] + "\x00" * (8 - len(name)), readLen = CallableValue(8))
|
||||||
#unknown
|
#unknown
|
||||||
self.options = UInt32Le()
|
self.options = UInt32Le()
|
||||||
|
|
||||||
class ClientNetworkData(CompositeType):
|
class ClientNetworkData(CompositeType):
|
||||||
"""
|
"""
|
||||||
GCC client network block
|
@summary: GCC client network block
|
||||||
All channels asked by client are listed here
|
All channels asked by client are listed here
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240512.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240512.aspx
|
||||||
"""
|
"""
|
||||||
@@ -315,7 +477,7 @@ class ClientNetworkData(CompositeType):
|
|||||||
|
|
||||||
class ServerNetworkData(CompositeType):
|
class ServerNetworkData(CompositeType):
|
||||||
"""
|
"""
|
||||||
GCC server network block
|
@summary: GCC server network block
|
||||||
All channels asked by client are listed here
|
All channels asked by client are listed here
|
||||||
@see: All channels asked by client are listed here
|
@see: All channels asked by client are listed here
|
||||||
"""
|
"""
|
||||||
@@ -330,7 +492,7 @@ class ServerNetworkData(CompositeType):
|
|||||||
|
|
||||||
class Settings(CompositeType):
|
class Settings(CompositeType):
|
||||||
"""
|
"""
|
||||||
Class which group all clients settings supported by RDPY
|
@summary: Class which group all clients settings supported by RDPY
|
||||||
"""
|
"""
|
||||||
def __init__(self, init = [], readLen = None):
|
def __init__(self, init = [], readLen = None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
@@ -345,24 +507,33 @@ class Settings(CompositeType):
|
|||||||
if i.type.value == messageType:
|
if i.type.value == messageType:
|
||||||
return i.dataBlock
|
return i.dataBlock
|
||||||
return None
|
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 clientSettings():
|
||||||
"""
|
"""
|
||||||
Build settings for client
|
@summary: Build settings for client
|
||||||
@return: Settings
|
@return: Settings
|
||||||
"""
|
"""
|
||||||
return Settings([ClientCoreData(), ClientNetworkData(), ClientSecurityData()])
|
return Settings([ClientCoreData(), ClientNetworkData(), ClientSecurityData()])
|
||||||
|
|
||||||
def serverSettings():
|
def serverSettings():
|
||||||
"""
|
"""
|
||||||
Build settings for server
|
@summary: Build settings for server
|
||||||
@return Settings
|
@return Settings
|
||||||
"""
|
"""
|
||||||
return Settings([ServerCoreData(), ServerSecurityData(), ServerNetworkData()])
|
return Settings([ServerCoreData(), ServerSecurityData(), ServerNetworkData()])
|
||||||
|
|
||||||
def readConferenceCreateRequest(s):
|
def readConferenceCreateRequest(s):
|
||||||
"""
|
"""
|
||||||
Read a response from client
|
@summary: Read a response from client
|
||||||
GCC create request
|
GCC create request
|
||||||
@param s: Stream
|
@param s: Stream
|
||||||
@param client settings (Settings)
|
@param client settings (Settings)
|
||||||
@@ -383,13 +554,13 @@ def readConferenceCreateRequest(s):
|
|||||||
|
|
||||||
per.readOctetStream(s, h221_cs_key, 4)
|
per.readOctetStream(s, h221_cs_key, 4)
|
||||||
length = per.readLength(s)
|
length = per.readLength(s)
|
||||||
clientSettings = Settings(readLen = UInt32Le(length))
|
clientSettings = Settings(readLen = CallableValue(length))
|
||||||
s.readType(clientSettings)
|
s.readType(clientSettings)
|
||||||
return clientSettings
|
return clientSettings
|
||||||
|
|
||||||
def readConferenceCreateResponse(s):
|
def readConferenceCreateResponse(s):
|
||||||
"""
|
"""
|
||||||
Read response from server
|
@summary: Read response from server
|
||||||
and return server settings read from this response
|
and return server settings read from this response
|
||||||
@param s: Stream
|
@param s: Stream
|
||||||
@return: ServerSettings
|
@return: ServerSettings
|
||||||
@@ -407,13 +578,13 @@ def readConferenceCreateResponse(s):
|
|||||||
raise InvalidExpectedDataException("cannot read h221_sc_key")
|
raise InvalidExpectedDataException("cannot read h221_sc_key")
|
||||||
|
|
||||||
length = per.readLength(s)
|
length = per.readLength(s)
|
||||||
serverSettings = Settings(readLen = UInt32Le(length))
|
serverSettings = Settings(readLen = CallableValue(length))
|
||||||
s.readType(serverSettings)
|
s.readType(serverSettings)
|
||||||
return serverSettings
|
return serverSettings
|
||||||
|
|
||||||
def writeConferenceCreateRequest(userData):
|
def writeConferenceCreateRequest(userData):
|
||||||
"""
|
"""
|
||||||
Write conference create request structure
|
@summary: Write conference create request structure
|
||||||
@param userData: Settings for client
|
@param userData: Settings for client
|
||||||
@return: GCC packet
|
@return: GCC packet
|
||||||
"""
|
"""
|
||||||
@@ -428,7 +599,7 @@ def writeConferenceCreateRequest(userData):
|
|||||||
|
|
||||||
def writeConferenceCreateResponse(serverData):
|
def writeConferenceCreateResponse(serverData):
|
||||||
"""
|
"""
|
||||||
Write a conference create response packet
|
@summary: Write a conference create response packet
|
||||||
@param serverData: Settings for server
|
@param serverData: Settings for server
|
||||||
@return: gcc packet
|
@return: gcc packet
|
||||||
"""
|
"""
|
||||||
@@ -437,6 +608,6 @@ def writeConferenceCreateResponse(serverData):
|
|||||||
|
|
||||||
return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
|
return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
|
||||||
per.writeLength(len(serverDataStream.getvalue()) + 14), per.writeChoice(0x14),
|
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.writeNumberOfSet(1), per.writeChoice(0xc0),
|
||||||
per.writeOctetStream(h221_sc_key, 4), per.writeOctetStream(serverDataStream.getvalue()))
|
per.writeOctetStream(h221_sc_key, 4), per.writeOctetStream(serverDataStream.getvalue()))
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -24,24 +24,24 @@ Each channel have a particular role.
|
|||||||
The main channel is the graphical channel.
|
The main channel is the graphical channel.
|
||||||
It exist channel for file system order, audio channel, clipboard etc...
|
It exist channel for file system order, audio channel, clipboard etc...
|
||||||
"""
|
"""
|
||||||
from rdpy.network.layer import LayerAutomata, IStreamSender, Layer
|
from rdpy.core.layer import LayerAutomata, IStreamSender, Layer
|
||||||
from rdpy.network.type import sizeof, Stream, UInt8, UInt16Le, String
|
from rdpy.core.type import sizeof, Stream, UInt8, UInt16Le, String
|
||||||
from rdpy.base.error import InvalidExpectedDataException, InvalidValue, InvalidSize
|
from rdpy.core.error import InvalidExpectedDataException, InvalidValue, InvalidSize, CallPureVirtualFuntion
|
||||||
from rdpy.protocol.rdp.ber import writeLength
|
import rdpy.core.log as log
|
||||||
import rdpy.base.log as log
|
|
||||||
|
|
||||||
import ber, gcc, per
|
import ber, gcc, per
|
||||||
|
import rdpy.security.rsa_wrapper as rsa
|
||||||
|
|
||||||
class Message(object):
|
class Message(object):
|
||||||
"""
|
"""
|
||||||
Message type
|
@summary: Message type
|
||||||
"""
|
"""
|
||||||
MCS_TYPE_CONNECT_INITIAL = 0x65
|
MCS_TYPE_CONNECT_INITIAL = 0x65
|
||||||
MCS_TYPE_CONNECT_RESPONSE = 0x66
|
MCS_TYPE_CONNECT_RESPONSE = 0x66
|
||||||
|
|
||||||
class DomainMCSPDU:
|
class DomainMCSPDU:
|
||||||
"""
|
"""
|
||||||
Domain MCS PDU header
|
@summary: Domain MCS PDU header
|
||||||
"""
|
"""
|
||||||
ERECT_DOMAIN_REQUEST = 1
|
ERECT_DOMAIN_REQUEST = 1
|
||||||
DISCONNECT_PROVIDER_ULTIMATUM = 8
|
DISCONNECT_PROVIDER_ULTIMATUM = 8
|
||||||
@@ -54,74 +54,113 @@ class DomainMCSPDU:
|
|||||||
|
|
||||||
class Channel:
|
class Channel:
|
||||||
"""
|
"""
|
||||||
Channel id of main channels use in RDP
|
@summary: Channel id of main channels use in RDP
|
||||||
"""
|
"""
|
||||||
MCS_GLOBAL_CHANNEL = 1003
|
MCS_GLOBAL_CHANNEL = 1003
|
||||||
MCS_USERCHANNEL_BASE = 1001
|
MCS_USERCHANNEL_BASE = 1001
|
||||||
|
|
||||||
|
class IGCCConfig(object):
|
||||||
|
"""
|
||||||
|
@summary: Channel information
|
||||||
|
"""
|
||||||
|
def getUserId(self):
|
||||||
|
"""
|
||||||
|
@return: {integer} mcs user id
|
||||||
|
@see: mcs.IGCCConfig
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getUserId", "IGCCConfig"))
|
||||||
|
|
||||||
|
def getChannelId(self):
|
||||||
|
"""
|
||||||
|
@return: {integer} return channel id of proxy
|
||||||
|
@see: mcs.IGCCConfig
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getChannelId", "IGCCConfig"))
|
||||||
|
|
||||||
|
def getGCCClientSettings(self):
|
||||||
|
"""
|
||||||
|
@return: {gcc.Settings} mcs layer gcc client settings
|
||||||
|
@see: mcs.IGCCConfig
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getGCCClientSettings", "IGCCConfig"))
|
||||||
|
|
||||||
|
def getGCCServerSettings(self):
|
||||||
|
"""
|
||||||
|
@return: {gcc.Settings} mcs layer gcc server settings
|
||||||
|
@see: mcs.IGCCConfig
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getGCCServerSettings", "IGCCConfig"))
|
||||||
|
|
||||||
class MCSLayer(LayerAutomata):
|
class MCSLayer(LayerAutomata):
|
||||||
"""
|
"""
|
||||||
Multiple Channel Service layer
|
@summary: Multiple Channel Service layer
|
||||||
the main layer of RDP protocol
|
the main layer of RDP protocol
|
||||||
is why he can do everything and more!
|
is why he can do everything and more!
|
||||||
"""
|
"""
|
||||||
class MCSProxySender(Layer, IStreamSender):
|
class MCSProxySender(Layer, IStreamSender, IGCCConfig):
|
||||||
"""
|
"""
|
||||||
Proxy use to set as transport layer for upper channel
|
@summary: Proxy use to set as transport layer for upper channel
|
||||||
use to abstract channel id for presentation layer
|
use to abstract channel id for presentation layer
|
||||||
"""
|
"""
|
||||||
def __init__(self, mcs, channelId):
|
def __init__(self, presentation, mcs, channelId):
|
||||||
"""
|
"""
|
||||||
@param mcs: MCS layer use as proxy
|
@param presentation: {Layer} presentation layer
|
||||||
@param channelId: channel id for presentation layer
|
@param mcs: {MCSLayer} MCS layer use as proxy
|
||||||
|
@param channelId: {integer} channel id for presentation layer
|
||||||
"""
|
"""
|
||||||
|
Layer.__init__(self, presentation)
|
||||||
self._mcs = mcs
|
self._mcs = mcs
|
||||||
self._channelId = channelId
|
self._channelId = channelId
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
"""
|
"""
|
||||||
A send proxy function, use channel id and specific
|
@summary: A send proxy function, use channel id and specific
|
||||||
send function of MCS layer
|
send function of MCS layer
|
||||||
|
@param data: {type.Type | Tuple}
|
||||||
"""
|
"""
|
||||||
self._mcs.send(self._channelId, data)
|
self._mcs.send(self._channelId, data)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Close wrapped layer
|
@summary: Close wrapped layer
|
||||||
"""
|
"""
|
||||||
self._mcs.close()
|
self._mcs.close()
|
||||||
|
|
||||||
def getUserId(self):
|
def getUserId(self):
|
||||||
"""
|
"""
|
||||||
@return: mcs user id
|
@return: {integer} mcs user id
|
||||||
|
@see: mcs.IGCCConfig
|
||||||
"""
|
"""
|
||||||
return self._mcs._userId
|
return self._mcs._userId
|
||||||
|
|
||||||
def getChannelId(self):
|
def getChannelId(self):
|
||||||
"""
|
"""
|
||||||
@return: return channel id of proxy
|
@return: {integer} return channel id of proxy
|
||||||
|
@see: mcs.IGCCConfig
|
||||||
"""
|
"""
|
||||||
return self._channelId
|
return self._channelId
|
||||||
|
|
||||||
def getGCCClientSettings(self):
|
def getGCCClientSettings(self):
|
||||||
"""
|
"""
|
||||||
@return: mcs layer gcc client settings
|
@return: {gcc.Settings} mcs layer gcc client settings
|
||||||
|
@see: mcs.IGCCConfig
|
||||||
"""
|
"""
|
||||||
return self._mcs._clientSettings
|
return self._mcs._clientSettings
|
||||||
|
|
||||||
def getGCCServerSettings(self):
|
def getGCCServerSettings(self):
|
||||||
"""
|
"""
|
||||||
@return: mcs layer gcc server settings
|
@return: {gcc.Settings} mcs layer gcc server settings
|
||||||
|
@see: mcs.IGCCConfig
|
||||||
"""
|
"""
|
||||||
return self._mcs._serverSettings
|
return self._mcs._serverSettings
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, presentation, receiveOpcode, sendOpcode, virtualChannels = []):
|
def __init__(self, presentation, receiveOpcode, sendOpcode, virtualChannels = []):
|
||||||
"""
|
"""
|
||||||
@param presentation: presentation layer
|
@param presentation: {Layer} presentation layer
|
||||||
@param virtualChannels: list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
|
@param virtualChannels: {Array(Layer]} list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
|
||||||
@param receiveOpcode: opcode check when receive data
|
@param receiveOpcode: {integer} opcode check when receive data
|
||||||
@param sendOpcode: opcode use when send data
|
@param sendOpcode: {integer} opcode use when send data
|
||||||
"""
|
"""
|
||||||
LayerAutomata.__init__(self, presentation)
|
LayerAutomata.__init__(self, presentation)
|
||||||
self._clientSettings = gcc.clientSettings()
|
self._clientSettings = gcc.clientSettings()
|
||||||
@@ -139,7 +178,7 @@ class MCSLayer(LayerAutomata):
|
|||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Send disconnect provider ultimatum
|
@summary: Send disconnect provider ultimatum
|
||||||
"""
|
"""
|
||||||
self._transport.send((UInt8(self.writeMCSPDUHeader(DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM, 1)),
|
self._transport.send((UInt8(self.writeMCSPDUHeader(DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM, 1)),
|
||||||
per.writeEnumerates(0x80), String("\x00" * 6)))
|
per.writeEnumerates(0x80), String("\x00" * 6)))
|
||||||
@@ -147,7 +186,7 @@ class MCSLayer(LayerAutomata):
|
|||||||
|
|
||||||
def allChannelConnected(self):
|
def allChannelConnected(self):
|
||||||
"""
|
"""
|
||||||
All channels are connected to MCS layer
|
@summary: All channels are connected to MCS layer
|
||||||
Send connect to upper channel
|
Send connect to upper channel
|
||||||
And prepare MCS layer to receive data
|
And prepare MCS layer to receive data
|
||||||
"""
|
"""
|
||||||
@@ -156,14 +195,13 @@ class MCSLayer(LayerAutomata):
|
|||||||
#try connection on all requested channel
|
#try connection on all requested channel
|
||||||
for (channelId, layer) in self._channels.iteritems():
|
for (channelId, layer) in self._channels.iteritems():
|
||||||
#use proxy for each channel
|
#use proxy for each channel
|
||||||
layer._transport = MCSLayer.MCSProxySender(self, channelId)
|
MCSLayer.MCSProxySender(layer, self, channelId).connect()
|
||||||
layer.connect()
|
|
||||||
|
|
||||||
def send(self, channelId, data):
|
def send(self, channelId, data):
|
||||||
"""
|
"""
|
||||||
Specific send function for channelId
|
@summary: Specific send function for channelId
|
||||||
@param channelId: Channel use to send
|
@param channelId: {integer} Channel use to send
|
||||||
@param data: message to send
|
@param data: {type.type | tuple} message to send
|
||||||
"""
|
"""
|
||||||
self._transport.send((self.writeMCSPDUHeader(UInt8(self._sendOpcode)),
|
self._transport.send((self.writeMCSPDUHeader(UInt8(self._sendOpcode)),
|
||||||
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
|
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
|
||||||
@@ -173,8 +211,8 @@ class MCSLayer(LayerAutomata):
|
|||||||
|
|
||||||
def recvData(self, data):
|
def recvData(self, data):
|
||||||
"""
|
"""
|
||||||
Main receive method
|
@summary: Main receive method
|
||||||
@param data: Stream
|
@param data: {Stream}
|
||||||
"""
|
"""
|
||||||
opcode = UInt8()
|
opcode = UInt8()
|
||||||
data.readType(opcode)
|
data.readType(opcode)
|
||||||
@@ -205,41 +243,42 @@ class MCSLayer(LayerAutomata):
|
|||||||
|
|
||||||
def writeDomainParams(self, maxChannels, maxUsers, maxTokens, maxPduSize):
|
def writeDomainParams(self, maxChannels, maxUsers, maxTokens, maxPduSize):
|
||||||
"""
|
"""
|
||||||
Write a special domain parameter structure
|
@summary: Write a special domain parameter structure
|
||||||
use in connection sequence
|
use in connection sequence
|
||||||
@param maxChannels: number of MCS channel use
|
@param maxChannels: {integer} number of MCS channel use
|
||||||
@param maxUsers: number of MCS user used (1)
|
@param maxUsers: {integer} number of MCS user used (1)
|
||||||
@param maxTokens: unknown
|
@param maxTokens: {integer} unknown
|
||||||
@param maxPduSize: unknown
|
@param maxPduSize: {integer} unknown
|
||||||
@return: domain parameter structure
|
@return: {Tuple(type)} domain parameter structure
|
||||||
"""
|
"""
|
||||||
domainParam = (ber.writeInteger(maxChannels), ber.writeInteger(maxUsers), ber.writeInteger(maxTokens),
|
domainParam = (ber.writeInteger(maxChannels), ber.writeInteger(maxUsers), ber.writeInteger(maxTokens),
|
||||||
ber.writeInteger(1), ber.writeInteger(0), ber.writeInteger(1),
|
ber.writeInteger(1), ber.writeInteger(0), ber.writeInteger(1),
|
||||||
ber.writeInteger(maxPduSize), ber.writeInteger(2))
|
ber.writeInteger(maxPduSize), ber.writeInteger(2))
|
||||||
return (ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True), writeLength(sizeof(domainParam)), domainParam)
|
return (ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True), ber.writeLength(sizeof(domainParam)), domainParam)
|
||||||
|
|
||||||
def writeMCSPDUHeader(self, mcsPdu, options = 0):
|
def writeMCSPDUHeader(self, mcsPdu, options = 0):
|
||||||
"""
|
"""
|
||||||
Write MCS PDU header
|
@summary: Write MCS PDU header
|
||||||
@param mcsPdu: PDU code
|
@param mcsPdu: {integer} PDU code
|
||||||
@param options: option contains in header
|
@param options: {integer} option contains in header
|
||||||
@return: UInt8
|
@return: {integer}
|
||||||
"""
|
"""
|
||||||
return (mcsPdu << 2) | options
|
return (mcsPdu << 2) | options
|
||||||
|
|
||||||
def readMCSPDUHeader(self, opcode, mcsPdu):
|
def readMCSPDUHeader(self, opcode, mcsPdu):
|
||||||
"""
|
"""
|
||||||
Read mcsPdu header and return options parameter
|
@summary: Read mcsPdu header and return options parameter
|
||||||
@param opcode: opcode
|
@param opcode: {integer} opcode
|
||||||
@param mcsPdu: mcsPdu will be checked
|
@param mcsPdu: {integer} mcsPdu will be checked
|
||||||
@return: true if opcode is correct
|
@return: {boolean} true if opcode is correct
|
||||||
"""
|
"""
|
||||||
return (opcode >> 2) == mcsPdu
|
return (opcode >> 2) == mcsPdu
|
||||||
|
|
||||||
def readDomainParams(self, s):
|
def readDomainParams(self, s):
|
||||||
"""
|
"""
|
||||||
Read domain parameters structure
|
@summary: Read domain parameters structure
|
||||||
@return: (max_channels, max_users, max_tokens, max_pdu_size)
|
@param s: {Stream}
|
||||||
|
@return: {Tuple} (max_channels, max_users, max_tokens, max_pdu_size)
|
||||||
"""
|
"""
|
||||||
if not ber.readUniversalTag(s, ber.Tag.BER_TAG_SEQUENCE, True):
|
if not ber.readUniversalTag(s, ber.Tag.BER_TAG_SEQUENCE, True):
|
||||||
raise InvalidValue("bad BER tags")
|
raise InvalidValue("bad BER tags")
|
||||||
@@ -256,12 +295,12 @@ class MCSLayer(LayerAutomata):
|
|||||||
|
|
||||||
class Client(MCSLayer):
|
class Client(MCSLayer):
|
||||||
"""
|
"""
|
||||||
Client automata of multiple channel service layer
|
@summary: Client automata of multiple channel service layer
|
||||||
"""
|
"""
|
||||||
def __init__(self, presentation, virtualChannels = []):
|
def __init__(self, presentation, virtualChannels = []):
|
||||||
"""
|
"""
|
||||||
@param presentation: presentation layer
|
@param presentation: {Layer} presentation layer
|
||||||
@param virtualChannels: list additional channels like rdpsnd... [tuple(mcs.ChannelDef, 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)
|
MCSLayer.__init__(self, presentation, DomainMCSPDU.SEND_DATA_INDICATION, DomainMCSPDU.SEND_DATA_REQUEST, virtualChannels)
|
||||||
#use to know state of static channel
|
#use to know state of static channel
|
||||||
@@ -272,13 +311,13 @@ class Client(MCSLayer):
|
|||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""
|
"""
|
||||||
Connect message in client automata case
|
@summary: Connect message in client automata case
|
||||||
Send ConnectInitial
|
Send ConnectInitial
|
||||||
Wait ConnectResponse
|
Wait ConnectResponse
|
||||||
"""
|
"""
|
||||||
self._clientSettings.getBlock(gcc.MessageType.CS_CORE).serverSelectedProtocol.value = self._transport._selectedProtocol
|
self._clientSettings.CS_CORE.serverSelectedProtocol.value = self._transport._selectedProtocol
|
||||||
#ask for virtual channel
|
#ask for virtual channel
|
||||||
self._clientSettings.getBlock(gcc.MessageType.CS_NET).channelDefArray._array = [x for (x, _) in self._virtualChannels]
|
self._clientSettings.CS_NET.channelDefArray._array = [x for (x, _) in self._virtualChannels]
|
||||||
#send connect initial
|
#send connect initial
|
||||||
self.sendConnectInitial()
|
self.sendConnectInitial()
|
||||||
#next wait response
|
#next wait response
|
||||||
@@ -286,7 +325,7 @@ class Client(MCSLayer):
|
|||||||
|
|
||||||
def connectNextChannel(self):
|
def connectNextChannel(self):
|
||||||
"""
|
"""
|
||||||
Send sendChannelJoinRequest message on next disconnect channel
|
@summary: Send sendChannelJoinRequest message on next disconnect channel
|
||||||
Send channel request or connect upper layer if all channels are connected
|
Send channel request or connect upper layer if all channels are connected
|
||||||
Wait channel confirm
|
Wait channel confirm
|
||||||
"""
|
"""
|
||||||
@@ -305,7 +344,7 @@ class Client(MCSLayer):
|
|||||||
|
|
||||||
#static virtual channel
|
#static virtual channel
|
||||||
if self._nbChannelRequested < self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelCount.value:
|
if self._nbChannelRequested < self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelCount.value:
|
||||||
channelId = self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray._array[self._nbChannelRequested]
|
channelId = self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray[self._nbChannelRequested]
|
||||||
self._nbChannelRequested += 1
|
self._nbChannelRequested += 1
|
||||||
self.sendChannelJoinRequest(channelId)
|
self.sendChannelJoinRequest(channelId)
|
||||||
return
|
return
|
||||||
@@ -314,11 +353,11 @@ class Client(MCSLayer):
|
|||||||
|
|
||||||
def recvConnectResponse(self, data):
|
def recvConnectResponse(self, data):
|
||||||
"""
|
"""
|
||||||
Receive MCS connect response from server
|
@summary: Receive MCS connect response from server
|
||||||
Send Erect domain Request
|
Send Erect domain Request
|
||||||
Send Attach User Request
|
Send Attach User Request
|
||||||
Wait Attach User Confirm
|
Wait Attach User Confirm
|
||||||
@param data: Stream
|
@param data: {Stream}
|
||||||
"""
|
"""
|
||||||
ber.readApplicationTag(data, UInt8(Message.MCS_TYPE_CONNECT_RESPONSE))
|
ber.readApplicationTag(data, UInt8(Message.MCS_TYPE_CONNECT_RESPONSE))
|
||||||
ber.readEnumerated(data)
|
ber.readEnumerated(data)
|
||||||
@@ -340,9 +379,9 @@ class Client(MCSLayer):
|
|||||||
|
|
||||||
def recvAttachUserConfirm(self, data):
|
def recvAttachUserConfirm(self, data):
|
||||||
"""
|
"""
|
||||||
Receive an attach user confirm
|
@summary: Receive an attach user confirm
|
||||||
Send Connect Channel
|
Send Connect Channel
|
||||||
@param data: Stream
|
@param data: {Stream}
|
||||||
"""
|
"""
|
||||||
opcode = UInt8()
|
opcode = UInt8()
|
||||||
data.readType(opcode)
|
data.readType(opcode)
|
||||||
@@ -359,9 +398,9 @@ class Client(MCSLayer):
|
|||||||
|
|
||||||
def recvChannelJoinConfirm(self, data):
|
def recvChannelJoinConfirm(self, data):
|
||||||
"""
|
"""
|
||||||
Receive a channel join confirm from server
|
@summary: Receive a channel join confirm from server
|
||||||
client automata function
|
client automata function
|
||||||
@param data: Stream
|
@param data: {Stream}
|
||||||
"""
|
"""
|
||||||
opcode = UInt8()
|
opcode = UInt8()
|
||||||
data.readType(opcode)
|
data.readType(opcode)
|
||||||
@@ -383,14 +422,14 @@ class Client(MCSLayer):
|
|||||||
if confirm == 0:
|
if confirm == 0:
|
||||||
serverNet = self._serverSettings.getBlock(gcc.MessageType.SC_NET)
|
serverNet = self._serverSettings.getBlock(gcc.MessageType.SC_NET)
|
||||||
for i in range(0, serverNet.channelCount.value):
|
for i in range(0, serverNet.channelCount.value):
|
||||||
if channelId == serverNet.channelIdArray._array[i].value:
|
if channelId == serverNet.channelIdArray[i].value:
|
||||||
self._channels[channelId] = self._virtualChannels[i][1]
|
self._channels[channelId] = self._virtualChannels[i][1]
|
||||||
|
|
||||||
self.connectNextChannel()
|
self.connectNextChannel()
|
||||||
|
|
||||||
def sendConnectInitial(self):
|
def sendConnectInitial(self):
|
||||||
"""
|
"""
|
||||||
Send connect initial packet
|
@summary: Send connect initial packet
|
||||||
client automata function
|
client automata function
|
||||||
"""
|
"""
|
||||||
ccReq = gcc.writeConferenceCreateRequest(self._clientSettings)
|
ccReq = gcc.writeConferenceCreateRequest(self._clientSettings)
|
||||||
@@ -406,7 +445,7 @@ class Client(MCSLayer):
|
|||||||
|
|
||||||
def sendErectDomainRequest(self):
|
def sendErectDomainRequest(self):
|
||||||
"""
|
"""
|
||||||
Send a formated erect domain request for RDP connection
|
@summary: Send a formated erect domain request for RDP connection
|
||||||
"""
|
"""
|
||||||
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ERECT_DOMAIN_REQUEST)),
|
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ERECT_DOMAIN_REQUEST)),
|
||||||
per.writeInteger(0),
|
per.writeInteger(0),
|
||||||
@@ -414,15 +453,15 @@ class Client(MCSLayer):
|
|||||||
|
|
||||||
def sendAttachUserRequest(self):
|
def sendAttachUserRequest(self):
|
||||||
"""
|
"""
|
||||||
Send a formated attach user request for RDP connection
|
@summary: Send a formated attach user request for RDP connection
|
||||||
"""
|
"""
|
||||||
self._transport.send(self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_REQUEST)))
|
self._transport.send(self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_REQUEST)))
|
||||||
|
|
||||||
def sendChannelJoinRequest(self, channelId):
|
def sendChannelJoinRequest(self, channelId):
|
||||||
"""
|
"""
|
||||||
Send a formated Channel join request from client to server
|
@summary: Send a formated Channel join request from client to server
|
||||||
client automata function
|
client automata function
|
||||||
@param channelId: id of channel requested
|
@param channelId: {integer} id of channel requested
|
||||||
"""
|
"""
|
||||||
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.CHANNEL_JOIN_REQUEST)),
|
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.CHANNEL_JOIN_REQUEST)),
|
||||||
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
|
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
|
||||||
@@ -430,12 +469,12 @@ class Client(MCSLayer):
|
|||||||
|
|
||||||
class Server(MCSLayer):
|
class Server(MCSLayer):
|
||||||
"""
|
"""
|
||||||
Server automata of multiple channel service layer
|
@summary: Server automata of multiple channel service layer
|
||||||
"""
|
"""
|
||||||
def __init__(self, presentation, virtualChannels = []):
|
def __init__(self, presentation, virtualChannels = []):
|
||||||
"""
|
"""
|
||||||
@param presentation: presentation layer
|
@param presentation: {Layer} presentation layer
|
||||||
@param virtualChannels: list additional channels like rdpsnd... [tuple(mcs.ChannelDef, 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)
|
MCSLayer.__init__(self, presentation, DomainMCSPDU.SEND_DATA_REQUEST, DomainMCSPDU.SEND_DATA_INDICATION, virtualChannels)
|
||||||
#nb channel requested
|
#nb channel requested
|
||||||
@@ -443,18 +482,26 @@ class Server(MCSLayer):
|
|||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""
|
"""
|
||||||
Connect message for server automata
|
@summary: Connect message for server automata
|
||||||
Wait Connect Initial
|
Wait Connect Initial
|
||||||
"""
|
"""
|
||||||
self._serverSettings.getBlock(gcc.MessageType.SC_CORE).clientRequestedProtocol.value = self._transport._requestedProtocol
|
#basic rdp security layer
|
||||||
|
if self._transport._selectedProtocol == 0:
|
||||||
|
|
||||||
|
self._serverSettings.SC_SECURITY.encryptionMethod.value = gcc.EncryptionMethod.ENCRYPTION_FLAG_128BIT
|
||||||
|
self._serverSettings.SC_SECURITY.encryptionLevel.value = gcc.EncryptionLevel.ENCRYPTION_LEVEL_HIGH
|
||||||
|
self._serverSettings.SC_SECURITY.serverRandom.value = rsa.random(256)
|
||||||
|
self._serverSettings.SC_SECURITY.serverCertificate = self._presentation.getCertificate()
|
||||||
|
|
||||||
|
self._serverSettings.SC_CORE.clientRequestedProtocol.value = self._transport._requestedProtocol
|
||||||
self.setNextState(self.recvConnectInitial)
|
self.setNextState(self.recvConnectInitial)
|
||||||
|
|
||||||
def recvConnectInitial(self, data):
|
def recvConnectInitial(self, data):
|
||||||
"""
|
"""
|
||||||
Receive MCS connect initial from client
|
@summary: Receive MCS connect initial from client
|
||||||
Send Connect Response
|
Send Connect Response
|
||||||
Wait Erect Domain Request
|
Wait Erect Domain Request
|
||||||
@param data: Stream
|
@param data: {Stream}
|
||||||
"""
|
"""
|
||||||
ber.readApplicationTag(data, UInt8(Message.MCS_TYPE_CONNECT_INITIAL))
|
ber.readApplicationTag(data, UInt8(Message.MCS_TYPE_CONNECT_INITIAL))
|
||||||
ber.readOctetString(data)
|
ber.readOctetString(data)
|
||||||
@@ -467,24 +514,25 @@ class Server(MCSLayer):
|
|||||||
self.readDomainParams(data)
|
self.readDomainParams(data)
|
||||||
self.readDomainParams(data)
|
self.readDomainParams(data)
|
||||||
self._clientSettings = gcc.readConferenceCreateRequest(Stream(ber.readOctetString(data)))
|
self._clientSettings = gcc.readConferenceCreateRequest(Stream(ber.readOctetString(data)))
|
||||||
|
|
||||||
i = 1
|
if not self._clientSettings.CS_NET is None:
|
||||||
for channelDef in self._clientSettings.getBlock(gcc.MessageType.CS_NET).channelDefArray._array:
|
i = 1
|
||||||
self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray._array.append(UInt16Le(i + Channel.MCS_GLOBAL_CHANNEL))
|
for channelDef in self._clientSettings.CS_NET.channelDefArray._array:
|
||||||
#if channel can be handle by serve add it
|
self._serverSettings.SC_NET.channelIdArray._array.append(UInt16Le(i + Channel.MCS_GLOBAL_CHANNEL))
|
||||||
for serverChannelDef, layer in self._virtualChannels:
|
#if channel can be handle by serve add it
|
||||||
if channelDef.name == serverChannelDef.name:
|
for serverChannelDef, layer in self._virtualChannels:
|
||||||
self._channels[i + Channel.MCS_GLOBAL_CHANNEL] = layer
|
if channelDef.name == serverChannelDef.name:
|
||||||
i += 1
|
self._channels[i + Channel.MCS_GLOBAL_CHANNEL] = layer
|
||||||
|
i += 1
|
||||||
|
|
||||||
self.sendConnectResponse()
|
self.sendConnectResponse()
|
||||||
self.setNextState(self.recvErectDomainRequest)
|
self.setNextState(self.recvErectDomainRequest)
|
||||||
|
|
||||||
def recvErectDomainRequest(self, data):
|
def recvErectDomainRequest(self, data):
|
||||||
"""
|
"""
|
||||||
Receive erect domain request
|
@summary: Receive erect domain request
|
||||||
Wait Attach User Request
|
Wait Attach User Request
|
||||||
@param data: Stream
|
@param data: {Stream}
|
||||||
"""
|
"""
|
||||||
opcode = UInt8()
|
opcode = UInt8()
|
||||||
data.readType(opcode)
|
data.readType(opcode)
|
||||||
@@ -499,10 +547,10 @@ class Server(MCSLayer):
|
|||||||
|
|
||||||
def recvAttachUserRequest(self, data):
|
def recvAttachUserRequest(self, data):
|
||||||
"""
|
"""
|
||||||
Receive Attach user request
|
@summary: Receive Attach user request
|
||||||
Send Attach User Confirm
|
Send Attach User Confirm
|
||||||
Wait Channel Join Request
|
Wait Channel Join Request
|
||||||
@param data: Stream
|
@param data: {Stream}
|
||||||
"""
|
"""
|
||||||
opcode = UInt8()
|
opcode = UInt8()
|
||||||
data.readType(opcode)
|
data.readType(opcode)
|
||||||
@@ -515,9 +563,9 @@ class Server(MCSLayer):
|
|||||||
|
|
||||||
def recvChannelJoinRequest(self, data):
|
def recvChannelJoinRequest(self, data):
|
||||||
"""
|
"""
|
||||||
Receive for each client channel a request
|
@summary: Receive for each client channel a request
|
||||||
Send Channel Join Confirm or Connect upper layer when all channel are joined
|
Send Channel Join Confirm or Connect upper layer when all channel are joined
|
||||||
@param data: Stream
|
@param data: {Stream}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
opcode = UInt8()
|
opcode = UInt8()
|
||||||
@@ -540,7 +588,7 @@ class Server(MCSLayer):
|
|||||||
|
|
||||||
def sendConnectResponse(self):
|
def sendConnectResponse(self):
|
||||||
"""
|
"""
|
||||||
Send connect response
|
@summary: Send connect response
|
||||||
"""
|
"""
|
||||||
ccReq = gcc.writeConferenceCreateResponse(self._serverSettings)
|
ccReq = gcc.writeConferenceCreateResponse(self._serverSettings)
|
||||||
ccReqStream = Stream()
|
ccReqStream = Stream()
|
||||||
@@ -552,7 +600,7 @@ class Server(MCSLayer):
|
|||||||
|
|
||||||
def sendAttachUserConfirm(self):
|
def sendAttachUserConfirm(self):
|
||||||
"""
|
"""
|
||||||
Send attach user confirm
|
@summary: Send attach user confirm
|
||||||
"""
|
"""
|
||||||
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_CONFIRM), 2),
|
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_CONFIRM), 2),
|
||||||
per.writeEnumerates(0),
|
per.writeEnumerates(0),
|
||||||
@@ -560,9 +608,9 @@ class Server(MCSLayer):
|
|||||||
|
|
||||||
def sendChannelJoinConfirm(self, channelId, confirm):
|
def sendChannelJoinConfirm(self, channelId, confirm):
|
||||||
"""
|
"""
|
||||||
Send a confirm channel (or not) to client
|
@summary: Send a confirm channel (or not) to client
|
||||||
@param channelId: id of channel
|
@param channelId: {integer} id of channel
|
||||||
@param confirm: connection state
|
@param confirm: {boolean} connection state
|
||||||
"""
|
"""
|
||||||
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.CHANNEL_JOIN_CONFIRM), 2),
|
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.CHANNEL_JOIN_CONFIRM), 2),
|
||||||
per.writeEnumerates(int(confirm)),
|
per.writeEnumerates(int(confirm)),
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
Per encoded function
|
Per encoded function
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.network.type import UInt8, UInt16Be, UInt32Be, String
|
from rdpy.core.type import UInt8, UInt16Be, UInt32Be, String
|
||||||
from rdpy.base.error import InvalidValue, InvalidExpectedDataException
|
from rdpy.core.error import InvalidValue, InvalidExpectedDataException
|
||||||
|
|
||||||
def readLength(s):
|
def readLength(s):
|
||||||
"""
|
"""
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -22,9 +22,9 @@ Transport packet layer implementation
|
|||||||
|
|
||||||
Use to build correct size packet and handle slow path and fast path mode
|
Use to build correct size packet and handle slow path and fast path mode
|
||||||
"""
|
"""
|
||||||
from rdpy.network.layer import RawLayer
|
from rdpy.core.layer import RawLayer
|
||||||
from rdpy.network.type import UInt8, UInt16Be, sizeof
|
from rdpy.core.type import UInt8, UInt16Be, sizeof
|
||||||
from rdpy.base.error import CallPureVirtualFuntion
|
from rdpy.core.error import CallPureVirtualFuntion
|
||||||
|
|
||||||
class Action(object):
|
class Action(object):
|
||||||
"""
|
"""
|
||||||
@@ -33,36 +33,67 @@ class Action(object):
|
|||||||
"""
|
"""
|
||||||
FASTPATH_ACTION_FASTPATH = 0x0
|
FASTPATH_ACTION_FASTPATH = 0x0
|
||||||
FASTPATH_ACTION_X224 = 0x3
|
FASTPATH_ACTION_X224 = 0x3
|
||||||
|
|
||||||
|
class SecFlags(object):
|
||||||
|
"""
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240621.aspx
|
||||||
|
"""
|
||||||
|
#hihi 'secure' checksum but private key is public !!!
|
||||||
|
FASTPATH_OUTPUT_SECURE_CHECKSUM = 0x1
|
||||||
|
FASTPATH_OUTPUT_ENCRYPTED = 0x2
|
||||||
|
|
||||||
class IFastPathListener(object):
|
class IFastPathListener(object):
|
||||||
"""
|
"""
|
||||||
@summary: Fast path packet listener
|
@summary: Fast path packet listener
|
||||||
Usually X224 layer
|
Usually X224 layer
|
||||||
"""
|
"""
|
||||||
def recvFastPath(self, fastPathS):
|
def recvFastPath(self, secFlag, fastPathS):
|
||||||
"""
|
"""
|
||||||
@summary: Call when fast path packet is received
|
@summary: Call when fast path packet is received
|
||||||
@param fastPathS: Stream
|
@param secFlag: {SecFlags}
|
||||||
|
@param fastPathS: {Stream}
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recvFastPath", "recvFastPath"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recvFastPath", "IFastPathListener"))
|
||||||
|
|
||||||
|
def initFastPath(self, fastPathSender):
|
||||||
|
"""
|
||||||
|
@summary: initialize stack
|
||||||
|
@param fastPathSender: {IFastPathSender}
|
||||||
|
"""
|
||||||
|
self.setFastPathSender(fastPathSender)
|
||||||
|
fastPathSender.setFastPathListener(self)
|
||||||
|
|
||||||
def setFastPathSender(self, fastPathSender):
|
def setFastPathSender(self, fastPathSender):
|
||||||
"""
|
"""
|
||||||
@summary: Call to set a fast path sender to listener
|
@param fastPathSender : {IFastPathSender}
|
||||||
@param fastPathSender: IFastPathSender
|
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "setFastPathSender", "recvFastPath"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "setFastPathSender", "IFastPathListener"))
|
||||||
|
|
||||||
class IFastPathSender(object):
|
class IFastPathSender(object):
|
||||||
"""
|
"""
|
||||||
@summary: Fast path send capability
|
@summary: Fast path send capability
|
||||||
"""
|
"""
|
||||||
def sendFastPath(self, fastPathS):
|
def sendFastPath(self, secFlag, fastPathS):
|
||||||
"""
|
"""
|
||||||
@summary: Send fastPathS Type as fast path packet
|
@summary: Send fastPathS Type as fast path packet
|
||||||
@param fastPathS: type transform to stream and send as fastpath
|
@param secFlag: {integer} Security flag for fastpath packet
|
||||||
|
@param fastPathS: {Type | Tuple} type transform to stream and send as fastpath
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendFastPath", "IFastPathSender"))
|
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(RawLayer, IFastPathSender):
|
||||||
"""
|
"""
|
||||||
@@ -70,23 +101,25 @@ class TPKT(RawLayer, IFastPathSender):
|
|||||||
represent the Raw Layer in stack (first layer)
|
represent the Raw Layer in stack (first layer)
|
||||||
This layer only handle size of packet and determine if is a fast path packet
|
This layer only handle size of packet and determine if is a fast path packet
|
||||||
"""
|
"""
|
||||||
def __init__(self, presentation, fastPathListener = None):
|
def __init__(self, presentation):
|
||||||
"""
|
"""
|
||||||
@param presentation: presentation layer, in RDP case is x224 layer
|
@param presentation: {Layer} presentation layer, in RDP case is x224 layer
|
||||||
@param fastPathListener: IFastPathListener
|
|
||||||
"""
|
"""
|
||||||
RawLayer.__init__(self, presentation)
|
RawLayer.__init__(self, presentation)
|
||||||
#last packet version read from header
|
|
||||||
self._lastPacketVersion = UInt8()
|
|
||||||
#length may be coded on more than 1 bytes
|
#length may be coded on more than 1 bytes
|
||||||
self._lastShortLength = UInt8()
|
self._lastShortLength = UInt8()
|
||||||
#fast path listener
|
#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
|
self._fastPathListener = fastPathListener
|
||||||
|
|
||||||
if not fastPathListener is None:
|
|
||||||
#set me as fast path sender
|
|
||||||
fastPathListener.setFastPathSender(self)
|
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""
|
"""
|
||||||
@summary: Call when transport layer connection
|
@summary: Call when transport layer connection
|
||||||
@@ -100,19 +133,21 @@ class TPKT(RawLayer, IFastPathSender):
|
|||||||
|
|
||||||
def readHeader(self, data):
|
def readHeader(self, data):
|
||||||
"""
|
"""
|
||||||
Read header of TPKT packet
|
@summary: Read header of TPKT packet
|
||||||
@param data: Stream received from twisted layer
|
@param data: {Stream} received from twisted layer
|
||||||
"""
|
"""
|
||||||
#first read packet version
|
#first read packet version
|
||||||
data.readType(self._lastPacketVersion)
|
version = UInt8()
|
||||||
|
data.readType(version)
|
||||||
#classic packet
|
#classic packet
|
||||||
if self._lastPacketVersion.value == Action.FASTPATH_ACTION_X224:
|
if version.value == Action.FASTPATH_ACTION_X224:
|
||||||
#padding
|
#padding
|
||||||
data.readType(UInt8())
|
data.readType(UInt8())
|
||||||
#read end header
|
#read end header
|
||||||
self.expect(2, self.readExtendedHeader)
|
self.expect(2, self.readExtendedHeader)
|
||||||
else:
|
else:
|
||||||
#is fast path packet
|
#is fast path packet
|
||||||
|
self._secFlag = ((version.value >> 6) & 0x3)
|
||||||
data.readType(self._lastShortLength)
|
data.readType(self._lastShortLength)
|
||||||
if self._lastShortLength.value & 0x80:
|
if self._lastShortLength.value & 0x80:
|
||||||
#size is 1 byte more
|
#size is 1 byte more
|
||||||
@@ -123,8 +158,8 @@ class TPKT(RawLayer, IFastPathSender):
|
|||||||
|
|
||||||
def readExtendedHeader(self, data):
|
def readExtendedHeader(self, data):
|
||||||
"""
|
"""
|
||||||
Header may be on 4 bytes
|
@summary: Header may be on 4 bytes
|
||||||
@param data: Stream from twisted layer
|
@param data: {Stream} from twisted layer
|
||||||
"""
|
"""
|
||||||
#next state is read data
|
#next state is read data
|
||||||
size = UInt16Be()
|
size = UInt16Be()
|
||||||
@@ -133,8 +168,8 @@ class TPKT(RawLayer, IFastPathSender):
|
|||||||
|
|
||||||
def readExtendedFastPathHeader(self, data):
|
def readExtendedFastPathHeader(self, data):
|
||||||
"""
|
"""
|
||||||
Fast path header may be on 1 byte more
|
@summary: Fast path header may be on 1 byte more
|
||||||
@param data: Stream from twisted layer
|
@param data: {Stream} from twisted layer
|
||||||
"""
|
"""
|
||||||
leftPart = UInt8()
|
leftPart = UInt8()
|
||||||
data.readType(leftPart)
|
data.readType(leftPart)
|
||||||
@@ -145,16 +180,16 @@ class TPKT(RawLayer, IFastPathSender):
|
|||||||
|
|
||||||
def readFastPath(self, data):
|
def readFastPath(self, data):
|
||||||
"""
|
"""
|
||||||
Fast path data
|
@summary: Fast path data
|
||||||
@param data: Stream from twisted layer
|
@param data: {Stream} from twisted layer
|
||||||
"""
|
"""
|
||||||
self._fastPathListener.recvFastPath(data)
|
self._fastPathListener.recvFastPath(self._secFlag, data)
|
||||||
self.expect(2, self.readHeader)
|
self.expect(2, self.readHeader)
|
||||||
|
|
||||||
def readData(self, data):
|
def readData(self, data):
|
||||||
"""
|
"""
|
||||||
Read classic TPKT packet, last state in tpkt automata
|
@summary: Read classic TPKT packet, last state in tpkt automata
|
||||||
@param data: Stream with correct size
|
@param data: {Stream} with correct size
|
||||||
"""
|
"""
|
||||||
#next state is pass to
|
#next state is pass to
|
||||||
self._presentation.recv(data)
|
self._presentation.recv(data)
|
||||||
@@ -162,13 +197,28 @@ class TPKT(RawLayer, IFastPathSender):
|
|||||||
|
|
||||||
def send(self, message):
|
def send(self, message):
|
||||||
"""
|
"""
|
||||||
Send encompassed data
|
@summary: Send encompassed data
|
||||||
@param message: network.Type message to send
|
@param message: {network.Type} message to send
|
||||||
"""
|
"""
|
||||||
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_X224), UInt8(0), UInt16Be(sizeof(message) + 4), message))
|
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_X224), UInt8(0), UInt16Be(sizeof(message) + 4), message))
|
||||||
|
|
||||||
def sendFastPath(self, fastPathS):
|
def sendFastPath(self, secFlag, fastPathS):
|
||||||
"""
|
"""
|
||||||
@param fastPathS: type transform to stream and send as fastpath
|
@param 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), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS))
|
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH | ((secFlag & 0x3) << 6)), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS))
|
||||||
|
|
||||||
|
def startTLS(self, sslContext):
|
||||||
|
"""
|
||||||
|
@summary: start TLS protocol
|
||||||
|
@param sslContext: {ssl.ClientContextFactory | ssl.DefaultOpenSSLContextFactory} context use for TLS protocol
|
||||||
|
"""
|
||||||
|
self.transport.startTLS(sslContext)
|
||||||
|
|
||||||
|
def startNLA(self, sslContext, callback):
|
||||||
|
"""
|
||||||
|
@summary: use to start NLA (NTLM over SSL) protocol
|
||||||
|
must be called after startTLS function
|
||||||
|
"""
|
||||||
|
self.transport.startNLA(sslContext, callback)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -21,12 +21,13 @@
|
|||||||
Implement transport PDU layer
|
Implement transport PDU layer
|
||||||
|
|
||||||
This layer have main goal to negociate SSL transport
|
This layer have main goal to negociate SSL transport
|
||||||
RDP basic security is not supported by RDPY (because is not a true security layer...)
|
RDP basic security is supported only on client side
|
||||||
"""
|
"""
|
||||||
|
from rdpy.core import log
|
||||||
|
|
||||||
from rdpy.network.layer import LayerAutomata, IStreamSender
|
from rdpy.core.layer import LayerAutomata, IStreamSender
|
||||||
from rdpy.network.type import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, String
|
from rdpy.core.type import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, String
|
||||||
from rdpy.base.error import InvalidExpectedDataException
|
from rdpy.core.error import InvalidExpectedDataException, RDPSecurityNegoFail
|
||||||
|
|
||||||
class MessageType(object):
|
class MessageType(object):
|
||||||
"""
|
"""
|
||||||
@@ -49,6 +50,7 @@ class NegociationType(object):
|
|||||||
class Protocols(object):
|
class Protocols(object):
|
||||||
"""
|
"""
|
||||||
@summary: Protocols available for x224 layer
|
@summary: Protocols available for x224 layer
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240500.aspx
|
||||||
"""
|
"""
|
||||||
PROTOCOL_RDP = 0x00000000
|
PROTOCOL_RDP = 0x00000000
|
||||||
PROTOCOL_SSL = 0x00000001
|
PROTOCOL_SSL = 0x00000001
|
||||||
@@ -130,10 +132,8 @@ class X224Layer(LayerAutomata, IStreamSender):
|
|||||||
@param presentation: upper layer, MCS layer in RDP case
|
@param presentation: upper layer, MCS layer in RDP case
|
||||||
"""
|
"""
|
||||||
LayerAutomata.__init__(self, presentation)
|
LayerAutomata.__init__(self, presentation)
|
||||||
#default selectedProtocol is SSl because is the only supported
|
|
||||||
#in this version of RDPY
|
|
||||||
#client requested selectedProtocol
|
#client requested selectedProtocol
|
||||||
self._requestedProtocol = Protocols.PROTOCOL_SSL
|
self._requestedProtocol = Protocols.PROTOCOL_SSL | Protocols.PROTOCOL_HYBRID
|
||||||
#server selected selectedProtocol
|
#server selected selectedProtocol
|
||||||
self._selectedProtocol = Protocols.PROTOCOL_SSL
|
self._selectedProtocol = Protocols.PROTOCOL_SSL
|
||||||
|
|
||||||
@@ -195,41 +195,59 @@ class Client(X224Layer):
|
|||||||
message = ServerConnectionConfirm()
|
message = ServerConnectionConfirm()
|
||||||
data.readType(message)
|
data.readType(message)
|
||||||
|
|
||||||
#check presence of negotiation response
|
|
||||||
if not message.protocolNeg._is_readed:
|
|
||||||
raise InvalidExpectedDataException("server must support negotiation protocol to use SSL")
|
|
||||||
|
|
||||||
if message.protocolNeg.failureCode._is_readed:
|
if message.protocolNeg.failureCode._is_readed:
|
||||||
raise InvalidExpectedDataException("negotiation failure code %x"%message.protocolNeg.failureCode.value)
|
raise RDPSecurityNegoFail("negotiation failure code %x"%message.protocolNeg.failureCode.value)
|
||||||
|
|
||||||
self._selectedProtocol = message.protocolNeg.selectedProtocol.value
|
#check presence of negotiation response
|
||||||
|
if message.protocolNeg._is_readed:
|
||||||
|
self._selectedProtocol = message.protocolNeg.selectedProtocol.value
|
||||||
|
else:
|
||||||
|
self._selectedProtocol = Protocols.PROTOCOL_RDP
|
||||||
|
|
||||||
if self._selectedProtocol != Protocols.PROTOCOL_SSL:
|
#NLA protocol doesn't support in actual version of RDPY
|
||||||
raise InvalidExpectedDataException("only SSL protocol is supported in RDPY version")
|
if self._selectedProtocol in [ Protocols.PROTOCOL_HYBRID_EX ]:
|
||||||
|
raise InvalidExpectedDataException("RDPY doesn't support PROTOCOL_HYBRID_EX security Layer")
|
||||||
#_transport is TPKT and transport is TCP layer of twisted
|
|
||||||
self._transport.transport.startTLS(ClientTLSContext())
|
|
||||||
|
|
||||||
#now i'm ready to receive data
|
#now i'm ready to receive data
|
||||||
self.setNextState(self.recvData)
|
self.setNextState(self.recvData)
|
||||||
|
|
||||||
#connection is done send to presentation
|
if self._selectedProtocol == Protocols.PROTOCOL_RDP:
|
||||||
self._presentation.connect()
|
log.warning("*" * 43)
|
||||||
|
log.warning("*" + " " * 10 + "RDP Security selected" + " " * 10 + "*")
|
||||||
|
log.warning("*" * 43)
|
||||||
|
#connection is done send to presentation
|
||||||
|
self._presentation.connect()
|
||||||
|
|
||||||
|
elif self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
||||||
|
log.info("*" * 43)
|
||||||
|
log.info("*" + " " * 10 + "SSL Security selected" + " " * 10 + "*")
|
||||||
|
log.info("*" * 43)
|
||||||
|
self._transport.startTLS(ClientTLSContext())
|
||||||
|
#connection is done send to presentation
|
||||||
|
self._presentation.connect()
|
||||||
|
|
||||||
|
elif self._selectedProtocol == Protocols.PROTOCOL_HYBRID:
|
||||||
|
log.info("*" * 43)
|
||||||
|
log.info("*" + " " * 10 + "NLA Security selected" + " " * 10 + "*")
|
||||||
|
log.info("*" * 43)
|
||||||
|
self._transport.startNLA(ClientTLSContext(), lambda:self._presentation.connect())
|
||||||
|
|
||||||
class Server(X224Layer):
|
class Server(X224Layer):
|
||||||
"""
|
"""
|
||||||
@summary: Server automata of X224 layer
|
@summary: Server automata of X224 layer
|
||||||
"""
|
"""
|
||||||
def __init__(self, presentation, privateKeyFileName, certificateFileName):
|
def __init__(self, presentation, privateKeyFileName = None, certificateFileName = None, forceSSL = False):
|
||||||
"""
|
"""
|
||||||
@param presentation: upper layer, MCS layer in RDP case
|
@param presentation: {layer} upper layer, MCS layer in RDP case
|
||||||
@param privateKeyFileName: file contain server private key
|
@param privateKeyFileName: {str} file contain server private key
|
||||||
@param certficiateFileName: file that contain public key
|
@param certficiateFileName: {str} file that contain public key
|
||||||
|
@param forceSSL: {boolean} reject old client that doerasn't support SSL
|
||||||
"""
|
"""
|
||||||
X224Layer.__init__(self, presentation)
|
X224Layer.__init__(self, presentation)
|
||||||
#Server mode informations for TLS connection
|
#Server mode informations for TLS connection
|
||||||
self._serverPrivateKeyFileName = privateKeyFileName
|
self._serverPrivateKeyFileName = privateKeyFileName
|
||||||
self._serverCertificateFileName = certificateFileName
|
self._serverCertificateFileName = certificateFileName
|
||||||
|
self._forceSSL = forceSSL and not self._serverPrivateKeyFileName is None and not self._serverCertificateFileName is None
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""
|
"""
|
||||||
@@ -241,26 +259,34 @@ class Server(X224Layer):
|
|||||||
"""
|
"""
|
||||||
@summary: Read connection confirm packet
|
@summary: Read connection confirm packet
|
||||||
Next state is send connection confirm
|
Next state is send connection confirm
|
||||||
@param data: Stream
|
@param data: {Stream}
|
||||||
@see : http://msdn.microsoft.com/en-us/library/cc240470.aspx
|
@see : http://msdn.microsoft.com/en-us/library/cc240470.aspx
|
||||||
"""
|
"""
|
||||||
message = ClientConnectionRequestPDU()
|
message = ClientConnectionRequestPDU()
|
||||||
data.readType(message)
|
data.readType(message)
|
||||||
|
|
||||||
if not message.protocolNeg._is_readed or message.protocolNeg.failureCode._is_readed:
|
if not message.protocolNeg._is_readed:
|
||||||
raise InvalidExpectedDataException("Too older RDP client")
|
self._requestedProtocol = Protocols.PROTOCOL_RDP
|
||||||
|
else:
|
||||||
|
self._requestedProtocol = message.protocolNeg.selectedProtocol.value
|
||||||
|
|
||||||
self._requestedProtocol = message.protocolNeg.selectedProtocol.value
|
#match best security layer available
|
||||||
|
if not self._serverPrivateKeyFileName is None and not self._serverCertificateFileName is None:
|
||||||
|
self._selectedProtocol = self._requestedProtocol & Protocols.PROTOCOL_SSL
|
||||||
|
else:
|
||||||
|
self._selectedProtocol = self._requestedProtocol & Protocols.PROTOCOL_RDP
|
||||||
|
|
||||||
if not self._requestedProtocol & Protocols.PROTOCOL_SSL:
|
#if force ssl is enable
|
||||||
|
if not self._selectedProtocol & Protocols.PROTOCOL_SSL and self._forceSSL:
|
||||||
|
log.warning("server reject client because doesn't support SSL")
|
||||||
#send error message and quit
|
#send error message and quit
|
||||||
message = ServerConnectionConfirm()
|
message = ServerConnectionConfirm()
|
||||||
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_FAILURE
|
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_FAILURE
|
||||||
message.protocolNeg.failureCode.value = NegotiationFailureCode.SSL_REQUIRED_BY_SERVER
|
message.protocolNeg.failureCode.value = NegotiationFailureCode.SSL_REQUIRED_BY_SERVER
|
||||||
self._transport.send(message)
|
self._transport.send(message)
|
||||||
raise InvalidExpectedDataException("rdpy needs ssl client compliant")
|
self.close()
|
||||||
|
return
|
||||||
|
|
||||||
self._selectedProtocol = Protocols.PROTOCOL_SSL
|
|
||||||
self.sendConnectionConfirm()
|
self.sendConnectionConfirm()
|
||||||
|
|
||||||
def sendConnectionConfirm(self):
|
def sendConnectionConfirm(self):
|
||||||
@@ -274,8 +300,11 @@ class Server(X224Layer):
|
|||||||
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_RSP
|
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_RSP
|
||||||
message.protocolNeg.selectedProtocol.value = self._selectedProtocol
|
message.protocolNeg.selectedProtocol.value = self._selectedProtocol
|
||||||
self._transport.send(message)
|
self._transport.send(message)
|
||||||
#_transport is TPKT and transport is TCP layer of twisted
|
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
||||||
self._transport.transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
|
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
|
||||||
|
#_transport is TPKT and transport is TCP layer of twisted
|
||||||
|
self._transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
|
||||||
|
|
||||||
#connection is done send to presentation
|
#connection is done send to presentation
|
||||||
self.setNextState(self.recvData)
|
self.setNextState(self.recvData)
|
||||||
self._presentation.connect()
|
self._presentation.connect()
|
||||||
@@ -303,7 +332,7 @@ class ServerTLSContext(ssl.DefaultOpenSSLContextFactory):
|
|||||||
def __init__(self, privateKeyFileName, certificateFileName):
|
def __init__(self, privateKeyFileName, certificateFileName):
|
||||||
class TPDUSSLContext(SSL.Context):
|
class TPDUSSLContext(SSL.Context):
|
||||||
def __init__(self, method):
|
def __init__(self, method):
|
||||||
SSL.Context.__init__(method)
|
SSL.Context.__init__(self, method)
|
||||||
self.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
|
self.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
|
||||||
self.set_options(SSL.OP_TLS_BLOCK_PADDING_BUG)
|
self.set_options(SSL.OP_TLS_BLOCK_PADDING_BUG)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -25,11 +25,11 @@ Implement Remote FrameBuffer protocol use in VNC client and server
|
|||||||
@todo: more encoding rectangle
|
@todo: more encoding rectangle
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.network.layer import RawLayer, RawLayerClientFactory
|
from rdpy.core.layer import RawLayer, RawLayerClientFactory
|
||||||
from rdpy.network.type import UInt8, UInt16Be, UInt32Be, SInt32Be, String, CompositeType
|
from rdpy.core.type import UInt8, UInt16Be, UInt32Be, SInt32Be, String, CompositeType
|
||||||
from rdpy.base.error import InvalidValue, CallPureVirtualFuntion
|
from rdpy.core.error import InvalidValue, CallPureVirtualFuntion
|
||||||
from rdpy.protocol.rfb.pyDes import des
|
from rdpy.security.pyDes import des
|
||||||
import rdpy.base.log as log
|
import rdpy.core.log as log
|
||||||
|
|
||||||
class ProtocolVersion(object):
|
class ProtocolVersion(object):
|
||||||
"""
|
"""
|
||||||
@@ -671,10 +671,11 @@ class ClientFactory(RawLayerClientFactory):
|
|||||||
self.buildObserver(controller, addr)
|
self.buildObserver(controller, addr)
|
||||||
return controller.getProtocol()
|
return controller.getProtocol()
|
||||||
|
|
||||||
def connectionLost(self, rfblayer):
|
def connectionLost(self, rfblayer, reason):
|
||||||
"""
|
"""
|
||||||
@summary: Override this method to handle connection lost
|
@summary: Override this method to handle connection lost
|
||||||
@param rfblayer: rfblayer that cause connectionLost event
|
@param rfblayer: rfblayer that cause connectionLost event
|
||||||
|
@param reason: twisted reason
|
||||||
"""
|
"""
|
||||||
#call controller
|
#call controller
|
||||||
rfblayer._clientListener.onClose()
|
rfblayer._clientListener.onClose()
|
||||||
|
|||||||
0
rdpy/security/__init__.py
Normal file
0
rdpy/security/__init__.py
Normal file
57
rdpy/security/rc4.py
Normal file
57
rdpy/security/rc4.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
"""
|
||||||
|
Copyright (C) 2012 Bo Zhu http://about.bozhu.me
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def KSA(key):
|
||||||
|
keylength = len(key)
|
||||||
|
|
||||||
|
S = range(256)
|
||||||
|
|
||||||
|
j = 0
|
||||||
|
for i in range(256):
|
||||||
|
j = (j + S[i] + key[i % keylength]) % 256
|
||||||
|
S[i], S[j] = S[j], S[i] # swap
|
||||||
|
|
||||||
|
return S
|
||||||
|
|
||||||
|
|
||||||
|
def PRGA(S):
|
||||||
|
i = 0
|
||||||
|
j = 0
|
||||||
|
while True:
|
||||||
|
i = (i + 1) % 256
|
||||||
|
j = (j + S[i]) % 256
|
||||||
|
S[i], S[j] = S[j], S[i] # swap
|
||||||
|
|
||||||
|
K = S[(S[i] + S[j]) % 256]
|
||||||
|
yield K
|
||||||
|
|
||||||
|
|
||||||
|
def RC4(key):
|
||||||
|
S = KSA(key)
|
||||||
|
return PRGA(S)
|
||||||
|
|
||||||
|
def 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])
|
||||||
99
rdpy/security/rsa_wrapper.py
Normal file
99
rdpy/security/rsa_wrapper.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Wrapper around RSA library
|
||||||
|
"""
|
||||||
|
|
||||||
|
import rsa
|
||||||
|
|
||||||
|
def newkeys(size):
|
||||||
|
"""
|
||||||
|
@summary: wrapper around rsa.newkeys function
|
||||||
|
@param size: {integer} size of key
|
||||||
|
"""
|
||||||
|
return rsa.newkeys(size)
|
||||||
|
|
||||||
|
def PublicKey(e, n):
|
||||||
|
"""
|
||||||
|
@param e: {long | str}public exponent
|
||||||
|
@param n: {long | str}modulus
|
||||||
|
"""
|
||||||
|
if isinstance(e, str):
|
||||||
|
e = rsa.transform.bytes2int(e)
|
||||||
|
if isinstance(n, str):
|
||||||
|
n = rsa.transform.bytes2int(n)
|
||||||
|
return { 'e' : e, 'n' : n }
|
||||||
|
|
||||||
|
def PrivateKey(d, n):
|
||||||
|
"""
|
||||||
|
@param d: {long | str}private exponent
|
||||||
|
@param n: {long | str}modulus
|
||||||
|
"""
|
||||||
|
if isinstance(d, str):
|
||||||
|
d = rsa.transform.bytes2int(d)
|
||||||
|
if isinstance(n, str):
|
||||||
|
n = rsa.transform.bytes2int(n)
|
||||||
|
return { 'd' : d, 'n' : n }
|
||||||
|
|
||||||
|
def int2bytes(i, fill_size=None):
|
||||||
|
"""
|
||||||
|
@summary: wrapper of rsa.transform.int2bytes
|
||||||
|
"""
|
||||||
|
return rsa.transform.int2bytes(i,fill_size)
|
||||||
|
|
||||||
|
def random(size):
|
||||||
|
"""
|
||||||
|
@summary: wrapper around rsa.randnum.read_random_bits function
|
||||||
|
@param size: {integer] size in bits
|
||||||
|
@return: {str} random bytes array
|
||||||
|
"""
|
||||||
|
return rsa.randnum.read_random_bits(size)
|
||||||
|
|
||||||
|
def encrypt(message, publicKey):
|
||||||
|
"""
|
||||||
|
@summary: wrapper around rsa.core.encrypt_int function
|
||||||
|
@param message: {str} source message
|
||||||
|
@param publicKey: {rsa.PublicKey}
|
||||||
|
"""
|
||||||
|
return rsa.transform.int2bytes(rsa.core.encrypt_int(rsa.transform.bytes2int(message), publicKey['e'], publicKey['n']), rsa.common.byte_size(publicKey['n']))
|
||||||
|
|
||||||
|
def decrypt(message, privateKey):
|
||||||
|
"""
|
||||||
|
@summary: wrapper around rsa.core.decrypt_int function
|
||||||
|
@param message: {str} source message
|
||||||
|
@param publicKey: {rsa.PrivateKey}
|
||||||
|
"""
|
||||||
|
return rsa.transform.int2bytes(rsa.core.decrypt_int(rsa.transform.bytes2int(message), privateKey['d'], privateKey['n']))
|
||||||
|
|
||||||
|
def sign(message, privateKey):
|
||||||
|
"""
|
||||||
|
@summary: sign message with private key
|
||||||
|
@param message: {str} message to sign
|
||||||
|
@param privateKey : {rsa.privateKey} key use to sugn
|
||||||
|
"""
|
||||||
|
return rsa.transform.int2bytes(rsa.core.encrypt_int(rsa.transform.bytes2int(message), privateKey['d'], privateKey['n']), rsa.common.byte_size(privateKey['n']))
|
||||||
|
|
||||||
|
def verify(message, publicKey):
|
||||||
|
"""
|
||||||
|
@summary: return hash
|
||||||
|
@param message: {str} message to verify
|
||||||
|
@param publicKey : {rsa.publicKey} key use to sugn
|
||||||
|
"""
|
||||||
|
return rsa.transform.int2bytes(rsa.core.decrypt_int(rsa.transform.bytes2int(message), publicKey['e'], publicKey['n']))
|
||||||
157
rdpy/security/x509.py
Normal file
157
rdpy/security/x509.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
@see: https://github.com/filippog/pyasn1/blob/master/examples/x509.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyasn1.type import tag, namedtype, namedval, univ, constraint, char, useful
|
||||||
|
from pyasn1.codec.ber import decoder
|
||||||
|
|
||||||
|
MAX = 64
|
||||||
|
|
||||||
|
class DirectoryString(univ.Choice):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
|
||||||
|
namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
|
||||||
|
namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
|
||||||
|
namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
|
||||||
|
namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
|
||||||
|
namedtype.NamedType('ia5String', char.IA5String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))) # hm, this should not be here!? XXX
|
||||||
|
)
|
||||||
|
|
||||||
|
class AttributeValue(DirectoryString): pass
|
||||||
|
|
||||||
|
class AttributeType(univ.ObjectIdentifier): pass
|
||||||
|
|
||||||
|
class AttributeTypeAndValue(univ.Sequence):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('type', AttributeType()),
|
||||||
|
namedtype.NamedType('value', AttributeValue())
|
||||||
|
)
|
||||||
|
|
||||||
|
class RelativeDistinguishedName(univ.SetOf):
|
||||||
|
componentType = AttributeTypeAndValue()
|
||||||
|
|
||||||
|
class RDNSequence(univ.SequenceOf):
|
||||||
|
componentType = RelativeDistinguishedName()
|
||||||
|
|
||||||
|
class Name(univ.Choice):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('', RDNSequence())
|
||||||
|
)
|
||||||
|
|
||||||
|
class AlgorithmIdentifier(univ.Sequence):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('algorithm', univ.ObjectIdentifier()),
|
||||||
|
namedtype.OptionalNamedType('parameters', univ.Null())
|
||||||
|
# XXX syntax screwed?
|
||||||
|
# namedtype.OptionalNamedType('parameters', univ.ObjectIdentifier())
|
||||||
|
)
|
||||||
|
|
||||||
|
class Extension(univ.Sequence):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('extnID', univ.ObjectIdentifier()),
|
||||||
|
namedtype.DefaultedNamedType('critical', univ.Boolean('False')),
|
||||||
|
namedtype.NamedType('extnValue', univ.OctetString())
|
||||||
|
)
|
||||||
|
|
||||||
|
class Extensions(univ.SequenceOf):
|
||||||
|
componentType = Extension()
|
||||||
|
sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)
|
||||||
|
|
||||||
|
class SubjectPublicKeyInfo(univ.Sequence):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('algorithm', AlgorithmIdentifier()),
|
||||||
|
namedtype.NamedType('subjectPublicKey', univ.BitString())
|
||||||
|
)
|
||||||
|
|
||||||
|
class UniqueIdentifier(univ.BitString): pass
|
||||||
|
|
||||||
|
class Time(univ.Choice):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('utcTime', useful.UTCTime()),
|
||||||
|
namedtype.NamedType('generalTime', useful.GeneralizedTime())
|
||||||
|
)
|
||||||
|
|
||||||
|
class Validity(univ.Sequence):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('notBefore', Time()),
|
||||||
|
namedtype.NamedType('notAfter', Time())
|
||||||
|
)
|
||||||
|
|
||||||
|
class CertificateSerialNumber(univ.Integer): pass
|
||||||
|
|
||||||
|
class Version(univ.Integer):
|
||||||
|
namedValues = namedval.NamedValues(
|
||||||
|
('v1', 0), ('v2', 1), ('v3', 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
class TBSCertificate(univ.Sequence):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.DefaultedNamedType('version', Version('v1', tagSet=Version.tagSet.tagExplicitly(tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))),
|
||||||
|
namedtype.NamedType('serialNumber', CertificateSerialNumber()),
|
||||||
|
namedtype.NamedType('signature', AlgorithmIdentifier()),
|
||||||
|
namedtype.NamedType('issuer', Name()),
|
||||||
|
namedtype.NamedType('validity', Validity()),
|
||||||
|
namedtype.NamedType('subject', Name()),
|
||||||
|
namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()),
|
||||||
|
namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
||||||
|
namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
|
||||||
|
namedtype.OptionalNamedType('extensions', Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
|
||||||
|
)
|
||||||
|
|
||||||
|
class X509Certificate(univ.Sequence):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('tbsCertificate', TBSCertificate()),
|
||||||
|
namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
|
||||||
|
namedtype.NamedType('signatureValue', univ.BitString())
|
||||||
|
)
|
||||||
|
|
||||||
|
class RSAPublicKey(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: asn1 public rsa key
|
||||||
|
@see: https://tools.ietf.org/html/rfc3447
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('modulus', univ.Integer()),
|
||||||
|
namedtype.NamedType('publicExponent', univ.Integer()),
|
||||||
|
)
|
||||||
|
|
||||||
|
def load(stream):
|
||||||
|
"""
|
||||||
|
@summary: load X509 certificate
|
||||||
|
@param stream: {str}
|
||||||
|
"""
|
||||||
|
return decoder.decode(stream, asn1Spec=X509Certificate())[0]
|
||||||
|
|
||||||
|
def extractRSAKey(certificate):
|
||||||
|
"""
|
||||||
|
@summary: try to extract rsa key
|
||||||
|
@return: (modulus, public exponent)
|
||||||
|
"""
|
||||||
|
#http://www.alvestrand.no/objectid/1.2.840.113549.1.1.1.html
|
||||||
|
|
||||||
|
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
|
||||||
182
rdpy/ui/qt4.py
182
rdpy/ui/qt4.py
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -26,20 +26,20 @@ QRemoteDesktop is a widget use for render in rdpy
|
|||||||
from PyQt4 import QtGui, QtCore
|
from PyQt4 import QtGui, QtCore
|
||||||
from rdpy.protocol.rfb.rfb import RFBClientObserver
|
from rdpy.protocol.rfb.rfb import RFBClientObserver
|
||||||
from rdpy.protocol.rdp.rdp import RDPClientObserver
|
from rdpy.protocol.rdp.rdp import RDPClientObserver
|
||||||
from rdpy.base.error import CallPureVirtualFuntion
|
from rdpy.core.error import CallPureVirtualFuntion
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import rdpy.base.log as log
|
import rdpy.core.log as log
|
||||||
import rle
|
import rle
|
||||||
|
|
||||||
class QAdaptor(object):
|
class QAdaptor(object):
|
||||||
"""
|
"""
|
||||||
Adaptor model with link between protocol
|
@summary: Adaptor model with link between protocol
|
||||||
And Qt widget
|
And Qt widget
|
||||||
"""
|
"""
|
||||||
def sendMouseEvent(self, e, isPressed):
|
def sendMouseEvent(self, e, isPressed):
|
||||||
"""
|
"""
|
||||||
Interface to send mouse event to protocol stack
|
@summary: Interface to send mouse event to protocol stack
|
||||||
@param e: QMouseEvent
|
@param e: QMouseEvent
|
||||||
@param isPressed: event come from press or release action
|
@param isPressed: event come from press or release action
|
||||||
"""
|
"""
|
||||||
@@ -47,21 +47,22 @@ class QAdaptor(object):
|
|||||||
|
|
||||||
def sendKeyEvent(self, e, isPressed):
|
def sendKeyEvent(self, e, isPressed):
|
||||||
"""
|
"""
|
||||||
Interface to send key event to protocol stack
|
@summary: Interface to send key event to protocol stack
|
||||||
@param e: QEvent
|
@param e: QEvent
|
||||||
@param isPressed: event come from press or release action
|
@param isPressed: event come from press or release action
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendKeyEvent", "QAdaptor"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendKeyEvent", "QAdaptor"))
|
||||||
|
|
||||||
def getWidget(self):
|
def sendWheelEvent(self, e):
|
||||||
"""
|
"""
|
||||||
@return: widget use for render
|
@summary: Interface to send wheel event to protocol stack
|
||||||
|
@param e: QWheelEvent
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getWidget", "QAdaptor"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendWheelEvent", "QAdaptor"))
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
"""
|
"""
|
||||||
Call when you want to close connection
|
@summary: Call when you want to close connection
|
||||||
@param: QCloseEvent
|
@param: QCloseEvent
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "closeEvent", "QAdaptor"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "closeEvent", "QAdaptor"))
|
||||||
@@ -77,7 +78,7 @@ def qtImageFormatFromRFBPixelFormat(pixelFormat):
|
|||||||
|
|
||||||
class RFBClientQt(RFBClientObserver, QAdaptor):
|
class RFBClientQt(RFBClientObserver, QAdaptor):
|
||||||
"""
|
"""
|
||||||
QAdaptor for specific RFB protocol stack
|
@summary: QAdaptor for specific RFB protocol stack
|
||||||
is to an RFB observer
|
is to an RFB observer
|
||||||
"""
|
"""
|
||||||
def __init__(self, controller):
|
def __init__(self, controller):
|
||||||
@@ -87,7 +88,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
|
|||||||
@param height: height of widget
|
@param height: height of widget
|
||||||
"""
|
"""
|
||||||
RFBClientObserver.__init__(self, controller)
|
RFBClientObserver.__init__(self, controller)
|
||||||
self._widget = QRemoteDesktop(self, 1024, 800)
|
self._widget = QRemoteDesktop(1024, 800, self)
|
||||||
|
|
||||||
def getWidget(self):
|
def getWidget(self):
|
||||||
"""
|
"""
|
||||||
@@ -97,7 +98,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
|
|||||||
|
|
||||||
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
|
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
|
||||||
"""
|
"""
|
||||||
Implement RFBClientObserver interface
|
@summary: Implement RFBClientObserver interface
|
||||||
@param width: width of new image
|
@param width: width of new image
|
||||||
@param height: height of new image
|
@param height: height of new image
|
||||||
@param x: x position of new image
|
@param x: x position of new image
|
||||||
@@ -119,13 +120,11 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
|
|||||||
@summary: event when server send cut text event
|
@summary: event when server send cut text event
|
||||||
@param text: text received
|
@param text: text received
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
def onBell(self):
|
def onBell(self):
|
||||||
"""
|
"""
|
||||||
@summary: event when server send biiip
|
@summary: event when server send biiip
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@@ -136,7 +135,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
|
|||||||
|
|
||||||
def sendMouseEvent(self, e, isPressed):
|
def sendMouseEvent(self, e, isPressed):
|
||||||
"""
|
"""
|
||||||
Convert Qt mouse event to RFB mouse event
|
@summary: Convert Qt mouse event to RFB mouse event
|
||||||
@param e: qMouseEvent
|
@param e: qMouseEvent
|
||||||
@param isPressed: event come from press or release action
|
@param isPressed: event come from press or release action
|
||||||
"""
|
"""
|
||||||
@@ -152,29 +151,37 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
|
|||||||
|
|
||||||
def sendKeyEvent(self, e, isPressed):
|
def sendKeyEvent(self, e, isPressed):
|
||||||
"""
|
"""
|
||||||
Convert Qt key press event to RFB press event
|
@summary: Convert Qt key press event to RFB press event
|
||||||
@param e: qKeyEvent
|
@param e: qKeyEvent
|
||||||
@param isPressed: event come from press or release action
|
@param isPressed: event come from press or release action
|
||||||
"""
|
"""
|
||||||
self.keyEvent(isPressed, e.nativeVirtualKey())
|
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):
|
def closeEvent(self, e):
|
||||||
"""
|
"""
|
||||||
Call when you want to close connection
|
@summary: Call when you want to close connection
|
||||||
@param: QCloseEvent
|
@param: QCloseEvent
|
||||||
"""
|
"""
|
||||||
self._controller.close()
|
self._controller.close()
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
Call when stack is close
|
@summary: Call when stack is close
|
||||||
"""
|
"""
|
||||||
#do something maybe a message
|
#do something maybe a message
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
|
def RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data):
|
||||||
"""
|
"""
|
||||||
Bitmap transformation to Qt object
|
@summary: Bitmap transformation to Qt object
|
||||||
@param width: width of bitmap
|
@param width: width of bitmap
|
||||||
@param height: height of bitmap
|
@param height: height of bitmap
|
||||||
@param bitsPerPixel: number of bit per pixel
|
@param bitsPerPixel: number of bit per pixel
|
||||||
@@ -204,15 +211,15 @@ def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
|
|||||||
if isCompress:
|
if isCompress:
|
||||||
buf = bytearray(width * height * 3)
|
buf = bytearray(width * height * 3)
|
||||||
rle.bitmap_decompress(buf, width, height, data, 3)
|
rle.bitmap_decompress(buf, width, height, data, 3)
|
||||||
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB24)
|
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB888)
|
||||||
else:
|
else:
|
||||||
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB24).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
|
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB888).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
|
||||||
|
|
||||||
elif bitsPerPixel == 32:
|
elif bitsPerPixel == 32:
|
||||||
if isCompress:
|
if isCompress:
|
||||||
buf = bytearray(width * height * 4)
|
buf = bytearray(width * height * 4)
|
||||||
rle.bitmap_decompress(buf, width, height, data, 4)
|
rle.bitmap_decompress(buf, width, height, data, 4)
|
||||||
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB24)
|
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB32)
|
||||||
else:
|
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 = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB32).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
|
||||||
else:
|
else:
|
||||||
@@ -222,16 +229,16 @@ def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
|
|||||||
|
|
||||||
class RDPClientQt(RDPClientObserver, QAdaptor):
|
class RDPClientQt(RDPClientObserver, QAdaptor):
|
||||||
"""
|
"""
|
||||||
Adaptor for RDP client
|
@summary: Adaptor for RDP client
|
||||||
"""
|
"""
|
||||||
def __init__(self, controller, width, height):
|
def __init__(self, controller, width, height):
|
||||||
"""
|
"""
|
||||||
@param controller: RDP controller
|
@param controller: {RDPClientController} RDP controller
|
||||||
@param width: width of widget
|
@param width: {int} width of widget
|
||||||
@param height: height of widget
|
@param height: {int} height of widget
|
||||||
"""
|
"""
|
||||||
RDPClientObserver.__init__(self, controller)
|
RDPClientObserver.__init__(self, controller)
|
||||||
self._widget = QRemoteDesktop(self, width, height)
|
self._widget = QRemoteDesktop(width, height, self)
|
||||||
#set widget screen to RDP stack
|
#set widget screen to RDP stack
|
||||||
controller.setScreen(width, height)
|
controller.setScreen(width, height)
|
||||||
|
|
||||||
@@ -243,7 +250,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
|
|||||||
|
|
||||||
def sendMouseEvent(self, e, isPressed):
|
def sendMouseEvent(self, e, isPressed):
|
||||||
"""
|
"""
|
||||||
Convert Qt mouse event to RDP mouse event
|
@summary: Convert Qt mouse event to RDP mouse event
|
||||||
@param e: qMouseEvent
|
@param e: qMouseEvent
|
||||||
@param isPressed: event come from press(true) or release(false) action
|
@param isPressed: event come from press(true) or release(false) action
|
||||||
"""
|
"""
|
||||||
@@ -251,15 +258,15 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
|
|||||||
buttonNumber = 0
|
buttonNumber = 0
|
||||||
if button == QtCore.Qt.LeftButton:
|
if button == QtCore.Qt.LeftButton:
|
||||||
buttonNumber = 1
|
buttonNumber = 1
|
||||||
elif button == QtCore.Qt.MidButton:
|
|
||||||
buttonNumber = 2
|
|
||||||
elif button == QtCore.Qt.RightButton:
|
elif button == QtCore.Qt.RightButton:
|
||||||
|
buttonNumber = 2
|
||||||
|
elif button == QtCore.Qt.MidButton:
|
||||||
buttonNumber = 3
|
buttonNumber = 3
|
||||||
self._controller.sendPointerEvent(e.pos().x(), e.pos().y(), buttonNumber, isPressed)
|
self._controller.sendPointerEvent(e.pos().x(), e.pos().y(), buttonNumber, isPressed)
|
||||||
|
|
||||||
def sendKeyEvent(self, e, isPressed):
|
def sendKeyEvent(self, e, isPressed):
|
||||||
"""
|
"""
|
||||||
Convert Qt key press event to RFB press event
|
@summary: Convert Qt key press event to RDP press event
|
||||||
@param e: QKeyEvent
|
@param e: QKeyEvent
|
||||||
@param isPressed: event come from press or release action
|
@param isPressed: event come from press or release action
|
||||||
"""
|
"""
|
||||||
@@ -267,66 +274,77 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
|
|||||||
if sys.platform == "linux2":
|
if sys.platform == "linux2":
|
||||||
code -= 8
|
code -= 8
|
||||||
self._controller.sendKeyEventScancode(code, isPressed)
|
self._controller.sendKeyEventScancode(code, isPressed)
|
||||||
|
|
||||||
|
def sendWheelEvent(self, e):
|
||||||
|
"""
|
||||||
|
@summary: Convert Qt wheel event to RDP Wheel event
|
||||||
|
@param e: QKeyEvent
|
||||||
|
@param isPressed: event come from press or release action
|
||||||
|
"""
|
||||||
|
self._controller.sendWheelEvent(e.pos().x(), e.pos().y(), (abs(e.delta()) / 8) / 15, e.delta() < 0, e.orientation() == QtCore.Qt.Horizontal)
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
"""
|
"""
|
||||||
Convert Qt close widget event into close stack event
|
@summary: Convert Qt close widget event into close stack event
|
||||||
@param e: QCloseEvent
|
@param e: QCloseEvent
|
||||||
"""
|
"""
|
||||||
self._controller.close()
|
self._controller.close()
|
||||||
|
|
||||||
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||||
"""
|
"""
|
||||||
Notify bitmap update
|
@summary: Notify bitmap update
|
||||||
@param destLeft: xmin position
|
@param destLeft: {int} xmin position
|
||||||
@param destTop: ymin position
|
@param destTop: {int} ymin position
|
||||||
@param destRight: xmax position because RDP can send bitmap with padding
|
@param destRight: {int} xmax position because RDP can send bitmap with padding
|
||||||
@param destBottom: ymax position because RDP can send bitmap with padding
|
@param destBottom: {int} ymax position because RDP can send bitmap with padding
|
||||||
@param width: width of bitmap
|
@param width: {int} width of bitmap
|
||||||
@param height: height of bitmap
|
@param height: {int} height of bitmap
|
||||||
@param bitsPerPixel: number of bit per pixel
|
@param bitsPerPixel: {int} number of bit per pixel
|
||||||
@param isCompress: use RLE compression
|
@param isCompress: {bool} use RLE compression
|
||||||
@param data: bitmap data
|
@param data: {str} bitmap data
|
||||||
"""
|
"""
|
||||||
image = RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data);
|
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data)
|
||||||
#if image need to be cut
|
#if image need to be cut
|
||||||
#For bit alignement server may send more than image pixel
|
#For bit alignement server may send more than image pixel
|
||||||
self._widget.notifyImage(destLeft, destTop, image, destRight - destLeft + 1, destBottom - destTop + 1)
|
self._widget.notifyImage(destLeft, destTop, image, destRight - destLeft + 1, destBottom - destTop + 1)
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
Call when stack is ready
|
@summary: Call when stack is ready
|
||||||
|
@see: rdp.RDPClientObserver.onReady
|
||||||
"""
|
"""
|
||||||
#do something maybe a loader
|
#do something maybe a loader
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Windows session is ready
|
||||||
|
@see: rdp.RDPClientObserver.onSessionReady
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
Call when stack is close
|
@summary: Call when stack is close
|
||||||
|
@see: rdp.RDPClientObserver.onClose
|
||||||
"""
|
"""
|
||||||
#do something maybe a message
|
#do something maybe a message
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class QRemoteDesktop(QtGui.QWidget):
|
class QRemoteDesktop(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
Qt display widget
|
@summary: Qt display widget
|
||||||
"""
|
"""
|
||||||
def __init__(self, adaptor, width, height):
|
def __init__(self, width, height, adaptor):
|
||||||
"""
|
"""
|
||||||
@param adaptor: QAdaptor
|
@param adaptor: {QAdaptor}
|
||||||
|
@param width: {int} width of widget
|
||||||
|
@param height: {int} height of widget
|
||||||
"""
|
"""
|
||||||
super(QRemoteDesktop, self).__init__()
|
super(QRemoteDesktop, self).__init__()
|
||||||
#adaptor use to send
|
#adaptor use to send
|
||||||
self._adaptor = adaptor
|
self._adaptor = adaptor
|
||||||
#set correct size
|
#set correct size
|
||||||
self.resize(width, height)
|
self.resize(width, height)
|
||||||
#refresh stack of image
|
|
||||||
#because we can update image only in paint
|
|
||||||
#event function. When protocol receive image
|
|
||||||
#we will stock into refresh list
|
|
||||||
#and in paint event paint list of all refresh images
|
|
||||||
self._refresh = []
|
|
||||||
#bind mouse event
|
#bind mouse event
|
||||||
self.setMouseTracking(True)
|
self.setMouseTracking(True)
|
||||||
#buffer image
|
#buffer image
|
||||||
@@ -334,70 +352,80 @@ class QRemoteDesktop(QtGui.QWidget):
|
|||||||
|
|
||||||
def notifyImage(self, x, y, qimage, width, height):
|
def notifyImage(self, x, y, qimage, width, height):
|
||||||
"""
|
"""
|
||||||
Function call from QAdaptor
|
@summary: Function call from QAdaptor
|
||||||
@param x: x position of new image
|
@param x: x position of new image
|
||||||
@param y: y position of new image
|
@param y: y position of new image
|
||||||
@param qimage: new QImage
|
@param qimage: new QImage
|
||||||
"""
|
"""
|
||||||
#save in refresh list (order is important)
|
#fill buffer image
|
||||||
self._refresh.append((x, y, qimage, width, height))
|
with QtGui.QPainter(self._buffer) as qp:
|
||||||
|
qp.drawImage(x, y, qimage, 0, 0, width, height)
|
||||||
#force update
|
#force update
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
def resize(self, width, height):
|
||||||
|
"""
|
||||||
|
@summary: override resize function
|
||||||
|
@param width: {int} width of widget
|
||||||
|
@param height: {int} height of widget
|
||||||
|
"""
|
||||||
|
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
|
||||||
|
QtGui.QWidget.resize(self, width, height)
|
||||||
|
|
||||||
def paintEvent(self, e):
|
def paintEvent(self, e):
|
||||||
"""
|
"""
|
||||||
Call when Qt renderer engine estimate that is needed
|
@summary: Call when Qt renderer engine estimate that is needed
|
||||||
@param e: QEvent
|
@param e: QEvent
|
||||||
"""
|
"""
|
||||||
#fill buffer image
|
|
||||||
with QtGui.QPainter(self._buffer) as qp:
|
|
||||||
#draw image
|
|
||||||
for (x, y, image, width, height) in self._refresh:
|
|
||||||
qp.drawImage(x, y, image, 0, 0, width, height)
|
|
||||||
#draw in widget
|
#draw in widget
|
||||||
with QtGui.QPainter(self) as qp:
|
with QtGui.QPainter(self) as qp:
|
||||||
qp.drawImage(0, 0, self._buffer)
|
qp.drawImage(0, 0, self._buffer)
|
||||||
|
|
||||||
self._refresh = []
|
|
||||||
|
|
||||||
def mouseMoveEvent(self, event):
|
def mouseMoveEvent(self, event):
|
||||||
"""
|
"""
|
||||||
Call when mouse move
|
@summary: Call when mouse move
|
||||||
@param event: QMouseEvent
|
@param event: QMouseEvent
|
||||||
"""
|
"""
|
||||||
self._adaptor.sendMouseEvent(event, False)
|
self._adaptor.sendMouseEvent(event, False)
|
||||||
|
|
||||||
def mousePressEvent(self, event):
|
def mousePressEvent(self, event):
|
||||||
"""
|
"""
|
||||||
Call when button mouse is pressed
|
@summary: Call when button mouse is pressed
|
||||||
@param event: QMouseEvent
|
@param event: QMouseEvent
|
||||||
"""
|
"""
|
||||||
self._adaptor.sendMouseEvent(event, True)
|
self._adaptor.sendMouseEvent(event, True)
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
def mouseReleaseEvent(self, event):
|
||||||
"""
|
"""
|
||||||
Call when button mouse is released
|
@summary: Call when button mouse is released
|
||||||
@param event: QMouseEvent
|
@param event: QMouseEvent
|
||||||
"""
|
"""
|
||||||
self._adaptor.sendMouseEvent(event, False)
|
self._adaptor.sendMouseEvent(event, False)
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
"""
|
"""
|
||||||
Call when button key is pressed
|
@summary: Call when button key is pressed
|
||||||
@param event: QKeyEvent
|
@param event: QKeyEvent
|
||||||
"""
|
"""
|
||||||
self._adaptor.sendKeyEvent(event, True)
|
self._adaptor.sendKeyEvent(event, True)
|
||||||
|
|
||||||
def keyReleaseEvent(self, event):
|
def keyReleaseEvent(self, event):
|
||||||
"""
|
"""
|
||||||
Call when button key is released
|
@summary: Call when button key is released
|
||||||
@param event: QKeyEvent
|
@param event: QKeyEvent
|
||||||
"""
|
"""
|
||||||
self._adaptor.sendKeyEvent(event, False)
|
self._adaptor.sendKeyEvent(event, False)
|
||||||
|
|
||||||
|
def wheelEvent(self, event):
|
||||||
|
"""
|
||||||
|
@summary: Call on wheel event
|
||||||
|
@param event: QWheelEvent
|
||||||
|
"""
|
||||||
|
self._adaptor.sendWheelEvent(event)
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
"""
|
"""
|
||||||
Call when widget is closed
|
@summary: Call when widget is closed
|
||||||
@param event: QCloseEvent
|
@param event: QCloseEvent
|
||||||
"""
|
"""
|
||||||
self._adaptor.closeEvent(event)
|
self._adaptor.closeEvent(event)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2014 Sylvain Peyrefitte
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
#
|
#
|
||||||
# This file is part of rdpy.
|
# This file is part of rdpy.
|
||||||
#
|
#
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"""
|
"""
|
||||||
Fake widget
|
Fake widget
|
||||||
"""
|
"""
|
||||||
from rdpy.base.error import CallPureVirtualFuntion
|
from rdpy.core.error import CallPureVirtualFuntion
|
||||||
from PyQt4 import QtGui, QtCore
|
from PyQt4 import QtGui, QtCore
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
29
setup.py
29
setup.py
@@ -1,34 +1,47 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import setuptools
|
||||||
from distutils.core import setup, Extension
|
from distutils.core import setup, Extension
|
||||||
|
|
||||||
setup(name='rdpy',
|
setup(name='rdpy',
|
||||||
version='1.0',
|
version='1.3.2',
|
||||||
description='Remote Desktop Protocol in Python',
|
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 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='Sylvain Peyrefitte',
|
||||||
author_email='citronneur@gmail.com',
|
author_email='citronneur@gmail.com',
|
||||||
url='https://github.com/citronneur/rdpy',
|
url='https://github.com/citronneur/rdpy',
|
||||||
packages=[
|
packages=[
|
||||||
'rdpy',
|
'rdpy',
|
||||||
'rdpy.base',
|
'rdpy.core',
|
||||||
'rdpy.network',
|
'rdpy.security',
|
||||||
'rdpy.protocol',
|
'rdpy.protocol',
|
||||||
'rdpy.protocol.rdp',
|
'rdpy.protocol.rdp',
|
||||||
'rdpy.protocol.rdp.pdu',
|
'rdpy.protocol.rdp.pdu',
|
||||||
|
'rdpy.protocol.rdp.nla',
|
||||||
|
'rdpy.protocol.rdp.t125',
|
||||||
'rdpy.protocol.rfb',
|
'rdpy.protocol.rfb',
|
||||||
'rdpy.ui'
|
'rdpy.ui'
|
||||||
],
|
],
|
||||||
ext_modules=[Extension('rle', ['ext/rle.c'])],
|
ext_modules=[Extension('rle', ['ext/rle.c'])],
|
||||||
scripts = [
|
scripts = [
|
||||||
'bin/rdpy-rdpclient',
|
'bin/rdpy-rdpclient.py',
|
||||||
'bin/rdpy-rdpproxy',
|
'bin/rdpy-rdphoneypot.py',
|
||||||
'bin/rdpy-rdpscreenshot',
|
'bin/rdpy-rdpmitm.py',
|
||||||
'bin/rdpy-vncclient',
|
'bin/rdpy-rdpscreenshot.py',
|
||||||
'bin/rdpy-vncscreenshot'
|
'bin/rdpy-rssplayer.py',
|
||||||
|
'bin/rdpy-vncclient.py',
|
||||||
|
'bin/rdpy-vncscreenshot.py'
|
||||||
],
|
],
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'twisted',
|
'twisted',
|
||||||
'pyopenssl',
|
'pyopenssl',
|
||||||
|
'service_identity',
|
||||||
'qt4reactor',
|
'qt4reactor',
|
||||||
|
'rsa',
|
||||||
|
'pyasn1',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
"""
|
"""
|
||||||
unit test for rdpy.base.const module
|
unit test for rdpy.core.const module
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os, sys
|
import os, sys
|
||||||
@@ -26,10 +26,10 @@ import os, sys
|
|||||||
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import rdpy.base.const
|
import rdpy.core.const
|
||||||
import rdpy.network.type
|
import rdpy.core.type
|
||||||
|
|
||||||
class ConstCase(unittest.TestCase):
|
class ConstTest(unittest.TestCase):
|
||||||
'''
|
'''
|
||||||
represent test case for all classes and function
|
represent test case for all classes and function
|
||||||
present in rdpy.base.const
|
present in rdpy.base.const
|
||||||
@@ -38,19 +38,19 @@ class ConstCase(unittest.TestCase):
|
|||||||
'''
|
'''
|
||||||
test if type attributes decorator works
|
test if type attributes decorator works
|
||||||
'''
|
'''
|
||||||
@rdpy.base.const.TypeAttributes(rdpy.network.type.UInt16Le)
|
@rdpy.core.const.TypeAttributes(rdpy.core.type.UInt16Le)
|
||||||
class Test:
|
class Test:
|
||||||
MEMBER_1 = 1
|
MEMBER_1 = 1
|
||||||
MEMBER_2 = 2
|
MEMBER_2 = 2
|
||||||
|
|
||||||
self.assertIsInstance(Test.MEMBER_1, rdpy.network.type.UInt16Le, "MEMBER_1 is not in correct type")
|
self.assertIsInstance(Test.MEMBER_1, rdpy.core.type.UInt16Le, "MEMBER_1 is not in correct type")
|
||||||
self.assertIsInstance(Test.MEMBER_2, rdpy.network.type.UInt16Le, "MEMBER_2 is not in correct type")
|
self.assertIsInstance(Test.MEMBER_2, rdpy.core.type.UInt16Le, "MEMBER_2 is not in correct type")
|
||||||
|
|
||||||
def test_const(self):
|
def test_const(self):
|
||||||
'''
|
'''
|
||||||
test if get on const class member generate new object each
|
test if get on const class member generate new object each
|
||||||
'''
|
'''
|
||||||
@rdpy.base.const.ConstAttributes
|
@rdpy.core.const.ConstAttributes
|
||||||
class Test:
|
class Test:
|
||||||
MEMBER_1 = 1
|
MEMBER_1 = 1
|
||||||
MEMBER_2 = 2
|
MEMBER_2 = 2
|
||||||
@@ -26,12 +26,12 @@ import os, sys
|
|||||||
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import rdpy.network.layer
|
import rdpy.core.layer
|
||||||
|
|
||||||
class LayerCase(unittest.TestCase):
|
class LayerTest(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
@summary: represent test case for all classes and function
|
@summary: represent test case for all classes and function
|
||||||
present in rdpy.network.layer
|
present in rdpy.core.layer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class LayerCaseException(Exception):
|
class LayerCaseException(Exception):
|
||||||
@@ -44,33 +44,33 @@ class LayerCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
@summary: test if connect event is send from transport to presentation
|
@summary: test if connect event is send from transport to presentation
|
||||||
"""
|
"""
|
||||||
class TestConnect(rdpy.network.layer.Layer):
|
class TestConnect(rdpy.core.layer.Layer):
|
||||||
def connect(self):
|
def connect(self):
|
||||||
raise LayerCase.LayerCaseException()
|
raise LayerTest.LayerCaseException()
|
||||||
|
|
||||||
self.assertRaises(LayerCase.LayerCaseException, rdpy.network.layer.Layer(presentation = TestConnect()).connect)
|
self.assertRaises(LayerTest.LayerCaseException, rdpy.core.layer.Layer(presentation = TestConnect()).connect)
|
||||||
|
|
||||||
def test_layer_automata_more_than_expected(self):
|
def test_layer_automata_more_than_expected(self):
|
||||||
"""
|
"""
|
||||||
@summary: test layer automata mechanism if data received is more than expected
|
@summary: test layer automata mechanism if data received is more than expected
|
||||||
"""
|
"""
|
||||||
class TestAutomata(rdpy.network.layer.RawLayer):
|
class TestAutomata(rdpy.core.layer.RawLayer):
|
||||||
def expectedCallBack(self, data):
|
def expectedCallBack(self, data):
|
||||||
if data.dataLen() == 4:
|
if data.dataLen() == 4:
|
||||||
raise LayerCase.LayerCaseException()
|
raise LayerTest.LayerCaseException()
|
||||||
|
|
||||||
t = TestAutomata()
|
t = TestAutomata()
|
||||||
t.expect(4, t.expectedCallBack)
|
t.expect(4, t.expectedCallBack)
|
||||||
self.assertRaises(LayerCase.LayerCaseException, t.dataReceived, "\x00\x00\x00\x00\x00")
|
self.assertRaises(LayerTest.LayerCaseException, t.dataReceived, "\x00\x00\x00\x00\x00")
|
||||||
|
|
||||||
def test_layer_automata_less_than_expected(self):
|
def test_layer_automata_less_than_expected(self):
|
||||||
"""
|
"""
|
||||||
@summary: test layer automata mechanism
|
@summary: test layer automata mechanism
|
||||||
"""
|
"""
|
||||||
class TestAutomata(rdpy.network.layer.RawLayer):
|
class TestAutomata(rdpy.core.layer.RawLayer):
|
||||||
def expectedCallBack(self, data):
|
def expectedCallBack(self, data):
|
||||||
if data.dataLen() == 4:
|
if data.dataLen() == 4:
|
||||||
raise LayerCase.LayerCaseException()
|
raise LayerTest.LayerCaseException()
|
||||||
|
|
||||||
t = TestAutomata()
|
t = TestAutomata()
|
||||||
t.expect(4, t.expectedCallBack)
|
t.expect(4, t.expectedCallBack)
|
||||||
@@ -26,10 +26,10 @@ import os, sys
|
|||||||
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import rdpy.network.type
|
import rdpy.core.type
|
||||||
from rdpy.base.error import InvalidSize
|
from rdpy.core.error import InvalidSize
|
||||||
|
|
||||||
class TypeCase(unittest.TestCase):
|
class TypeTest(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
@summary: represent test case for all classes and function
|
@summary: represent test case for all classes and function
|
||||||
present in rdpy.network.type
|
present in rdpy.network.type
|
||||||
@@ -38,24 +38,24 @@ class TypeCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
@summary: test if callable value with const ctor doesn't change value
|
@summary: test if callable value with const ctor doesn't change value
|
||||||
"""
|
"""
|
||||||
c = rdpy.network.type.CallableValue(5)
|
c = rdpy.core.type.CallableValue(5)
|
||||||
self.assertEqual(c.value, 5, "invalid callable const")
|
self.assertEqual(c.value, 5, "invalid callable const")
|
||||||
|
|
||||||
def test_callable_value_lambda(self):
|
def test_callable_value_lambda(self):
|
||||||
"""
|
"""
|
||||||
@summary: test if callable value with lambda ctor return dynamic value
|
@summary: test if callable value with lambda ctor return dynamic value
|
||||||
"""
|
"""
|
||||||
c = rdpy.network.type.CallableValue(lambda:5)
|
c = rdpy.core.type.CallableValue(lambda:5)
|
||||||
self.assertEqual(c.value, 5, "invalid callable lambda")
|
self.assertEqual(c.value, 5, "invalid callable lambda")
|
||||||
|
|
||||||
def test_type_write_conditional_true(self):
|
def test_type_write_conditional_true(self):
|
||||||
"""
|
"""
|
||||||
@summary: test when write is obligatory call write function
|
@summary: test when write is obligatory call write function
|
||||||
"""
|
"""
|
||||||
class TestType(rdpy.network.type.Type):
|
class TestType(rdpy.core.type.Type):
|
||||||
def __write__(self, s):
|
def __write__(self, s):
|
||||||
raise Exception()
|
raise Exception()
|
||||||
s = rdpy.network.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
self.assertRaises(Exception, s.writeType, TestType(conditional = lambda:True))
|
self.assertRaises(Exception, s.writeType, TestType(conditional = lambda:True))
|
||||||
|
|
||||||
@unittest.expectedFailure
|
@unittest.expectedFailure
|
||||||
@@ -63,20 +63,20 @@ class TypeCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
@summary: test when write doesn't needed, doesn't call write function
|
@summary: test when write doesn't needed, doesn't call write function
|
||||||
"""
|
"""
|
||||||
class TestType(rdpy.network.type.Type):
|
class TestType(rdpy.core.type.Type):
|
||||||
def __write__(self, s):
|
def __write__(self, s):
|
||||||
raise Exception()
|
raise Exception()
|
||||||
s = rdpy.network.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
self.assertRaises(Exception, s.writeType, TestType(conditional = lambda:False))
|
self.assertRaises(Exception, s.writeType, TestType(conditional = lambda:False))
|
||||||
|
|
||||||
def test_type_read_conditional_true(self):
|
def test_type_read_conditional_true(self):
|
||||||
"""
|
"""
|
||||||
@summary: test when read is obligatory call write function
|
@summary: test when read is obligatory call write function
|
||||||
"""
|
"""
|
||||||
class TestType(rdpy.network.type.Type):
|
class TestType(rdpy.core.type.Type):
|
||||||
def __read__(self, s):
|
def __read__(self, s):
|
||||||
raise Exception()
|
raise Exception()
|
||||||
s = rdpy.network.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
self.assertRaises(Exception, s.readType, TestType(conditional = lambda:True))
|
self.assertRaises(Exception, s.readType, TestType(conditional = lambda:True))
|
||||||
|
|
||||||
@unittest.expectedFailure
|
@unittest.expectedFailure
|
||||||
@@ -84,10 +84,10 @@ class TypeCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
@summary: test when read doesn't needed, doesn't call read function
|
@summary: test when read doesn't needed, doesn't call read function
|
||||||
"""
|
"""
|
||||||
class TestType(rdpy.network.type.Type):
|
class TestType(rdpy.core.type.Type):
|
||||||
def __read__(self, s):
|
def __read__(self, s):
|
||||||
raise Exception()
|
raise Exception()
|
||||||
s = rdpy.network.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
self.assertRaises(Exception, s.readType, TestType(conditional = lambda:False))
|
self.assertRaises(Exception, s.readType, TestType(conditional = lambda:False))
|
||||||
|
|
||||||
|
|
||||||
@@ -95,106 +95,106 @@ class TypeCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
@summary: test if sizeof of simple type is init value(4) when type is conditional true
|
@summary: test if sizeof of simple type is init value(4) when type is conditional true
|
||||||
"""
|
"""
|
||||||
v = rdpy.network.type.SimpleType("I", 4, False, 0, conditional = lambda:True)
|
v = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:True)
|
||||||
self.assertEqual(rdpy.network.type.sizeof(v), 4, "invalid sizeof")
|
self.assertEqual(rdpy.core.type.sizeof(v), 4, "invalid sizeof")
|
||||||
|
|
||||||
def test_sizeof_conditional_false(self):
|
def test_sizeof_conditional_false(self):
|
||||||
"""
|
"""
|
||||||
@summary: test if sizeof of simple type is 0 when type is conditional false
|
@summary: test if sizeof of simple type is 0 when type is conditional false
|
||||||
"""
|
"""
|
||||||
v = rdpy.network.type.SimpleType("I", 4, False, 0, conditional = lambda:False)
|
v = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:False)
|
||||||
self.assertEqual(rdpy.network.type.sizeof(v), 0, "invalid sizeof")
|
self.assertEqual(rdpy.core.type.sizeof(v), 0, "invalid sizeof")
|
||||||
|
|
||||||
def test_sizeof_list(self):
|
def test_sizeof_list(self):
|
||||||
"""
|
"""
|
||||||
@summary: test call sizeof on list of type
|
@summary: test call sizeof on list of type
|
||||||
"""
|
"""
|
||||||
v = [rdpy.network.type.UInt8(), rdpy.network.type.UInt16Le(), rdpy.network.type.UInt32Le()]
|
v = [rdpy.core.type.UInt8(), rdpy.core.type.UInt16Le(), rdpy.core.type.UInt32Le()]
|
||||||
self.assertEqual(rdpy.network.type.sizeof(v), 7, "invalid sizeof")
|
self.assertEqual(rdpy.core.type.sizeof(v), 7, "invalid sizeof")
|
||||||
|
|
||||||
def test_sizeof_list_conditional(self):
|
def test_sizeof_list_conditional(self):
|
||||||
"""
|
"""
|
||||||
@summary: test call sizeof on list of type with one type hidden
|
@summary: test call sizeof on list of type with one type hidden
|
||||||
"""
|
"""
|
||||||
v = [rdpy.network.type.UInt8(), rdpy.network.type.UInt16Le(conditional = lambda:False), rdpy.network.type.UInt32Le()]
|
v = [rdpy.core.type.UInt8(), rdpy.core.type.UInt16Le(conditional = lambda:False), rdpy.core.type.UInt32Le()]
|
||||||
self.assertEqual(rdpy.network.type.sizeof(v), 5, "invalid sizeof")
|
self.assertEqual(rdpy.core.type.sizeof(v), 5, "invalid sizeof")
|
||||||
|
|
||||||
def test_sizeof_tuple(self):
|
def test_sizeof_tuple(self):
|
||||||
"""
|
"""
|
||||||
@summary: test call sizeof on tuple of type
|
@summary: test call sizeof on tuple of type
|
||||||
"""
|
"""
|
||||||
v = [rdpy.network.type.UInt8(), rdpy.network.type.UInt16Le(), rdpy.network.type.UInt32Le()]
|
v = [rdpy.core.type.UInt8(), rdpy.core.type.UInt16Le(), rdpy.core.type.UInt32Le()]
|
||||||
self.assertEqual(rdpy.network.type.sizeof(v), 7, "invalid sizeof")
|
self.assertEqual(rdpy.core.type.sizeof(v), 7, "invalid sizeof")
|
||||||
|
|
||||||
def test_sizeof_tuple_conditional(self):
|
def test_sizeof_tuple_conditional(self):
|
||||||
"""
|
"""
|
||||||
@summary: test call sizeof on tuple of type with one type hidden
|
@summary: test call sizeof on tuple of type with one type hidden
|
||||||
"""
|
"""
|
||||||
v = (rdpy.network.type.UInt8(), rdpy.network.type.UInt16Le(), rdpy.network.type.UInt32Le(conditional = lambda:False))
|
v = (rdpy.core.type.UInt8(), rdpy.core.type.UInt16Le(), rdpy.core.type.UInt32Le(conditional = lambda:False))
|
||||||
self.assertEqual(rdpy.network.type.sizeof(v), 3, "invalid sizeof")
|
self.assertEqual(rdpy.core.type.sizeof(v), 3, "invalid sizeof")
|
||||||
|
|
||||||
def test_stream_write_uint8_type(self):
|
def test_stream_write_uint8_type(self):
|
||||||
"""
|
"""
|
||||||
@summary: test write uint8 in stream
|
@summary: test write uint8 in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.network.type.UInt8(1))
|
s.writeType(rdpy.core.type.UInt8(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x01', "invalid stream write")
|
self.assertEqual(''.join(s.buflist), '\x01', "invalid stream write")
|
||||||
|
|
||||||
def test_stream_write_uint16Le_type(self):
|
def test_stream_write_uint16Le_type(self):
|
||||||
"""
|
"""
|
||||||
@summary: test write UInt16Le in stream
|
@summary: test write UInt16Le in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.network.type.UInt16Le(1))
|
s.writeType(rdpy.core.type.UInt16Le(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x01\x00', "invalid stream write")
|
self.assertEqual(''.join(s.buflist), '\x01\x00', "invalid stream write")
|
||||||
|
|
||||||
def test_stream_write_uint16Be_type(self):
|
def test_stream_write_uint16Be_type(self):
|
||||||
"""
|
"""
|
||||||
@summary: test write UInt16Be in stream
|
@summary: test write UInt16Be in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.network.type.UInt16Be(1))
|
s.writeType(rdpy.core.type.UInt16Be(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x00\x01', "invalid stream write")
|
self.assertEqual(''.join(s.buflist), '\x00\x01', "invalid stream write")
|
||||||
|
|
||||||
def test_stream_write_uint24Le_type(self):
|
def test_stream_write_uint24Le_type(self):
|
||||||
"""
|
"""
|
||||||
@summary: test write UInt24Le in stream
|
@summary: test write UInt24Le in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.network.type.UInt24Le(1))
|
s.writeType(rdpy.core.type.UInt24Le(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x01\x00\x00', "invalid stream write")
|
self.assertEqual(''.join(s.buflist), '\x01\x00\x00', "invalid stream write")
|
||||||
|
|
||||||
def test_stream_write_uint24Be_type(self):
|
def test_stream_write_uint24Be_type(self):
|
||||||
"""
|
"""
|
||||||
@summary: test write uint24Be in stream
|
@summary: test write uint24Be in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.network.type.UInt24Be(1))
|
s.writeType(rdpy.core.type.UInt24Be(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x00\x00\x01', "invalid stream write")
|
self.assertEqual(''.join(s.buflist), '\x00\x00\x01', "invalid stream write")
|
||||||
|
|
||||||
def test_stream_write_uint32Le_type(self):
|
def test_stream_write_uint32Le_type(self):
|
||||||
"""
|
"""
|
||||||
@summary: test write UInt32Le in stream
|
@summary: test write UInt32Le in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.network.type.UInt32Le(1))
|
s.writeType(rdpy.core.type.UInt32Le(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x01\x00\x00\x00', "invalid stream write")
|
self.assertEqual(''.join(s.buflist), '\x01\x00\x00\x00', "invalid stream write")
|
||||||
|
|
||||||
def test_stream_write_uint32Be_type(self):
|
def test_stream_write_uint32Be_type(self):
|
||||||
"""
|
"""
|
||||||
@summary: test write UInt32Be in stream
|
@summary: test write UInt32Be in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.network.type.UInt32Be(1))
|
s.writeType(rdpy.core.type.UInt32Be(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x00\x00\x00\x01', "invalid stream write")
|
self.assertEqual(''.join(s.buflist), '\x00\x00\x00\x01', "invalid stream write")
|
||||||
|
|
||||||
def test_stream_read_uint8_type(self):
|
def test_stream_read_uint8_type(self):
|
||||||
"""
|
"""
|
||||||
@summary: test read UInt8 type from stream
|
@summary: test read UInt8 type from stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream('\x01')
|
s = rdpy.core.type.Stream('\x01')
|
||||||
t = rdpy.network.type.UInt8()
|
t = rdpy.core.type.UInt8()
|
||||||
s.readType(t)
|
s.readType(t)
|
||||||
self.assertEqual(t.value, 1, "invalid stream read value")
|
self.assertEqual(t.value, 1, "invalid stream read value")
|
||||||
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
||||||
@@ -203,8 +203,8 @@ class TypeCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
@summary: test read UInt16Le type from stream
|
@summary: test read UInt16Le type from stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream('\x01\x00')
|
s = rdpy.core.type.Stream('\x01\x00')
|
||||||
t = rdpy.network.type.UInt16Le()
|
t = rdpy.core.type.UInt16Le()
|
||||||
s.readType(t)
|
s.readType(t)
|
||||||
self.assertEqual(t.value, 1, "invalid stream read value")
|
self.assertEqual(t.value, 1, "invalid stream read value")
|
||||||
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
||||||
@@ -213,8 +213,8 @@ class TypeCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
@summary: test read UInt16Be type from stream
|
@summary: test read UInt16Be type from stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream('\x00\x01')
|
s = rdpy.core.type.Stream('\x00\x01')
|
||||||
t = rdpy.network.type.UInt16Be()
|
t = rdpy.core.type.UInt16Be()
|
||||||
s.readType(t)
|
s.readType(t)
|
||||||
self.assertEqual(t.value, 1, "invalid stream read value")
|
self.assertEqual(t.value, 1, "invalid stream read value")
|
||||||
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
||||||
@@ -223,8 +223,8 @@ class TypeCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
@summary: test read UInt24Le type from stream
|
@summary: test read UInt24Le type from stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream('\x01\x00\x00')
|
s = rdpy.core.type.Stream('\x01\x00\x00')
|
||||||
t = rdpy.network.type.UInt24Le()
|
t = rdpy.core.type.UInt24Le()
|
||||||
s.readType(t)
|
s.readType(t)
|
||||||
self.assertEqual(t.value, 1, "invalid stream read value")
|
self.assertEqual(t.value, 1, "invalid stream read value")
|
||||||
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
||||||
@@ -233,8 +233,8 @@ class TypeCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
@summary: test read UInt24Be type from stream
|
@summary: test read UInt24Be type from stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream('\x00\x00\x01')
|
s = rdpy.core.type.Stream('\x00\x00\x01')
|
||||||
t = rdpy.network.type.UInt24Be()
|
t = rdpy.core.type.UInt24Be()
|
||||||
s.readType(t)
|
s.readType(t)
|
||||||
self.assertEqual(t.value, 1, "invalid stream read")
|
self.assertEqual(t.value, 1, "invalid stream read")
|
||||||
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
||||||
@@ -243,8 +243,8 @@ class TypeCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
@summary: test read UInt32Le type from stream
|
@summary: test read UInt32Le type from stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream('\x01\x00\x00\x00')
|
s = rdpy.core.type.Stream('\x01\x00\x00\x00')
|
||||||
t = rdpy.network.type.UInt32Le()
|
t = rdpy.core.type.UInt32Le()
|
||||||
s.readType(t)
|
s.readType(t)
|
||||||
self.assertEqual(t.value, 1, "invalid stream read value")
|
self.assertEqual(t.value, 1, "invalid stream read value")
|
||||||
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
||||||
@@ -253,8 +253,8 @@ class TypeCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
@summary: test read UInt32Be type from stream
|
@summary: test read UInt32Be type from stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.network.type.Stream('\x00\x00\x00\x01')
|
s = rdpy.core.type.Stream('\x00\x00\x00\x01')
|
||||||
t = rdpy.network.type.UInt32Be()
|
t = rdpy.core.type.UInt32Be()
|
||||||
s.readType(t)
|
s.readType(t)
|
||||||
self.assertEqual(t.value, 1, "invalid stream read")
|
self.assertEqual(t.value, 1, "invalid stream read")
|
||||||
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
self.assertEqual(s.dataLen(), 0, "not read all stream")
|
||||||
@@ -264,9 +264,9 @@ class TypeCase(unittest.TestCase):
|
|||||||
@summary: test optional option in case of simple type reading
|
@summary: test optional option in case of simple type reading
|
||||||
"""
|
"""
|
||||||
#unsigned int case
|
#unsigned int case
|
||||||
t = rdpy.network.type.SimpleType("I", 4, False, 0, optional = True)
|
t = rdpy.core.type.SimpleType("I", 4, False, 0, optional = True)
|
||||||
#empty stream
|
#empty stream
|
||||||
s1 = rdpy.network.type.Stream()
|
s1 = rdpy.core.type.Stream()
|
||||||
s1.readType(t)
|
s1.readType(t)
|
||||||
self.assertEqual(t.value, 0, "invalid stream read optional value")
|
self.assertEqual(t.value, 0, "invalid stream read optional value")
|
||||||
|
|
||||||
@@ -275,8 +275,8 @@ class TypeCase(unittest.TestCase):
|
|||||||
@summary: test conditional option in case of simple type reading and when condition is false (not read)
|
@summary: test conditional option in case of simple type reading and when condition is false (not read)
|
||||||
"""
|
"""
|
||||||
#unsigned int case
|
#unsigned int case
|
||||||
t = rdpy.network.type.SimpleType("I", 4, False, 0, conditional = lambda:False)
|
t = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:False)
|
||||||
s1 = rdpy.network.type.Stream("\x01\x00\x00\x00")
|
s1 = rdpy.core.type.Stream("\x01\x00\x00\x00")
|
||||||
s1.readType(t)
|
s1.readType(t)
|
||||||
self.assertEqual(t.value, 0, "invalid stream read conditional value")
|
self.assertEqual(t.value, 0, "invalid stream read conditional value")
|
||||||
|
|
||||||
@@ -285,8 +285,8 @@ class TypeCase(unittest.TestCase):
|
|||||||
@summary: test conditional option in case of simple type reading and when condition is true (must be read)
|
@summary: test conditional option in case of simple type reading and when condition is true (must be read)
|
||||||
"""
|
"""
|
||||||
#unsigned int case
|
#unsigned int case
|
||||||
t = rdpy.network.type.SimpleType("I", 4, False, 0, conditional = lambda:True)
|
t = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:True)
|
||||||
s1 = rdpy.network.type.Stream("\x01\x00\x00\x00")
|
s1 = rdpy.core.type.Stream("\x01\x00\x00\x00")
|
||||||
s1.readType(t)
|
s1.readType(t)
|
||||||
self.assertEqual(t.value, 1, "invalid stream read conditional value")
|
self.assertEqual(t.value, 1, "invalid stream read conditional value")
|
||||||
|
|
||||||
@@ -294,13 +294,13 @@ class TypeCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
@summary: test if constant constraint fail, the reading stream is correctly rollback
|
@summary: test if constant constraint fail, the reading stream is correctly rollback
|
||||||
"""
|
"""
|
||||||
class TestComposite(rdpy.network.type.CompositeType):
|
class TestComposite(rdpy.core.type.CompositeType):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
rdpy.network.type.CompositeType.__init__(self)
|
rdpy.core.type.CompositeType.__init__(self)
|
||||||
self.padding = rdpy.network.type.UInt32Le(0)
|
self.padding = rdpy.core.type.UInt32Le(0)
|
||||||
self.constraint = rdpy.network.type.UInt32Le(1, constant = True)
|
self.constraint = rdpy.core.type.UInt32Le(1, constant = True)
|
||||||
|
|
||||||
s = rdpy.network.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00")
|
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||||
try:
|
try:
|
||||||
s.readType(TestComposite())
|
s.readType(TestComposite())
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -313,19 +313,19 @@ class TypeCase(unittest.TestCase):
|
|||||||
@summary: test if constant constraint fail even in recurcive composite type,
|
@summary: test if constant constraint fail even in recurcive composite type,
|
||||||
the reading stream is correctly rollback
|
the reading stream is correctly rollback
|
||||||
"""
|
"""
|
||||||
class TestSubComposite(rdpy.network.type.CompositeType):
|
class TestSubComposite(rdpy.core.type.CompositeType):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
rdpy.network.type.CompositeType.__init__(self)
|
rdpy.core.type.CompositeType.__init__(self)
|
||||||
self.padding = rdpy.network.type.UInt32Le(0)
|
self.padding = rdpy.core.type.UInt32Le(0)
|
||||||
self.constraint = rdpy.network.type.UInt32Le(1, constant = True)
|
self.constraint = rdpy.core.type.UInt32Le(1, constant = True)
|
||||||
|
|
||||||
class TestComposite(rdpy.network.type.CompositeType):
|
class TestComposite(rdpy.core.type.CompositeType):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
rdpy.network.type.CompositeType.__init__(self)
|
rdpy.core.type.CompositeType.__init__(self)
|
||||||
self.padding = rdpy.network.type.UInt32Le(0)
|
self.padding = rdpy.core.type.UInt32Le(0)
|
||||||
self.recurcive = TestSubComposite()
|
self.recurcive = TestSubComposite()
|
||||||
|
|
||||||
s = rdpy.network.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||||
try:
|
try:
|
||||||
s.readType(TestComposite())
|
s.readType(TestComposite())
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -338,19 +338,19 @@ class TypeCase(unittest.TestCase):
|
|||||||
@summary: test if constant constraint fail even in recurcive composite type,
|
@summary: test if constant constraint fail even in recurcive composite type,
|
||||||
the reading stream is correctly rollback
|
the reading stream is correctly rollback
|
||||||
"""
|
"""
|
||||||
class TestSubComposite(rdpy.network.type.CompositeType):
|
class TestSubComposite(rdpy.core.type.CompositeType):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
rdpy.network.type.CompositeType.__init__(self)
|
rdpy.core.type.CompositeType.__init__(self)
|
||||||
self.padding = rdpy.network.type.UInt32Le(0)
|
self.padding = rdpy.core.type.UInt32Le(0)
|
||||||
self.constraint = rdpy.network.type.UInt32Le(1)
|
self.constraint = rdpy.core.type.UInt32Le(1)
|
||||||
|
|
||||||
class TestComposite(rdpy.network.type.CompositeType):
|
class TestComposite(rdpy.core.type.CompositeType):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
rdpy.network.type.CompositeType.__init__(self)
|
rdpy.core.type.CompositeType.__init__(self)
|
||||||
self.padding = rdpy.network.type.UInt32Le(0)
|
self.padding = rdpy.core.type.UInt32Le(0)
|
||||||
self.recurcive = TestSubComposite()
|
self.recurcive = TestSubComposite()
|
||||||
|
|
||||||
s = rdpy.network.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||||
try:
|
try:
|
||||||
s.readType(TestComposite())
|
s.readType(TestComposite())
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -364,12 +364,12 @@ class TypeCase(unittest.TestCase):
|
|||||||
if total stream read length < to forced read length
|
if total stream read length < to forced read length
|
||||||
the trash must be read as padding
|
the trash must be read as padding
|
||||||
"""
|
"""
|
||||||
class TestReadLength(rdpy.network.type.CompositeType):
|
class TestReadLength(rdpy.core.type.CompositeType):
|
||||||
def __init__(self, readLen):
|
def __init__(self, readLen):
|
||||||
rdpy.network.type.CompositeType.__init__(self, readLen = readLen)
|
rdpy.core.type.CompositeType.__init__(self, readLen = readLen)
|
||||||
self.padding = rdpy.network.type.UInt32Le(0)
|
self.padding = rdpy.core.type.UInt32Le(0)
|
||||||
s = rdpy.network.type.Stream("\x00" * 10)
|
s = rdpy.core.type.Stream("\x00" * 10)
|
||||||
s.readType(TestReadLength(rdpy.network.type.UInt8(10)))
|
s.readType(TestReadLength(rdpy.core.type.UInt8(10)))
|
||||||
self.assertEqual(s.dataLen(), 0, "invalid stream read trash data as padding")
|
self.assertEqual(s.dataLen(), 0, "invalid stream read trash data as padding")
|
||||||
|
|
||||||
def test_stream_read_with_static_length_inferior(self):
|
def test_stream_read_with_static_length_inferior(self):
|
||||||
@@ -378,12 +378,12 @@ class TypeCase(unittest.TestCase):
|
|||||||
if total stream read length > to forced read length
|
if total stream read length > to forced read length
|
||||||
an InvalidSize exception is throw
|
an InvalidSize exception is throw
|
||||||
"""
|
"""
|
||||||
class TestReadLength(rdpy.network.type.CompositeType):
|
class TestReadLength(rdpy.core.type.CompositeType):
|
||||||
def __init__(self, readLen):
|
def __init__(self, readLen):
|
||||||
rdpy.network.type.CompositeType.__init__(self, readLen = readLen)
|
rdpy.core.type.CompositeType.__init__(self, readLen = readLen)
|
||||||
self.padding = rdpy.network.type.UInt32Le(0)
|
self.padding = rdpy.core.type.UInt32Le(0)
|
||||||
s = rdpy.network.type.Stream("\x00" * 10)
|
s = rdpy.core.type.Stream("\x00" * 10)
|
||||||
self.assertRaises(InvalidSize, s.readType, TestReadLength(rdpy.network.type.UInt8(2)))
|
self.assertRaises(InvalidSize, s.readType, TestReadLength(rdpy.core.type.UInt8(2)))
|
||||||
|
|
||||||
def test_stream_read_string(self):
|
def test_stream_read_string(self):
|
||||||
"""
|
"""
|
||||||
@@ -26,11 +26,11 @@ import os, sys
|
|||||||
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import rdpy.protocol.rdp.ber as ber
|
import rdpy.protocol.rdp.t125.ber as ber
|
||||||
import rdpy.network.type as type
|
import rdpy.core.type as type
|
||||||
import rdpy.base.error as error
|
import rdpy.core.error as error
|
||||||
|
|
||||||
class BERCase(unittest.TestCase):
|
class BERTest(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
@summary: test case for ber layer (RDP)
|
@summary: test case for ber layer (RDP)
|
||||||
"""
|
"""
|
||||||
|
|||||||
128
test/test_protocol_rdp_cssp_ntlm.py
Normal file
128
test/test_protocol_rdp_cssp_ntlm.py
Normal 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.protocol.rdp.nla import cssp, ntlm
|
||||||
|
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.decodeDERTRequest(peer0_0.decode('base64'))
|
||||||
|
challenge_data_request = cssp.decodeDERTRequest(peer1_0.decode('base64'))
|
||||||
|
authenticate_data_request = cssp.decodeDERTRequest(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.readType(negotiate)
|
||||||
|
|
||||||
|
challenge = ntlm.ChallengeMessage()
|
||||||
|
challenge_data.readType(challenge)
|
||||||
|
|
||||||
|
ServerChallenge = challenge.ServerChallenge.value
|
||||||
|
ServerName = challenge.getTargetInfo()
|
||||||
|
|
||||||
|
authenticate = ntlm.AuthenticateMessage()
|
||||||
|
authenticate_data.readType(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")
|
||||||
|
|
||||||
129
test/test_protocol_rdp_lic.py
Normal file
129
test/test_protocol_rdp_lic.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
unit test for rdpy.protocol.rdp.lic automata
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os, sys
|
||||||
|
# Change path so we find rdpy
|
||||||
|
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from rdpy.protocol.rdp import lic, sec
|
||||||
|
import rdpy.core.type as type
|
||||||
|
|
||||||
|
#dump of server request
|
||||||
|
SERVERREQUEST = """
|
||||||
|
AQNfCBkr6c1CVLRPx7PPYVgzW5uMQ1pSvtzs9XlTt74jwjslAAAGACwAAABNAGkAYwByAG8AcwBv
|
||||||
|
AGYAdAAgAEMAbwByAHAAbwByAGEAdABpAG8AbgAAAAgAAABBADAAMgAAAA0ABAABAAAAAwDZBwIA
|
||||||
|
AAACAAAAWAMAADCCA1QwggJAoAMCAQICCAGemF/kFo3QMAkGBSsOAwIdBQAwODE2MBUGA1UEBx4O
|
||||||
|
AFMASQBSAEEARABFAEwwHQYDVQQDHhYAVgBTAEkALQBTAFIAVgAtADAAMAA0MB4XDTcwMTAyMTA4
|
||||||
|
MDMxN1oXDTQ5MTAyMTA4MDMxN1owODE2MBUGA1UEBx4OAFMASQBSAEEARABFAEwwHQYDVQQDHhYA
|
||||||
|
VgBTAEkALQBTAFIAVgAtADAAMAA0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0mh+
|
||||||
|
rgsNk7WCR8Ck46jTV6OgnMgsFmdt0RqBJqtFwk+uqH8cBKFohtD9CeHTeQYkk+8m1C5GDpjQ/5BK
|
||||||
|
jaBfZHW+g5lZQITKbTi2n8TZfDg76wHvfl71JzwB84AG736KOZSOkBg8mJk3xf8LX5hbYHPQCRc5
|
||||||
|
y+06CWOyPiBYCEElXPOqsZYwum72N/NIsrKe98QHLn+X6MSbZ9Qzw6jnoBFgjk65bzpcChaOhnlW
|
||||||
|
875D6+8KZXgFmHT6MSJX7eabKw1zbKXszYpPvPfMDybp+6z125AJ+GxJ847AQ10xOV2X13ZVfSvS
|
||||||
|
Np62ogDD23duWHvcwLmHJ+45Jdgt7y4C0wIDAQABo2owaDAPBgNVHRMECDAGAQH/AgEAMFUGCSsG
|
||||||
|
AQQBgjcSCARIQwBDAFIATQBWAEoANgBWAEMAVABUAFcAUgBXAEcATQA4AEgASwBIAFkAMwBKAEsA
|
||||||
|
OABXADMAVgBNAEIARABXAFQAUQBGAAAAMAkGBSsOAwIdBQADggEBAD2NQPpBREuD+CZI9T4bUF9s
|
||||||
|
2yyQ8HidxJ+7NMcDyW4Ozid/SrAT9JP42RQrBosgq8S8teWNubrsNMsJjGaeueeQisLP1OigXQH1
|
||||||
|
8DcnLUc5TZKIsKjBqxJ40tssR5KaOfjGHXX5VoADsthnp3wO4bRuaXyhpLQzN1bUNKQk0Fok6Fzt
|
||||||
|
zo/rNEiAQv+M/Y1vmCoTuulXwxymZ+UdbxHPr/IvmxD0Gcmz8Qm45Lpypt2t0lGwNiuV2/3dm13C
|
||||||
|
spjIGxJFf/26QiRuZi5cps2YsOaQfW3xByklE5EbZWYIlsTCQpht+owKqnoPy7aD2On8z0IyGgY3
|
||||||
|
YdsOzHDvRZ+S0hxhBAAAMIIEXTCCA0mgAwIBAgIFAQAAABgwCQYFKw4DAh0FADA4MTYwFQYDVQQH
|
||||||
|
Hg4AUwBJAFIAQQBEAEUATDAdBgNVBAMeFgBWAFMASQAtAFMAUgBWAC0AMAAwADQwHhcNMTMxMTI3
|
||||||
|
MTQyNDIyWhcNMzgwMTE5MDMxNDA3WjCBqDGBpTAnBgNVBAMeIABuAGMAYQBjAG4AXwBuAHAAOgAx
|
||||||
|
ADkAMgAuADEANgA4MDUGA1UEBx4uAG4AYwBhAGMAbgBfAG4AcAA6ADEAOQAyAC4AMQA2ADgALgAx
|
||||||
|
ADMANQAuADMAMDBDBgNVBAUePAAxAEIAYwBLAGUAYQBiADcAWABPAGoAbgBXAEUATQBuAHgAMQBw
|
||||||
|
AFMAaQA2AEoAUgBBAHUAMAA9AA0ACjBYMAkGBSsOAwIPBQADSwAwSAJBANGDkkicUIVwIX2apm1b
|
||||||
|
U9WXVMnVT+S081PVP87vGp6VtzYpT9cMNKv70Qi2U3MQoQ4MuKS1XN2uc9SrNC7RWoUCAwEAAaOC
|
||||||
|
Ac8wggHLMBQGCSsGAQQBgjcSBAEB/wQEAQAFADA8BgkrBgEEAYI3EgIBAf8ELE0AaQBjAHIAbwBz
|
||||||
|
AG8AZgB0ACAAQwBvAHIAcABvAHIAYQB0AGkAbwBuAAAAMIHNBgkrBgEEAYI3EgUBAf8EgbwAMAAA
|
||||||
|
AQAAAAIAAAAJBAAAHABKAGYASgCwAAEAMwBkADIANgA3ADkANQA0AC0AZQBlAGIANwAtADEAMQBk
|
||||||
|
ADEALQBiADkANABlAC0AMAAwAGMAMAA0AGYAYQAzADAAOAAwAGQAAAAzAGQAMgA2ADcAOQA1ADQA
|
||||||
|
LQBlAGUAYgA3AC0AMQAxAGQAMQAtAGIAOQA0AGUALQAwADAAYwAwADQAZgBhADMAMAA4ADAAZAAA
|
||||||
|
AAAAABAAgOQAAAAAADB0BgkrBgEEAYI3EgYBAf8EZAAwAAAAABgASABWAFMASQAtAFMAUgBWAC0A
|
||||||
|
MAAwADQAAAA1ADUAMAA0ADEALQAwADAAOAAtADIAOQAxADMAMwA5ADEALQA4ADQANgAwADkAAABT
|
||||||
|
AEkAUgBBAEQARQBMAAAAAAAwLwYDVR0jAQH/BCUwI6EapBhWAFMASQAtAFMAUgBWAC0AMAAwADQA
|
||||||
|
AACCBQEAAAAYMAkGBSsOAwIdBQADggEBAGFI8teU2iypPG2BbpNLa+zZeb87MNjtzHgQlC0RfoK0
|
||||||
|
dY91t3OXA5pdyD5IRRkvGUVKBf/5G/OVZZvKTnyh3daopD6InPy1X6Wlx5QveCL8ydd/H+ezm0zl
|
||||||
|
KlMg0pImG0V2NX50q4b9R4I5xiaA7mUeww6rz6iAh1iB2CYHOc/uGAS1/PPtHqnfss5bY+wZR+tW
|
||||||
|
dvyMFvn6DbvzAN9wT2KIcv1LtMGgxv28v+dnqGhcxrLRE4nRjMIV/AKpBJXhgH7DaUEc1NJNDPUt
|
||||||
|
5Pdz4qy8WM2d8JO0yNXQVqykJahgt5TJxCoP46SPFUU5XBbNTZuKv7CtPkSxgtrdrzwFTtcAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAQAAAA4ADgBtaWNyb3NvZnQuY29tAA==
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TestLic(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
@summary: Test case for MCS automata
|
||||||
|
"""
|
||||||
|
|
||||||
|
class LIC_PASS(Exception):
|
||||||
|
"""
|
||||||
|
@summary: for OK tests
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class LIC_FAIL(Exception):
|
||||||
|
"""
|
||||||
|
@summary: for KO tests
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_valid_client_licensing_error_message(self):
|
||||||
|
l = lic.LicenseManager(None)
|
||||||
|
s = type.Stream()
|
||||||
|
s.writeType(lic.createValidClientLicensingErrorMessage())
|
||||||
|
#reinit position
|
||||||
|
s.pos = 0
|
||||||
|
|
||||||
|
self.assertTrue(l.recv(s), "Manager can retrieve valid case")
|
||||||
|
|
||||||
|
def test_new_license(self):
|
||||||
|
class Transport(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._state = False
|
||||||
|
def sendFlagged(self, flag, message):
|
||||||
|
if flag != sec.SecurityFlag.SEC_LICENSE_PKT:
|
||||||
|
return
|
||||||
|
s = type.Stream()
|
||||||
|
s.writeType(message)
|
||||||
|
s.pos = 0
|
||||||
|
s.readType(lic.LicPacket(lic.ClientNewLicenseRequest()))
|
||||||
|
self._state = True
|
||||||
|
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)
|
||||||
|
|
||||||
|
s = type.Stream(SERVERREQUEST.decode("base64"))
|
||||||
|
|
||||||
|
self.assertFalse(l.recv(s) and t._state, "Bad message after license request")
|
||||||
36
test/test_protocol_rdp_mcs.py
Normal file
36
test/test_protocol_rdp_mcs.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
unit test for rdpy.protocol.rdp.mcs automata
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os, sys
|
||||||
|
# Change path so we find rdpy
|
||||||
|
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class MCSTest(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
@summary: test case for per layer (RDP)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_per_readLength(self):
|
||||||
|
pass
|
||||||
@@ -26,11 +26,11 @@ import os, sys
|
|||||||
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import rdpy.protocol.rdp.per as per
|
import rdpy.protocol.rdp.t125.per as per
|
||||||
import rdpy.network.type as type
|
import rdpy.core.type as type
|
||||||
import rdpy.base.error as error
|
import rdpy.core.error as error
|
||||||
|
|
||||||
class PERCase(unittest.TestCase):
|
class PERTest(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
@summary: test case for per layer (RDP)
|
@summary: test case for per layer (RDP)
|
||||||
"""
|
"""
|
||||||
|
|||||||
48
test/test_protocol_rdp_rc4.py
Normal file
48
test/test_protocol_rdp_rc4.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
unit test for rdpy.protocol.rdp.rc4 module
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os, sys
|
||||||
|
# Change path so we find rdpy
|
||||||
|
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import rdpy.security.rc4 as rc4
|
||||||
|
|
||||||
|
|
||||||
|
class RC4Test(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
@summary: unit tests for rc4
|
||||||
|
@see: http://fr.wikipedia.org/wiki/RC4
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_rc4_key_plaintext(self):
|
||||||
|
self.assertEqual("\xBB\xF3\x16\xE8\xD9\x40\xAF\x0A\xD3", rc4.crypt(rc4.RC4Key("Key"), "Plaintext"), "RC4 bad crypt")
|
||||||
|
self.assertEqual("Plaintext", rc4.crypt(rc4.RC4Key("Key"), "\xBB\xF3\x16\xE8\xD9\x40\xAF\x0A\xD3"), "RC4 bad crypt")
|
||||||
|
|
||||||
|
def test_rc4_wiki_pedia(self):
|
||||||
|
self.assertEqual("\x10\x21\xBF\x04\x20", rc4.crypt(rc4.RC4Key("Wiki"), "pedia"), "RC4 bad crypt")
|
||||||
|
self.assertEqual("pedia", rc4.crypt(rc4.RC4Key("Wiki"), "\x10\x21\xBF\x04\x20"), "RC4 bad crypt")
|
||||||
|
|
||||||
|
def test_rc4_secret_attack_at_down(self):
|
||||||
|
self.assertEqual("\x45\xA0\x1F\x64\x5F\xC3\x5B\x38\x35\x52\x54\x4B\x9B\xF5", rc4.crypt(rc4.RC4Key("Secret"), "Attack at dawn"), "RC4 bad crypt")
|
||||||
|
self.assertEqual("Attack at dawn", rc4.crypt(rc4.RC4Key("Secret"), "\x45\xA0\x1F\x64\x5F\xC3\x5B\x38\x35\x52\x54\x4B\x9B\xF5"), "RC4 bad crypt")
|
||||||
@@ -27,10 +27,10 @@ sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import rdpy.protocol.rdp.tpkt as tpkt
|
import rdpy.protocol.rdp.tpkt as tpkt
|
||||||
import rdpy.network.type as type
|
import rdpy.core.type as type
|
||||||
import rdpy.base.error as error
|
import rdpy.core.error as error
|
||||||
|
|
||||||
class TPKTCase(unittest.TestCase):
|
class TPKTTest(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
@summary: test case for tpkt layer (RDP)
|
@summary: test case for tpkt layer (RDP)
|
||||||
"""
|
"""
|
||||||
@@ -47,10 +47,10 @@ class TPKTCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
class Presentation(object):
|
class Presentation(object):
|
||||||
def connect(self):
|
def connect(self):
|
||||||
raise TPKTCase.TPKT_PASS()
|
raise TPKTTest.TPKT_PASS()
|
||||||
|
|
||||||
layer = tpkt.TPKT(Presentation(), None)
|
layer = tpkt.TPKT(Presentation())
|
||||||
self.assertRaises(TPKTCase.TPKT_PASS, layer.connect)
|
self.assertRaises(TPKTTest.TPKT_PASS, layer.connect)
|
||||||
|
|
||||||
def test_tpkt_layer_recv(self):
|
def test_tpkt_layer_recv(self):
|
||||||
"""
|
"""
|
||||||
@@ -61,16 +61,16 @@ class TPKTCase(unittest.TestCase):
|
|||||||
pass
|
pass
|
||||||
def recv(self, data):
|
def recv(self, data):
|
||||||
data.readType(type.String("test_tpkt_layer_recv", constant = True))
|
data.readType(type.String("test_tpkt_layer_recv", constant = True))
|
||||||
raise TPKTCase.TPKT_PASS()
|
raise TPKTTest.TPKT_PASS()
|
||||||
|
|
||||||
message = type.String("test_tpkt_layer_recv")
|
message = type.String("test_tpkt_layer_recv")
|
||||||
|
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType((type.UInt8(tpkt.Action.FASTPATH_ACTION_X224), type.UInt8(), type.UInt16Be(type.sizeof(message) + 4), message))
|
s.writeType((type.UInt8(tpkt.Action.FASTPATH_ACTION_X224), type.UInt8(), type.UInt16Be(type.sizeof(message) + 4), message))
|
||||||
|
|
||||||
layer = tpkt.TPKT(Presentation(), None)
|
layer = tpkt.TPKT(Presentation())
|
||||||
layer.connect()
|
layer.connect()
|
||||||
self.assertRaises(TPKTCase.TPKT_PASS, layer.dataReceived, s.getvalue())
|
self.assertRaises(TPKTTest.TPKT_PASS, layer.dataReceived, s.getvalue())
|
||||||
|
|
||||||
def test_tpkt_layer_recv_fastpath(self):
|
def test_tpkt_layer_recv_fastpath(self):
|
||||||
"""
|
"""
|
||||||
@@ -79,18 +79,19 @@ class TPKTCase(unittest.TestCase):
|
|||||||
class FastPathLayer(tpkt.IFastPathListener):
|
class FastPathLayer(tpkt.IFastPathListener):
|
||||||
def setFastPathSender(self, fastPathSender):
|
def setFastPathSender(self, fastPathSender):
|
||||||
pass
|
pass
|
||||||
def recvFastPath(self, fastPathS):
|
def recvFastPath(self, secFlag, fastPathS):
|
||||||
fastPathS.readType(type.String("test_tpkt_layer_recv_fastpath", constant = True))
|
fastPathS.readType(type.String("test_tpkt_layer_recv_fastpath", constant = True))
|
||||||
raise TPKTCase.TPKT_PASS()
|
raise TPKTTest.TPKT_PASS()
|
||||||
|
|
||||||
message = type.String("test_tpkt_layer_recv_fastpath")
|
message = type.String("test_tpkt_layer_recv_fastpath")
|
||||||
|
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType((type.UInt8(tpkt.Action.FASTPATH_ACTION_FASTPATH), type.UInt8(type.sizeof(message) + 2), message))
|
s.writeType((type.UInt8(tpkt.Action.FASTPATH_ACTION_FASTPATH), type.UInt8(type.sizeof(message) + 2), message))
|
||||||
|
|
||||||
layer = tpkt.TPKT(None, FastPathLayer())
|
layer = tpkt.TPKT(None)
|
||||||
|
layer.initFastPath(FastPathLayer())
|
||||||
layer.connect()
|
layer.connect()
|
||||||
self.assertRaises(TPKTCase.TPKT_PASS, layer.dataReceived, s.getvalue())
|
self.assertRaises(TPKTTest.TPKT_PASS, layer.dataReceived, s.getvalue())
|
||||||
|
|
||||||
def test_tpkt_layer_recv_fastpath_ext_length(self):
|
def test_tpkt_layer_recv_fastpath_ext_length(self):
|
||||||
"""
|
"""
|
||||||
@@ -99,15 +100,16 @@ class TPKTCase(unittest.TestCase):
|
|||||||
class FastPathLayer(tpkt.IFastPathListener):
|
class FastPathLayer(tpkt.IFastPathListener):
|
||||||
def setFastPathSender(self, fastPathSender):
|
def setFastPathSender(self, fastPathSender):
|
||||||
pass
|
pass
|
||||||
def recvFastPath(self, fastPathS):
|
def recvFastPath(self, secflag, fastPathS):
|
||||||
fastPathS.readType(type.String("test_tpkt_layer_recv_fastpath_ext_length", constant = True))
|
fastPathS.readType(type.String("test_tpkt_layer_recv_fastpath_ext_length", constant = True))
|
||||||
raise TPKTCase.TPKT_PASS()
|
raise TPKTTest.TPKT_PASS()
|
||||||
|
|
||||||
message = type.String("test_tpkt_layer_recv_fastpath_ext_length")
|
message = type.String("test_tpkt_layer_recv_fastpath_ext_length")
|
||||||
|
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType((type.UInt8(tpkt.Action.FASTPATH_ACTION_FASTPATH), type.UInt16Be((type.sizeof(message) + 3) | 0x8000), message))
|
s.writeType((type.UInt8(tpkt.Action.FASTPATH_ACTION_FASTPATH), type.UInt16Be((type.sizeof(message) + 3) | 0x8000), message))
|
||||||
|
|
||||||
layer = tpkt.TPKT(None, FastPathLayer())
|
layer = tpkt.TPKT(None)
|
||||||
|
layer.initFastPath(FastPathLayer())
|
||||||
layer.connect()
|
layer.connect()
|
||||||
self.assertRaises(TPKTCase.TPKT_PASS, layer.dataReceived, s.getvalue())
|
self.assertRaises(TPKTTest.TPKT_PASS, layer.dataReceived, s.getvalue())
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import rdpy.protocol.rdp.x224 as x224
|
import rdpy.protocol.rdp.x224 as x224
|
||||||
import rdpy.network.type as type
|
import rdpy.core.type as type
|
||||||
import rdpy.base.error as error
|
import rdpy.core.error as error
|
||||||
|
|
||||||
class X224Case(unittest.TestCase):
|
class X224Test(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
@summary: test case for x224 layer (RDP)
|
@summary: test case for x224 layer (RDP)
|
||||||
"""
|
"""
|
||||||
@@ -54,7 +54,7 @@ class X224Case(unittest.TestCase):
|
|||||||
class Presentation(object):
|
class Presentation(object):
|
||||||
def recv(self, data):
|
def recv(self, data):
|
||||||
data.readType(type.String('test_x224_layer_recvData', constant = True))
|
data.readType(type.String('test_x224_layer_recvData', constant = True))
|
||||||
raise X224Case.X224_PASS()
|
raise X224Test.X224_PASS()
|
||||||
|
|
||||||
layer = x224.X224Layer(Presentation())
|
layer = x224.X224Layer(Presentation())
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
@@ -62,7 +62,7 @@ class X224Case(unittest.TestCase):
|
|||||||
#reinit position
|
#reinit position
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
|
|
||||||
self.assertRaises(X224Case.X224_PASS, layer.recvData, s)
|
self.assertRaises(X224Test.X224_PASS, layer.recvData, s)
|
||||||
|
|
||||||
def test_x224_layer_send(self):
|
def test_x224_layer_send(self):
|
||||||
"""
|
"""
|
||||||
@@ -75,12 +75,12 @@ class X224Case(unittest.TestCase):
|
|||||||
s.pos = 0
|
s.pos = 0
|
||||||
s.readType(x224.X224DataHeader())
|
s.readType(x224.X224DataHeader())
|
||||||
s.readType(type.String('test_x224_layer_send', constant = True))
|
s.readType(type.String('test_x224_layer_send', constant = True))
|
||||||
raise X224Case.X224_PASS()
|
raise X224Test.X224_PASS()
|
||||||
|
|
||||||
layer = x224.X224Layer(None)
|
layer = x224.X224Layer(None)
|
||||||
layer._transport = Transport()
|
layer._transport = Transport()
|
||||||
|
|
||||||
self.assertRaises(X224Case.X224_PASS, layer.send, type.String('test_x224_layer_send'))
|
self.assertRaises(X224Test.X224_PASS, layer.send, type.String('test_x224_layer_send'))
|
||||||
|
|
||||||
def test_x224_client_connect(self):
|
def test_x224_client_connect(self):
|
||||||
"""
|
"""
|
||||||
@@ -95,37 +95,21 @@ class X224Case(unittest.TestCase):
|
|||||||
s.readType(t)
|
s.readType(t)
|
||||||
|
|
||||||
if t.protocolNeg.code != x224.NegociationType.TYPE_RDP_NEG_REQ:
|
if t.protocolNeg.code != x224.NegociationType.TYPE_RDP_NEG_REQ:
|
||||||
raise X224Case.X224_FAIL()
|
raise X224Test.X224_FAIL()
|
||||||
|
|
||||||
if t.protocolNeg.selectedProtocol.value != x224.Protocols.PROTOCOL_SSL:
|
|
||||||
raise X224Case.X224_FAIL()
|
|
||||||
|
|
||||||
def nextAutomata(data):
|
def nextAutomata(data):
|
||||||
raise X224Case.X224_PASS()
|
raise X224Test.X224_PASS()
|
||||||
|
|
||||||
layer = x224.Client(None)
|
layer = x224.Client(None)
|
||||||
layer._transport = Transport()
|
layer._transport = Transport()
|
||||||
layer.recvConnectionConfirm = nextAutomata
|
layer.recvConnectionConfirm = nextAutomata
|
||||||
layer.connect()
|
layer.connect()
|
||||||
|
|
||||||
self.assertRaises(X224Case.X224_PASS, layer.recv, type.String('\x01\x02'))
|
self.assertRaises(X224Test.X224_PASS, layer.recv, type.String('\x01\x02'))
|
||||||
|
|
||||||
def test_x224_client_recvConnectionConfirm_negotiation_old(self):
|
|
||||||
"""
|
|
||||||
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
|
|
||||||
whithout protocol negotiation (doesn't support)
|
|
||||||
"""
|
|
||||||
message = x224.ServerConnectionConfirm()
|
|
||||||
del message._typeName[message._typeName.index("protocolNeg")]
|
|
||||||
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):
|
def test_x224_client_recvConnectionConfirm_negotiation_failure(self):
|
||||||
"""
|
"""
|
||||||
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
|
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
|
||||||
check negotiation failure
|
check negotiation failure
|
||||||
"""
|
"""
|
||||||
message = x224.ServerConnectionConfirm()
|
message = x224.ServerConnectionConfirm()
|
||||||
@@ -134,20 +118,7 @@ class X224Case(unittest.TestCase):
|
|||||||
s.writeType(message)
|
s.writeType(message)
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
layer = x224.Client(None)
|
layer = x224.Client(None)
|
||||||
self.assertRaises(error.InvalidExpectedDataException, layer.recvConnectionConfirm, s)
|
self.assertRaises(error.RDPSecurityNegoFail, layer.recvConnectionConfirm, s)
|
||||||
|
|
||||||
def test_x224_client_recvConnectionConfirm_negotiation_bad_protocol(self):
|
|
||||||
"""
|
|
||||||
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
|
|
||||||
Server ask another protocol than SSL
|
|
||||||
"""
|
|
||||||
message = x224.ServerConnectionConfirm()
|
|
||||||
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_RDP
|
|
||||||
s = type.Stream()
|
|
||||||
s.writeType(message)
|
|
||||||
s.pos = 0
|
|
||||||
layer = x224.Client(None)
|
|
||||||
self.assertRaises(error.InvalidExpectedDataException, layer.recvConnectionConfirm, s)
|
|
||||||
|
|
||||||
def test_x224_client_recvConnectionConfirm_ok(self):
|
def test_x224_client_recvConnectionConfirm_ok(self):
|
||||||
"""
|
"""
|
||||||
@@ -157,12 +128,10 @@ class X224Case(unittest.TestCase):
|
|||||||
tls_begin = False
|
tls_begin = False
|
||||||
presentation_connect = False
|
presentation_connect = False
|
||||||
class Transport(object):
|
class Transport(object):
|
||||||
def __init__(self):
|
|
||||||
class TLSTransport(object):
|
def startTLS(self, context):
|
||||||
def startTLS(self, context):
|
global tls_begin
|
||||||
global tls_begin
|
tls_begin = True
|
||||||
tls_begin = True
|
|
||||||
self.transport = TLSTransport()
|
|
||||||
|
|
||||||
class Presentation(object):
|
class Presentation(object):
|
||||||
def connect(self):
|
def connect(self):
|
||||||
@@ -170,7 +139,7 @@ class X224Case(unittest.TestCase):
|
|||||||
presentation_connect = True
|
presentation_connect = True
|
||||||
|
|
||||||
def recvData(data):
|
def recvData(data):
|
||||||
raise X224Case.X224_PASS()
|
raise X224Test.X224_PASS()
|
||||||
|
|
||||||
message = x224.ServerConnectionConfirm()
|
message = x224.ServerConnectionConfirm()
|
||||||
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_SSL
|
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_SSL
|
||||||
@@ -186,38 +155,7 @@ class X224Case(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(tls_begin, "TLS is not started")
|
self.assertTrue(tls_begin, "TLS is not started")
|
||||||
self.assertTrue(presentation_connect, "connect event is not forwarded")
|
self.assertTrue(presentation_connect, "connect event is not forwarded")
|
||||||
self.assertRaises(X224Case.X224_PASS, layer.recv, type.String('\x01\x02'))
|
self.assertRaises(X224Test.X224_PASS, layer.recv, type.String('\x01\x02'))
|
||||||
|
|
||||||
def test_x224_server_recvConnectionRequest_invalid_old_client(self):
|
|
||||||
"""
|
|
||||||
@summary: unit test for X224Server.recvConnectionRequest function
|
|
||||||
old client with non protocol neg
|
|
||||||
"""
|
|
||||||
message = x224.ClientConnectionRequestPDU()
|
|
||||||
del message._typeName[message._typeName.index("protocolNeg")]
|
|
||||||
s = type.Stream()
|
|
||||||
s.writeType(message)
|
|
||||||
s.pos = 0
|
|
||||||
|
|
||||||
layer = x224.Server(None, "key", "cert")
|
|
||||||
layer.connect()
|
|
||||||
|
|
||||||
self.assertRaises(error.InvalidExpectedDataException, layer.recv, s)
|
|
||||||
|
|
||||||
def test_x224_server_recvConnectionRequest_invalid_protocol_neg_failure(self):
|
|
||||||
"""
|
|
||||||
@summary: unit test for X224Server.recvConnectionRequest function
|
|
||||||
"""
|
|
||||||
message = x224.ClientConnectionRequestPDU()
|
|
||||||
message.protocolNeg.code.value = x224.NegociationType.TYPE_RDP_NEG_FAILURE
|
|
||||||
s = type.Stream()
|
|
||||||
s.writeType(message)
|
|
||||||
s.pos = 0
|
|
||||||
|
|
||||||
layer = x224.Server(None, "key", "cert")
|
|
||||||
layer.connect()
|
|
||||||
|
|
||||||
self.assertRaises(error.InvalidExpectedDataException, layer.recv, s)
|
|
||||||
|
|
||||||
def test_x224_server_recvConnectionRequest_client_accept_ssl(self):
|
def test_x224_server_recvConnectionRequest_client_accept_ssl(self):
|
||||||
"""
|
"""
|
||||||
@@ -228,9 +166,11 @@ class X224Case(unittest.TestCase):
|
|||||||
class Transport(object):
|
class Transport(object):
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
if not isinstance(data, x224.ServerConnectionConfirm):
|
if not isinstance(data, x224.ServerConnectionConfirm):
|
||||||
raise X224Case.X224_FAIL()
|
raise X224Test.X224_FAIL()
|
||||||
if data.protocolNeg.code.value != x224.NegociationType.TYPE_RDP_NEG_FAILURE or data.protocolNeg.failureCode.value != x224.NegotiationFailureCode.SSL_REQUIRED_BY_SERVER:
|
if data.protocolNeg.code.value != x224.NegociationType.TYPE_RDP_NEG_FAILURE or data.protocolNeg.failureCode.value != x224.NegotiationFailureCode.SSL_REQUIRED_BY_SERVER:
|
||||||
raise X224Case.X224_FAIL()
|
raise X224Test.X224_FAIL()
|
||||||
|
def close(self):
|
||||||
|
raise X224Test.X224_PASS()
|
||||||
|
|
||||||
message = x224.ClientConnectionRequestPDU()
|
message = x224.ClientConnectionRequestPDU()
|
||||||
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_HYBRID
|
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_HYBRID
|
||||||
@@ -238,11 +178,11 @@ class X224Case(unittest.TestCase):
|
|||||||
s.writeType(message)
|
s.writeType(message)
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
|
|
||||||
layer = x224.Server(None, "key", "cert")
|
layer = x224.Server(None, "key", "cert", True)
|
||||||
layer._transport = Transport()
|
layer._transport = Transport()
|
||||||
layer.connect()
|
layer.connect()
|
||||||
|
|
||||||
self.assertRaises(error.InvalidExpectedDataException, layer.recv, s)
|
self.assertRaises(X224Test.X224_PASS, layer.recv, s)
|
||||||
|
|
||||||
def test_x224_server_recvConnectionRequest_valid(self):
|
def test_x224_server_recvConnectionRequest_valid(self):
|
||||||
"""
|
"""
|
||||||
@@ -259,18 +199,15 @@ class X224Case(unittest.TestCase):
|
|||||||
x224.ServerTLSContext = ServerTLSContext
|
x224.ServerTLSContext = ServerTLSContext
|
||||||
|
|
||||||
class Transport(object):
|
class Transport(object):
|
||||||
def __init__(self):
|
def startTLS(self, context):
|
||||||
class TLS(object):
|
global tls
|
||||||
def startTLS(self, context):
|
tls = True
|
||||||
global tls
|
|
||||||
tls = True
|
|
||||||
self.transport = TLS()
|
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
if not isinstance(data, x224.ServerConnectionConfirm):
|
if not isinstance(data, x224.ServerConnectionConfirm):
|
||||||
raise X224Case.X224_FAIL()
|
raise X224Test.X224_FAIL()
|
||||||
if data.protocolNeg.code.value != x224.NegociationType.TYPE_RDP_NEG_RSP or data.protocolNeg.selectedProtocol.value != x224.Protocols.PROTOCOL_SSL:
|
if data.protocolNeg.code.value != x224.NegociationType.TYPE_RDP_NEG_RSP or data.protocolNeg.selectedProtocol.value != x224.Protocols.PROTOCOL_SSL:
|
||||||
raise X224Case.X224_FAIL()
|
raise X224Test.X224_FAIL()
|
||||||
|
|
||||||
class Presentation(object):
|
class Presentation(object):
|
||||||
def connect(self):
|
def connect(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user