Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c4b42544c | ||
|
|
9550734743 | ||
|
|
73b97d6929 | ||
|
|
018c59fe42 | ||
|
|
9cac72a8d2 | ||
|
|
3f56b25f46 | ||
|
|
cef16a9f64 | ||
|
|
9aea135fd9 | ||
|
|
4109b7a6fe | ||
|
|
d3b0ae5e90 | ||
|
|
a1f9afa87a | ||
|
|
b8ff4136b6 | ||
|
|
ce04150790 | ||
|
|
bcec1aad25 | ||
|
|
c18a4c4101 | ||
|
|
3e37899ae4 | ||
|
|
cd6e14e7ef | ||
|
|
629d2160af | ||
|
|
e23def3179 | ||
|
|
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 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,3 +7,5 @@ README.md~
|
|||||||
dist/*
|
dist/*
|
||||||
build/*
|
build/*
|
||||||
rdpy.egg-info/*
|
rdpy.egg-info/*
|
||||||
|
*.pyd
|
||||||
|
.idea
|
||||||
|
|||||||
@@ -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 service_identity rsa
|
- pip install qt4reactor pyopenssl twisted service_identity rsa pyasn1
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- python setup.py install
|
- python setup.py install
|
||||||
|
|||||||
107
README.md
107
README.md
@@ -1,22 +1,44 @@
|
|||||||
# 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 a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol. RDPY is built over the event driven network engine Twisted.
|
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (client and server side). RDPY is built over the event driven network engine Twisted. RDPY support standard RDP security layer, RDP over SSL and NLA authentication (through ntlmv2 authentication protocol).
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Dependencies are only needed for pyqt4 binaries :
|
||||||
|
* rdpy-rdpclient
|
||||||
|
* rdpy-rdpscreenshot
|
||||||
|
* rdpy-vncclient
|
||||||
|
* rdpy-vncscreenshot
|
||||||
|
* rdpy-rssplayer
|
||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
|
|
||||||
Exemple from Debian based system :
|
Example for Debian based systems :
|
||||||
```
|
```
|
||||||
sudo apt-get install python-qt4
|
sudo apt-get install python-qt4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### OS X
|
||||||
|
Example for OS X to install PyQt with homebrew
|
||||||
|
```
|
||||||
|
$ brew install qt sip pyqt
|
||||||
|
```
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
x86 | x86_64
|
x86 | x86_64
|
||||||
@@ -28,7 +50,7 @@ x86 | x86_64
|
|||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://github.com/citronneur/rdpy.git rdpy
|
$ git clone https://github.com/citronneur/rdpy.git rdpy
|
||||||
$ pip install twisted pyopenssl qt4reactor service_identity rsa
|
$ pip install twisted pyopenssl qt4reactor service_identity rsa pyasn1
|
||||||
$ python rdpy/setup.py install
|
$ python rdpy/setup.py install
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -37,7 +59,7 @@ Or use PIP:
|
|||||||
$ pip install rdpy
|
$ pip install rdpy
|
||||||
```
|
```
|
||||||
|
|
||||||
For virtualenv, you need to link qt4 library to it:
|
For virtualenv, you will need to link the qt4 library to it:
|
||||||
```
|
```
|
||||||
$ ln -s /usr/lib/python2.7/dist-packages/PyQt4/ $VIRTUAL_ENV/lib/python2.7/site-packages/
|
$ ln -s /usr/lib/python2.7/dist-packages/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/
|
||||||
@@ -45,16 +67,18 @@ $ 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-rdpclient.py [-u username] [-p password] [-d domain] [...] XXX.XXX.XXX.XXX[:3389]
|
$ rdpy-rdpclient.py [-u username] [-p password] [-d domain] [-r rss_ouput_file] [...] XXX.XXX.XXX.XXX[:3389]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can use rdpy-rdpclient 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 .
|
||||||
@@ -65,7 +89,7 @@ $ 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-rdpscreenshot.py [-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]
|
||||||
@@ -73,53 +97,55 @@ $ rdpy-rdpscreenshot.py [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX
|
|||||||
|
|
||||||
### rdpy-vncscreenshot
|
### rdpy-vncscreenshot
|
||||||
|
|
||||||
rdpy-vncscreenshot save first screen update in file.
|
rdpy-vncscreenshot saves the first screen update in file.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900]
|
$ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900]
|
||||||
```
|
```
|
||||||
|
|
||||||
### rdpy-rdpproxy
|
### rdpy-rdpmitm
|
||||||
|
|
||||||
rdpy-rdpproxy is a RDP proxy with shadow and record function.
|
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-rdpproxy.py -t target_ip[:target_port] [-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 target ip and port represent the target host.
|
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.
|
||||||
|
|
||||||
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer. The CredSSP security layer is planned for an upcoming release. If one of both parameters are omitted, the server use standard RDP as security layer.
|
### rdpy-rdphoneypot
|
||||||
|
|
||||||
The IP and port admin are used in order to shadow active sessions thanks to a RDP client (rdpy-rdpclient, remina, mstsc) set username parameter like name of session printed by proxy.
|
rdpy-rdphoneypot is an RDP honey Pot. Use Recorded Session Scenario to replay scenario through RDP Protocol.
|
||||||
|
|
||||||
Exemple :
|
|
||||||
```
|
```
|
||||||
$ rdpy-rdpproxy.py -t [my_computer] -i 0.0.0.0:56654 3389
|
$ rdpy-rdphoneypot.py [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] rss_file_path_1 ... rss_file_path_N
|
||||||
$ INFO : Shadow listener on 0.0.0.0:56654
|
|
||||||
$ INFO : **************************************************
|
|
||||||
$ INFO : Now connected
|
|
||||||
$ INFO : ['super-administrator']
|
|
||||||
$ INFO : **************************************************
|
|
||||||
```
|
```
|
||||||
|
|
||||||
To shadow 'super-administrator' session :
|
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer. If one of both parameters are omitted, the server use standard RDP as security layer.
|
||||||
|
You can specify more than one files to match more common screen size.
|
||||||
|
|
||||||
|
### rdpy-rssplayer
|
||||||
|
|
||||||
|
rdpy-rssplayer is use to replay Record Session Scenario (rss) files generates by either rdpy-rdpmitm or rdpy-rdpclient binaries.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rdpy-rdpclient.py -u super-administrator 127.0.0.1:56654
|
$ 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.
|
||||||
|
|
||||||
### Simple RDP Client
|
### Simple RDP Client
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from rdpy.protocol.rdp import rdp
|
from rdpy.core import rdp
|
||||||
|
|
||||||
class MyRDPFactory(rdp.ClientFactory):
|
class MyRDPFactory(rdp.ClientFactory):
|
||||||
|
|
||||||
@@ -131,7 +157,7 @@ class MyRDPFactory(rdp.ClientFactory):
|
|||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
|
|
||||||
class MyObserver(rdp.RDPClientObserver)
|
class MyObserver(rdp.RDPClientObserver):
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@@ -156,6 +182,11 @@ class MyRDPFactory(rdp.ClientFactory):
|
|||||||
@param data: bitmap data
|
@param data: bitmap data
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Windows session is ready
|
||||||
|
"""
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: Call when stack is close
|
@summary: Call when stack is close
|
||||||
@@ -164,19 +195,19 @@ class MyRDPFactory(rdp.ClientFactory):
|
|||||||
return MyObserver(controller)
|
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()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Simple RDP Server
|
### Simple RDP Server
|
||||||
```python
|
```python
|
||||||
from rdpy.protocol.rdp import rdp
|
from rdpy.core import rdp
|
||||||
|
|
||||||
class MyRDPFactory(rdp.ServerFactory):
|
class MyRDPFactory(rdp.ServerFactory):
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
|
|
||||||
class MyObserver(rdp.RDPServerObserver)
|
class MyObserver(rdp.RDPServerObserver):
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@@ -205,7 +236,7 @@ class MyRDPFactory(rdp.ServerFactory):
|
|||||||
@summary: Event call on mouse event
|
@summary: Event call on mouse event
|
||||||
@param x: x position
|
@param x: x position
|
||||||
@param y: y position
|
@param y: y position
|
||||||
@param button: 1, 2 or 3 button
|
@param button: 1, 2, 3, 4 or 5 button
|
||||||
@param isPressed: True if mouse button is pressed
|
@param isPressed: True if mouse button is pressed
|
||||||
@see: rdp.RDPServerObserver.onPointerEvent
|
@see: rdp.RDPServerObserver.onPointerEvent
|
||||||
"""
|
"""
|
||||||
@@ -225,7 +256,7 @@ reactor.run()
|
|||||||
|
|
||||||
### Simple VNC Client
|
### Simple VNC Client
|
||||||
```python
|
```python
|
||||||
from rdpy.protocol.rfb import rdp
|
from rdpy.protocol.rfb import rfb
|
||||||
|
|
||||||
class MyRFBFactory(rfb.ClientFactory):
|
class MyRFBFactory(rfb.ClientFactory):
|
||||||
|
|
||||||
@@ -236,7 +267,7 @@ class MyRFBFactory(rfb.ClientFactory):
|
|||||||
reactor.stop()
|
reactor.stop()
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
class MyObserver(rfb.RFBClientObserver)
|
class MyObserver(rfb.RFBClientObserver):
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@@ -274,6 +305,6 @@ class MyRFBFactory(rfb.ClientFactory):
|
|||||||
return MyObserver(controller)
|
return MyObserver(controller)
|
||||||
|
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRFBFactory())
|
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389, MyRFBFactory())
|
||||||
reactor.run()
|
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.
|
||||||
#
|
#
|
||||||
@@ -21,197 +21,27 @@
|
|||||||
example of use rdpy as rdp client
|
example of use rdpy as rdp client
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, os, getopt, socket
|
import sys
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from PyQt4 import QtGui, QtCore
|
from rdpy.core import tpkt, x224
|
||||||
from rdpy.ui.qt4 import RDPClientQt
|
from rdpy.core.nla import ntlm
|
||||||
from rdpy.protocol.rdp import rdp
|
from rdpy.core.t125 import mcs
|
||||||
from rdpy.core.error import RDPSecurityNegoFail
|
from rdpy.model.message import UInt8
|
||||||
|
|
||||||
import rdpy.core.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, fullscreen, keyboardLayout, optimized, security):
|
|
||||||
"""
|
|
||||||
@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)
|
|
||||||
"""
|
|
||||||
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"
|
|
||||||
if self._nego:
|
|
||||||
self._security = "ssl"
|
|
||||||
else:
|
|
||||||
self._security = security
|
|
||||||
self._w = None
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
|
||||||
"""
|
|
||||||
@summary: Build RFB observer
|
|
||||||
We use a RDPClientQt as RDP observer
|
|
||||||
@param controller: build factory and needed by observer
|
|
||||||
@param addr: destination address
|
|
||||||
@return: RDPClientQt
|
|
||||||
"""
|
|
||||||
#create client observer
|
|
||||||
self._client = RDPClientQt(controller, self._width, self._height)
|
|
||||||
#create qt widget
|
|
||||||
self._w = self._client.getWidget()
|
|
||||||
self._w.setWindowTitle('rdpy-rdpclient')
|
|
||||||
if self._fullscreen:
|
|
||||||
self._w.showFullScreen()
|
|
||||||
else:
|
|
||||||
self._w.show()
|
|
||||||
|
|
||||||
controller.setUsername(self._username)
|
|
||||||
controller.setPassword(self._passwod)
|
|
||||||
controller.setDomain(self._domain)
|
|
||||||
controller.setKeyboardLayout(self._keyboardLayout)
|
|
||||||
controller.setHostname(socket.gethostname())
|
|
||||||
if self._optimized:
|
|
||||||
controller.setPerformanceSession()
|
|
||||||
controller.setSecurityLevel(self._security)
|
|
||||||
|
|
||||||
return self._client
|
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
|
||||||
"""
|
|
||||||
@summary: Connection lost event
|
|
||||||
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
|
|
||||||
@param reason: str use to advertise reason of lost connection
|
|
||||||
"""
|
|
||||||
#try reconnect with basic RDP security
|
|
||||||
if reason.type == RDPSecurityNegoFail and self._nego:
|
|
||||||
#stop nego
|
|
||||||
log.info("due to security nego error back to standard RDP security layer")
|
|
||||||
self._nego = False
|
|
||||||
self._security = "rdp"
|
|
||||||
self._client._widget.hide()
|
|
||||||
connector.connect()
|
|
||||||
return
|
|
||||||
|
|
||||||
QtGui.QMessageBox.warning(self._w, "Warning", "Lost connection : %s"%reason)
|
|
||||||
reactor.stop()
|
|
||||||
app.exit()
|
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
|
||||||
"""
|
|
||||||
@summary: Connection failed event
|
|
||||||
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
|
|
||||||
@param reason: str use to advertise reason of lost connection
|
|
||||||
"""
|
|
||||||
QtGui.QMessageBox.warning(self._w, "Warning", "Connection failed : %s"%reason)
|
|
||||||
reactor.stop()
|
|
||||||
app.exit()
|
|
||||||
|
|
||||||
def autoDetectKeyboardLayout():
|
|
||||||
"""
|
|
||||||
@summary: try to auto detect keyboard layout
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if os.name == 'posix':
|
|
||||||
from subprocess import check_output
|
|
||||||
result = check_output(["setxkbmap", "-print"])
|
|
||||||
if "azerty" in result:
|
|
||||||
return "fr"
|
|
||||||
elif os.name == 'nt':
|
|
||||||
import win32api, win32con, win32process
|
|
||||||
from ctypes import windll
|
|
||||||
w = windll.user32.GetForegroundWindow()
|
|
||||||
tid = windll.user32.GetWindowThreadProcessId(w, 0)
|
|
||||||
result = windll.user32.GetKeyboardLayout(tid)
|
|
||||||
log.info(result)
|
|
||||||
if result == 0x40c040c:
|
|
||||||
return "fr"
|
|
||||||
except Exception as e:
|
|
||||||
log.info("failed to auto detect keyboard layout " + str(e))
|
|
||||||
pass
|
|
||||||
return "en"
|
|
||||||
|
|
||||||
def help():
|
|
||||||
print "Usage: rdpy-rdpclient [options] ip[:port]"
|
|
||||||
print "\t-u: user name"
|
|
||||||
print "\t-p: password"
|
|
||||||
print "\t-d: domain"
|
|
||||||
print "\t-w: width of screen [default : 1024]"
|
|
||||||
print "\t-l: height of screen [default : 800]"
|
|
||||||
print "\t-f: enable full screen mode [default : False]"
|
|
||||||
print "\t-k: keyboard layout [en|fr] [default : en]"
|
|
||||||
print "\t-o: optimized session (disable costly effect) [default : False]"
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
#default script argument
|
#sys.exit(app.exec_())
|
||||||
username = ""
|
|
||||||
password = ""
|
|
||||||
domain = ""
|
|
||||||
width = 1024
|
|
||||||
height = 800
|
|
||||||
fullscreen = False
|
|
||||||
optimized = False
|
|
||||||
keyboardLayout = autoDetectKeyboardLayout()
|
|
||||||
|
|
||||||
try:
|
async def tcp_echo_client(message):
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "hfou:p:d:w:l:k:")
|
reader, writer = await asyncio.open_connection(
|
||||||
except getopt.GetoptError:
|
'127.0.0.1', 33389)
|
||||||
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
|
|
||||||
|
|
||||||
if ':' in args[0]:
|
x224_layer = await x224.connect(tpkt.Tpkt(reader, writer), ntlm.NTLMv2("", "sylvain", "sylvain"))
|
||||||
ip, port = args[0].split(':')
|
mcs_layer = mcs.Client(x224_layer)
|
||||||
else:
|
await mcs_layer.connect()
|
||||||
ip, port = args[0], "3389"
|
|
||||||
|
|
||||||
#create application
|
await asyncio.sleep(10)
|
||||||
app = QtGui.QApplication(sys.argv)
|
print("foooooooooooooooooooo")
|
||||||
|
|
||||||
#add qt4 reactor
|
asyncio.run(tcp_echo_client('Hello World!'))
|
||||||
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"))
|
|
||||||
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, getopt, datetime
|
||||||
|
|
||||||
|
from rdpy.core import log, rss
|
||||||
|
from rdpy.core import rdp
|
||||||
|
from twisted.internet import reactor
|
||||||
|
|
||||||
|
log._LOG_LEVEL = log.Level.INFO
|
||||||
|
|
||||||
|
class HoneyPotServer(rdp.RDPServerObserver):
|
||||||
|
def __init__(self, controller, 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("%s --- select file (%s, %s) -> %s"%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'),width, height, rssFilePath))
|
||||||
|
self._rssFile = rss.createReader(rssFilePath)
|
||||||
|
|
||||||
|
domain, username, password = self._controller.getCredentials()
|
||||||
|
hostname = self._controller.getHostname()
|
||||||
|
log.info("""%s --- Credentials: domain: %s username: %s password: %s hostname: %s"""%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), domain, username, password, hostname));
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def onClose(self):
|
||||||
|
""" HoneyPot """
|
||||||
|
|
||||||
|
def onKeyEventScancode(self, code, isPressed, 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("%s --- Connection from %s:%s"%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), addr.host, addr.port))
|
||||||
|
return HoneyPotServer(controller, self._rssFileSizeList)
|
||||||
|
|
||||||
|
def readSize(filePath):
|
||||||
|
"""
|
||||||
|
@summary: read size event in rss file
|
||||||
|
@param filePath: path of rss file
|
||||||
|
"""
|
||||||
|
r = rss.createReader(filePath)
|
||||||
|
while True:
|
||||||
|
e = r.nextEvent()
|
||||||
|
if e is None:
|
||||||
|
return None
|
||||||
|
elif e.type.value == rss.EventType.SCREEN:
|
||||||
|
return e.event.width.value, e.event.height.value
|
||||||
|
|
||||||
|
def help():
|
||||||
|
"""
|
||||||
|
@summary: Print help in console
|
||||||
|
"""
|
||||||
|
print """
|
||||||
|
Usage: rdpy-rdphoneypot.py
|
||||||
|
[-L logfile]
|
||||||
|
[-l listen_port default 3389]
|
||||||
|
[-k private_key_file_path (mandatory for SSL)]
|
||||||
|
[-c certificate_file_path (mandatory for SSL)]
|
||||||
|
rss_filepath(1..n)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
listen = "3389"
|
||||||
|
privateKeyFilePath = None
|
||||||
|
certificateFilePath = None
|
||||||
|
rssFileSizeList = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:L:")
|
||||||
|
except getopt.GetoptError:
|
||||||
|
help()
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt == "-h":
|
||||||
|
help()
|
||||||
|
sys.exit()
|
||||||
|
elif opt == "-L":
|
||||||
|
log._LOG_FILE = arg
|
||||||
|
elif opt == "-l":
|
||||||
|
listen = arg
|
||||||
|
elif opt == "-k":
|
||||||
|
privateKeyFilePath = arg
|
||||||
|
elif opt == "-c":
|
||||||
|
certificateFilePath = arg
|
||||||
|
|
||||||
|
#build size map
|
||||||
|
log.info("%s --- Start rdphoneypot"%datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
|
||||||
|
log.info("%s --- Build size map"%datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
|
||||||
|
for arg in args:
|
||||||
|
size = readSize(arg)
|
||||||
|
rssFileSizeList.append((size, arg))
|
||||||
|
log.info("%s --- (%s, %s) -> %s"%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), size[0], size[1], arg))
|
||||||
|
|
||||||
|
reactor.listenTCP(int(listen), HoneyPotServerFactory(rssFileSizeList, privateKeyFilePath, certificateFilePath))
|
||||||
|
reactor.run()
|
||||||
322
bin/rdpy-rdpmitm.py
Executable file
322
bin/rdpy-rdpmitm.py
Executable file
@@ -0,0 +1,322 @@
|
|||||||
|
#!/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 os
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
|
||||||
|
from rdpy.core import log, rss
|
||||||
|
from rdpy.core import rdp
|
||||||
|
from twisted.internet import reactor
|
||||||
|
|
||||||
|
log._LOG_LEVEL = log.Level.INFO
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyServer(rdp.RDPServerObserver):
|
||||||
|
"""
|
||||||
|
@summary: Server side of proxy
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, controller, target, clientSecurityLevel, rssRecorder):
|
||||||
|
"""
|
||||||
|
@param controller: {RDPServerController}
|
||||||
|
@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, 3, 4 or 5 button
|
||||||
|
@param isPressed: {bool} True if mouse button is pressed
|
||||||
|
@see: rdp.RDPServerObserver.onPointerEvent
|
||||||
|
"""
|
||||||
|
if self._client is None:
|
||||||
|
return
|
||||||
|
self._client._controller.sendPointerEvent(x, y, button, isPressed)
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyServerFactory(rdp.ServerFactory):
|
||||||
|
"""
|
||||||
|
@summary: Factory on listening events
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, target, ouputDir, privateKeyFilePath, certificateFilePath, clientSecurity):
|
||||||
|
"""
|
||||||
|
@param target: {tuple(ip, prt)}
|
||||||
|
@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 parseIpPort(interface, defaultPort="3389"):
|
||||||
|
if ':' in interface:
|
||||||
|
s = interface.split(':')
|
||||||
|
return s[0], int(s[1])
|
||||||
|
else:
|
||||||
|
return interface, int(defaultPort)
|
||||||
|
|
||||||
|
|
||||||
|
def isDirectory(outputDirectory):
|
||||||
|
if outputDirectory is None or not os.path.dirname(outputDirectory):
|
||||||
|
log.error("{} is an invalid output directory or directory doesn't exist".format(
|
||||||
|
outputDirectory))
|
||||||
|
return outputDirectory
|
||||||
|
|
||||||
|
|
||||||
|
def mapSecurityLayer(layer):
|
||||||
|
return {
|
||||||
|
"rdp": rdp.SecurityLevel.RDP_LEVEL_RDP,
|
||||||
|
"tls": rdp.SecurityLevel.RDP_LEVEL_SSL,
|
||||||
|
"nla": rdp.SecurityLevel.RDP_LEVEL_NLA
|
||||||
|
}[layer]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
p = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
|
|
||||||
|
p.add_argument('-l', '--listen', type=parseIpPort, default="0.0.0.0:3389",
|
||||||
|
help="<addr>[:<port>] to bind the server")
|
||||||
|
p.add_argument('-t', '--target', type=parseIpPort, required=True,
|
||||||
|
help="<addr>[:<port>] of the target you want to connect to via proxy")
|
||||||
|
p.add_argument('-o', '--output', type=isDirectory,
|
||||||
|
help="output directory", required=True)
|
||||||
|
p.add_argument('-s', '--sec', choices=["rdp", "tls", "nla"],
|
||||||
|
default="rdp", help="set protocol security layer")
|
||||||
|
ssl = p.add_argument_group()
|
||||||
|
ssl.add_argument('-c', '--certificate', help="certificate for TLS connections")
|
||||||
|
ssl.add_argument('-k', '--key', help="private key of the given certificate for TLS connections")
|
||||||
|
|
||||||
|
args = p.parse_args()
|
||||||
|
|
||||||
|
if args.certificate and args.key and not args.sec == "nla":
|
||||||
|
args.sec = "tls"
|
||||||
|
|
||||||
|
log.info("running server on {addr}, using {sec} security layer, proxying to {target}".format(
|
||||||
|
addr=args.listen, sec=args.sec.upper(), target=args.target))
|
||||||
|
reactor.listenTCP(args.listen[1], ProxyServerFactory(
|
||||||
|
args.target, args.output, args.key, args.certificate, mapSecurityLayer(args.sec)),
|
||||||
|
interface=args.listen[0])
|
||||||
|
|
||||||
|
reactor.run()
|
||||||
@@ -1,382 +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
|
|
||||||
---------------------------
|
|
||||||
| ProxyShadow |
|
|
||||||
--------------
|
|
||||||
^
|
|
||||||
Shadow -------------------|
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys, os, getopt, json
|
|
||||||
|
|
||||||
from rdpy.core 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
|
|
||||||
"""
|
|
||||||
_SESSIONS_ = {}
|
|
||||||
def __init__(self, controller, target):
|
|
||||||
"""
|
|
||||||
@param controller: {RDPServerController}
|
|
||||||
@param target: {tuple(ip, port)}
|
|
||||||
"""
|
|
||||||
rdp.RDPServerObserver.__init__(self, controller)
|
|
||||||
self._target = target
|
|
||||||
self._client = None
|
|
||||||
|
|
||||||
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())
|
|
||||||
ProxyServer._SESSIONS_[self._controller.getHostname()] = client
|
|
||||||
nowConnected()
|
|
||||||
|
|
||||||
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()
|
|
||||||
log.info("Credentials dump : connection from %s with %s\\%s [%s]"%(self._controller.getHostname(), domain, username, password))
|
|
||||||
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
reactor.connectTCP(self._target[0], int(self._target[1]), ProxyClientFactory(self, width, height,
|
|
||||||
domain, username, password))
|
|
||||||
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
|
|
||||||
|
|
||||||
del ProxyServer._SESSIONS_[self._controller.getHostname()]
|
|
||||||
nowConnected()
|
|
||||||
#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
|
|
||||||
"""
|
|
||||||
if self._client is None:
|
|
||||||
return
|
|
||||||
self._client._controller.sendKeyEventScancode(code, isPressed)
|
|
||||||
|
|
||||||
def onKeyEventUnicode(self, code, isPressed):
|
|
||||||
"""
|
|
||||||
@summary: Event call when a keyboard event is catch in unicode format
|
|
||||||
@param code: unicode of key
|
|
||||||
@param isPressed: True if key is down
|
|
||||||
@see: rdp.RDPServerObserver.onKeyEventUnicode
|
|
||||||
"""
|
|
||||||
if self._client is None:
|
|
||||||
return
|
|
||||||
self._client._controller.sendKeyEventUnicode(code, isPressed)
|
|
||||||
|
|
||||||
def onPointerEvent(self, x, y, button, isPressed):
|
|
||||||
"""
|
|
||||||
@summary: Event call on mouse event
|
|
||||||
@param x: 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
|
|
||||||
"""
|
|
||||||
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, privateKeyFilePath = None, certificateFilePath = None):
|
|
||||||
"""
|
|
||||||
@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)
|
|
||||||
"""
|
|
||||||
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
|
|
||||||
self._target = target
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
|
||||||
"""
|
|
||||||
@param controller: rdp.RDPServerController
|
|
||||||
@param addr: destination address
|
|
||||||
@see: rdp.ServerFactory.buildObserver
|
|
||||||
"""
|
|
||||||
return ProxyServer(controller, self._target)
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
|
||||||
|
|
||||||
self._server.clientConnected(self)
|
|
||||||
|
|
||||||
def onClose(self):
|
|
||||||
"""
|
|
||||||
@summary: Event inform that stack is close
|
|
||||||
@see: rdp.RDPClientObserver.onClose
|
|
||||||
"""
|
|
||||||
|
|
||||||
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
|
|
||||||
self._security = "ssl"
|
|
||||||
|
|
||||||
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)
|
|
||||||
return ProxyClient(controller, self._server)
|
|
||||||
|
|
||||||
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 == error.RDPSecurityNegoFail:
|
|
||||||
#stop nego
|
|
||||||
log.info("due to security nego error back to standard RDP security layer")
|
|
||||||
self._security = "rdp"
|
|
||||||
connector.connect()
|
|
||||||
return
|
|
||||||
|
|
||||||
class Shadow(rdp.RDPServerObserver):
|
|
||||||
"""
|
|
||||||
@summary: Use to manage admin session
|
|
||||||
"""
|
|
||||||
def __init__(self, controller):
|
|
||||||
"""
|
|
||||||
@param server: rdp.RDPServerController
|
|
||||||
"""
|
|
||||||
rdp.RDPServerObserver.__init__(self, controller)
|
|
||||||
self._client = None
|
|
||||||
|
|
||||||
def onReady(self):
|
|
||||||
"""
|
|
||||||
@summary: Stack is ready and connected
|
|
||||||
May be called after an setColorDepth too
|
|
||||||
@see: rdp.RDPServerObserver.onReady
|
|
||||||
"""
|
|
||||||
if self._client is None:
|
|
||||||
username = self._controller.getUsername()
|
|
||||||
if not ProxyServer._SESSIONS_.has_key(username):
|
|
||||||
log.info("invalid session name [%s]"%username)
|
|
||||||
self._controller.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
self._client = ProxyClient(ProxyServer._SESSIONS_[username]._controller, self)
|
|
||||||
self._controller.setColorDepth(self._client._controller.getColorDepth())
|
|
||||||
else:
|
|
||||||
#refresh client
|
|
||||||
width, height = self._controller.getScreen()
|
|
||||||
self._client._controller.sendRefreshOrder(0, 0, width, height)
|
|
||||||
|
|
||||||
def onClose(self):
|
|
||||||
"""
|
|
||||||
@summary: Stack is close
|
|
||||||
@see: rdp.RDPServerObserver.onClose
|
|
||||||
"""
|
|
||||||
|
|
||||||
def onKeyEventScancode(self, code, isPressed):
|
|
||||||
""" Shadow
|
|
||||||
"""
|
|
||||||
|
|
||||||
def onKeyEventUnicode(self, code, isPressed):
|
|
||||||
""" Shadow
|
|
||||||
"""
|
|
||||||
|
|
||||||
def onPointerEvent(self, x, y, button, isPressed):
|
|
||||||
""" Shadow
|
|
||||||
"""
|
|
||||||
|
|
||||||
class ShadowFactory(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, 16, privateKeyFilePath, certificateFilePath)
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
|
||||||
"""
|
|
||||||
@summary: Build ProxyAdmin
|
|
||||||
@param controller: rdp.RDPServerController
|
|
||||||
@param addr: destination address
|
|
||||||
@return: ProxyAdmin
|
|
||||||
@see: rdp.ServerFactory.buildObserver
|
|
||||||
"""
|
|
||||||
return Shadow(controller)
|
|
||||||
|
|
||||||
def help():
|
|
||||||
"""
|
|
||||||
@summary: Print help in console
|
|
||||||
"""
|
|
||||||
print "Usage: rdpy-rdpproxy -t target_ip[:target_port] [-k private_key_file_path (mandatory for SSL)] [-c certificate_file_path (mandatory for SSL)] [-i admin_ip[:admin_port]] listen_port"
|
|
||||||
|
|
||||||
def parseIpPort(interface, defaultPort = "3389"):
|
|
||||||
if ':' in interface:
|
|
||||||
return interface.split(':')
|
|
||||||
else:
|
|
||||||
return interface, defaultPort
|
|
||||||
|
|
||||||
def nowConnected():
|
|
||||||
log.info("*" * 50)
|
|
||||||
log.info("Now connected")
|
|
||||||
log.info(ProxyServer._SESSIONS_.keys())
|
|
||||||
log.info("*" * 50)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
target = None
|
|
||||||
privateKeyFilePath = None
|
|
||||||
certificateFilePath = None
|
|
||||||
shadowInterface = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "ht:k:c:i:")
|
|
||||||
except getopt.GetoptError:
|
|
||||||
help()
|
|
||||||
for opt, arg in opts:
|
|
||||||
if opt == "-h":
|
|
||||||
help()
|
|
||||||
sys.exit()
|
|
||||||
elif opt == "-t":
|
|
||||||
target = arg
|
|
||||||
elif opt == "-k":
|
|
||||||
privateKeyFilePath = arg
|
|
||||||
elif opt == "-c":
|
|
||||||
certificateFilePath = arg
|
|
||||||
elif opt == "-i":
|
|
||||||
shadowInterface = arg
|
|
||||||
|
|
||||||
if target is None:
|
|
||||||
log.error("Target is mandatory")
|
|
||||||
help()
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
reactor.listenTCP(int(args[0]), ProxyServerFactory(parseIpPort(target), privateKeyFilePath, certificateFilePath))
|
|
||||||
|
|
||||||
if not shadowInterface is None:
|
|
||||||
shadowInterface, shadowPort = parseIpPort(shadowInterface)
|
|
||||||
log.info("Shadow listener on %s:%s"%(shadowInterface, shadowPort))
|
|
||||||
reactor.listenTCP(int(shadowPort), ShadowFactory(privateKeyFilePath, certificateFilePath), interface = shadowInterface)
|
|
||||||
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.
|
||||||
#
|
#
|
||||||
@@ -23,24 +23,26 @@ example of use rdpy
|
|||||||
take screenshot of login page
|
take screenshot of login page
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, os, getopt
|
import getopt
|
||||||
|
import sys
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtGui
|
||||||
from rdpy.protocol.rdp import rdp
|
from rdpy.core import rdp
|
||||||
from rdpy.ui.qt4 import RDPBitmapToQtImage
|
from rdpy.ui.qt4 import RDPBitmapToQtImage
|
||||||
import rdpy.core.log as log
|
import rdpy.core.log as log
|
||||||
from rdpy.core.error import RDPSecurityNegoFail
|
from rdpy.core.error import RDPSecurityNegoFail
|
||||||
from twisted.internet import task
|
|
||||||
|
|
||||||
# set log level
|
# set log level
|
||||||
log._LOG_LEVEL = log.Level.INFO
|
log._LOG_LEVEL = log.Level.INFO
|
||||||
|
|
||||||
|
|
||||||
class RDPScreenShotFactory(rdp.ClientFactory):
|
class RDPScreenShotFactory(rdp.ClientFactory):
|
||||||
"""
|
"""
|
||||||
@summary: Factory for screenshot exemple
|
@summary: Factory for screenshot exemple
|
||||||
"""
|
"""
|
||||||
__INSTANCE__ = 0
|
__INSTANCE__ = 0
|
||||||
__STATE__ = []
|
__STATE__ = []
|
||||||
|
|
||||||
def __init__(self, reactor, app, width, height, path, timeout):
|
def __init__(self, reactor, app, width, height, path, timeout):
|
||||||
"""
|
"""
|
||||||
@param reactor: twisted reactor
|
@param reactor: twisted reactor
|
||||||
@@ -56,7 +58,8 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
self._height = height
|
self._height = height
|
||||||
self._path = path
|
self._path = path
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
self._security = "ssl"
|
#NLA server can't be screenshooting
|
||||||
|
self._security = rdp.SecurityLevel.RDP_LEVEL_SSL
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
def clientConnectionLost(self, connector, reason):
|
||||||
"""
|
"""
|
||||||
@@ -66,7 +69,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
if reason.type == RDPSecurityNegoFail and self._security != "rdp":
|
if reason.type == RDPSecurityNegoFail and self._security != "rdp":
|
||||||
log.info("due to RDPSecurityNegoFail try standard security layer")
|
log.info("due to RDPSecurityNegoFail try standard security layer")
|
||||||
self._security = "rdp"
|
self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
|
||||||
connector.connect()
|
connector.connect()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -90,7 +93,6 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
self._reactor.stop()
|
self._reactor.stop()
|
||||||
self._app.exit()
|
self._app.exit()
|
||||||
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
"""
|
"""
|
||||||
@summary: build ScreenShot observer
|
@summary: build ScreenShot observer
|
||||||
@@ -121,7 +123,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
@summary: callback use when bitmap is received
|
@summary: callback use when bitmap is received
|
||||||
"""
|
"""
|
||||||
image = RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data);
|
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data);
|
||||||
with QtGui.QPainter(self._buffer) as qp:
|
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)
|
||||||
@@ -135,6 +137,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
|
||||||
@@ -145,7 +154,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
def checkUpdate(self):
|
def checkUpdate(self):
|
||||||
self._controller.close();
|
self._controller.close();
|
||||||
|
|
||||||
controller.setScreen(width, height);
|
controller.setScreen(self._width, self._height);
|
||||||
controller.setSecurityLevel(self._security)
|
controller.setSecurityLevel(self._security)
|
||||||
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout, self._reactor)
|
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout, self._reactor)
|
||||||
|
|
||||||
@@ -179,6 +188,7 @@ def main(width, height, path, timeout, hosts):
|
|||||||
app.exec_()
|
app.exec_()
|
||||||
return RDPScreenShotFactory.__STATE__
|
return RDPScreenShotFactory.__STATE__
|
||||||
|
|
||||||
|
|
||||||
def help():
|
def help():
|
||||||
print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
|
print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
|
||||||
print "\t-w: width of screen default value is 1024"
|
print "\t-w: width of screen default value is 1024"
|
||||||
|
|||||||
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.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -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.
|
||||||
#
|
#
|
||||||
|
|||||||
15
ext/rle.c
15
ext/rle.c
@@ -939,9 +939,18 @@ static PyMethodDef rle_methods[] =
|
|||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
static struct PyModuleDef rle =
|
||||||
initrle(void)
|
|
||||||
{
|
{
|
||||||
(void) Py_InitModule("rle", rle_methods);
|
PyModuleDef_HEAD_INIT,
|
||||||
|
"rle", /* name of module */
|
||||||
|
"", /* module documentation, may be NULL */
|
||||||
|
-1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */
|
||||||
|
rle_methods
|
||||||
|
};
|
||||||
|
|
||||||
|
PyMODINIT_FUNC
|
||||||
|
PyInit_rle(void)
|
||||||
|
{
|
||||||
|
(void) PyModule_Create(&rle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,11 @@
|
|||||||
@see: http://msdn.microsoft.com/en-us/library/cc241880.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc241880.aspx
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.core.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType, Stream
|
from rdpy.model.type import CompositeType, CallableValue, UInt8, UInt16Le, UInt32Le, Buffer, sizeof, FactoryType, ArrayType, Stream
|
||||||
from rdpy.core.error import InvalidExpectedDataException
|
from rdpy.model.error import InvalidExpectedDataException
|
||||||
import rdpy.core.log as log
|
import rdpy.model.log as log
|
||||||
import sec, gcc
|
from rdpy.core import sec
|
||||||
|
from rdpy.core.t125 import gcc
|
||||||
from rdpy.security import rc4
|
from rdpy.security import rc4
|
||||||
from rdpy.security import rsa_wrapper as rsa
|
from rdpy.security import rsa_wrapper as rsa
|
||||||
|
|
||||||
@@ -97,11 +98,11 @@ class LicenseBinaryBlob(CompositeType):
|
|||||||
@summary: Blob use by license manager to exchange security data
|
@summary: Blob use by license manager to exchange security data
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, blobType = BinaryBlobType.BB_ANY_BLOB):
|
def __init__(self, blobType = BinaryBlobType.BB_ANY_BLOB, optional = False):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, optional = optional)
|
||||||
self.wBlobType = UInt16Le(blobType, constant = True if blobType != BinaryBlobType.BB_ANY_BLOB else False)
|
self.wBlobType = UInt16Le(blobType, constant = True if blobType != BinaryBlobType.BB_ANY_BLOB else False)
|
||||||
self.wBlobLen = UInt16Le(lambda:sizeof(self.blobData))
|
self.wBlobLen = UInt16Le(lambda:sizeof(self.blobData))
|
||||||
self.blobData = String(readLen = self.wBlobLen)
|
self.blobData = Buffer(readLen = self.wBlobLen)
|
||||||
|
|
||||||
class LicensingErrorMessage(CompositeType):
|
class LicensingErrorMessage(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -110,11 +111,11 @@ class LicensingErrorMessage(CompositeType):
|
|||||||
"""
|
"""
|
||||||
_MESSAGE_TYPE_ = MessageType.ERROR_ALERT
|
_MESSAGE_TYPE_ = MessageType.ERROR_ALERT
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.dwErrorCode = UInt32Le()
|
self.dwErrorCode = UInt32Le()
|
||||||
self.dwStateTransition = UInt32Le()
|
self.dwStateTransition = UInt32Le()
|
||||||
self.blob = LicenseBinaryBlob(BinaryBlobType.BB_ERROR_BLOB)
|
self.blob = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
|
||||||
|
|
||||||
class ProductInformation(CompositeType):
|
class ProductInformation(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -126,10 +127,10 @@ class ProductInformation(CompositeType):
|
|||||||
self.dwVersion = UInt32Le()
|
self.dwVersion = UInt32Le()
|
||||||
self.cbCompanyName = UInt32Le(lambda:sizeof(self.pbCompanyName))
|
self.cbCompanyName = UInt32Le(lambda:sizeof(self.pbCompanyName))
|
||||||
#may contain "Microsoft Corporation" from server microsoft
|
#may contain "Microsoft Corporation" from server microsoft
|
||||||
self.pbCompanyName = String("Microsoft Corporation", readLen = self.cbCompanyName, unicode = True)
|
self.pbCompanyName = Buffer("Microsoft Corporation", readLen = self.cbCompanyName, unicode = True)
|
||||||
self.cbProductId = UInt32Le(lambda:sizeof(self.pbProductId))
|
self.cbProductId = UInt32Le(lambda:sizeof(self.pbProductId))
|
||||||
#may contain "A02" from microsoft license server
|
#may contain "A02" from microsoft license server
|
||||||
self.pbProductId = String("A02", readLen = self.cbProductId, unicode = True)
|
self.pbProductId = Buffer("A02", readLen = self.cbProductId, unicode = True)
|
||||||
|
|
||||||
|
|
||||||
class Scope(CompositeType):
|
class Scope(CompositeType):
|
||||||
@@ -159,9 +160,9 @@ class ServerLicenseRequest(CompositeType):
|
|||||||
"""
|
"""
|
||||||
_MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST
|
_MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.serverRandom = String("\x00" * 32, readLen = UInt8(32))
|
self.serverRandom = Buffer("\x00" * 32, readLen = CallableValue(32))
|
||||||
self.productInfo = ProductInformation()
|
self.productInfo = ProductInformation()
|
||||||
self.keyExchangeList = LicenseBinaryBlob(BinaryBlobType.BB_KEY_EXCHG_ALG_BLOB)
|
self.keyExchangeList = LicenseBinaryBlob(BinaryBlobType.BB_KEY_EXCHG_ALG_BLOB)
|
||||||
self.serverCertificate = LicenseBinaryBlob(BinaryBlobType.BB_CERTIFICATE_BLOB)
|
self.serverCertificate = LicenseBinaryBlob(BinaryBlobType.BB_CERTIFICATE_BLOB)
|
||||||
@@ -175,14 +176,14 @@ class ClientNewLicenseRequest(CompositeType):
|
|||||||
"""
|
"""
|
||||||
_MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST
|
_MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
#RSA and must be only RSA
|
#RSA and must be only RSA
|
||||||
self.preferredKeyExchangeAlg = UInt32Le(0x00000001, constant = True)
|
self.preferredKeyExchangeAlg = UInt32Le(0x00000001, constant = True)
|
||||||
#pure microsoft client ;-)
|
#pure microsoft client ;-)
|
||||||
#http://msdn.microsoft.com/en-us/library/1040af38-c733-4fb3-acd1-8db8cc979eda#id10
|
#http://msdn.microsoft.com/en-us/library/1040af38-c733-4fb3-acd1-8db8cc979eda#id10
|
||||||
self.platformId = UInt32Le(0x04000000 | 0x00010000)
|
self.platformId = UInt32Le(0x04000000 | 0x00010000)
|
||||||
self.clientRandom = String("\x00" * 32, readLen = UInt8(32))
|
self.clientRandom = Buffer("\x00" * 32, readLen = CallableValue(32))
|
||||||
self.encryptedPreMasterSecret = LicenseBinaryBlob(BinaryBlobType.BB_RANDOM_BLOB)
|
self.encryptedPreMasterSecret = LicenseBinaryBlob(BinaryBlobType.BB_RANDOM_BLOB)
|
||||||
self.ClientUserName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_USER_NAME_BLOB)
|
self.ClientUserName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_USER_NAME_BLOB)
|
||||||
self.ClientMachineName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_MACHINE_NAME_BLOB)
|
self.ClientMachineName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_MACHINE_NAME_BLOB)
|
||||||
@@ -194,11 +195,11 @@ class ServerPlatformChallenge(CompositeType):
|
|||||||
"""
|
"""
|
||||||
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE
|
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.connectFlags = UInt32Le()
|
self.connectFlags = UInt32Le()
|
||||||
self.encryptedPlatformChallenge = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
|
self.encryptedPlatformChallenge = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
|
||||||
self.MACData = String(readLen = UInt8(16))
|
self.MACData = Buffer(readLen = CallableValue(16))
|
||||||
|
|
||||||
class ClientPLatformChallengeResponse(CompositeType):
|
class ClientPLatformChallengeResponse(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -207,11 +208,11 @@ class ClientPLatformChallengeResponse(CompositeType):
|
|||||||
"""
|
"""
|
||||||
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE_RESPONSE
|
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE_RESPONSE
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.encryptedPlatformChallengeResponse = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
|
self.encryptedPlatformChallengeResponse = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
|
||||||
self.encryptedHWID = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
|
self.encryptedHWID = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
|
||||||
self.MACData = String(readLen = UInt8(16))
|
self.MACData = Buffer(readLen = CallableValue(16))
|
||||||
|
|
||||||
class LicPacket(CompositeType):
|
class LicPacket(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -231,9 +232,9 @@ class LicPacket(CompositeType):
|
|||||||
"""
|
"""
|
||||||
for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest, ServerPlatformChallenge, ClientPLatformChallengeResponse]:
|
for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest, ServerPlatformChallenge, ClientPLatformChallengeResponse]:
|
||||||
if self.bMsgtype.value == c._MESSAGE_TYPE_:
|
if self.bMsgtype.value == c._MESSAGE_TYPE_:
|
||||||
return c()
|
return c(readLen = self.wMsgSize - 4)
|
||||||
log.debug("unknown license message : %s"%self.bMsgtype.value)
|
log.debug("unknown license message : %s"%self.bMsgtype.value)
|
||||||
return String()
|
return Buffer(readLen =self.wMsgSize - 4)
|
||||||
|
|
||||||
if message is None:
|
if message is None:
|
||||||
message = FactoryType(LicensingMessageFactory)
|
message = FactoryType(LicensingMessageFactory)
|
||||||
@@ -271,7 +272,7 @@ class LicenseManager(object):
|
|||||||
@return true when license automata is finish
|
@return true when license automata is finish
|
||||||
"""
|
"""
|
||||||
licPacket = LicPacket()
|
licPacket = LicPacket()
|
||||||
s.readType(licPacket)
|
s.read_type(licPacket)
|
||||||
|
|
||||||
#end of automata
|
#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:
|
if licPacket.bMsgtype.value == MessageType.ERROR_ALERT and licPacket.licensingMessage.dwErrorCode.value == ErrorCode.STATUS_VALID_CLIENT and licPacket.licensingMessage.dwStateTransition.value == StateTransition.ST_NO_TRANSITION:
|
||||||
@@ -302,9 +303,12 @@ class LicenseManager(object):
|
|||||||
"""
|
"""
|
||||||
#get server information
|
#get server information
|
||||||
serverRandom = licenseRequest.serverRandom.value
|
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)
|
s = Stream(licenseRequest.serverCertificate.blobData.value)
|
||||||
serverCertificate = gcc.ServerCertificate()
|
serverCertificate = gcc.ServerCertificate()
|
||||||
s.readType(serverCertificate)
|
s.read_type(serverCertificate)
|
||||||
|
|
||||||
#generate crypto values
|
#generate crypto values
|
||||||
clientRandom = rsa.random(256)
|
clientRandom = rsa.random(256)
|
||||||
@@ -331,10 +335,12 @@ class LicenseManager(object):
|
|||||||
#decrypt server challenge
|
#decrypt server challenge
|
||||||
#it should be TEST word in unicode format
|
#it should be TEST word in unicode format
|
||||||
serverChallenge = rc4.crypt(rc4.RC4Key(self._licenseKey), serverEncryptedChallenge)
|
serverChallenge = rc4.crypt(rc4.RC4Key(self._licenseKey), serverEncryptedChallenge)
|
||||||
|
if serverChallenge != "T\x00E\x00S\x00T\x00\x00\x00":
|
||||||
|
raise InvalidExpectedDataException("bad license server challenge")
|
||||||
|
|
||||||
#generate hwid
|
#generate hwid
|
||||||
s = Stream()
|
s = Stream()
|
||||||
s.writeType((UInt32Le(2), String(self._hostname + self._username + "\x00" * 16)))
|
s.write_type((UInt32Le(2), Buffer(self._hostname + self._username + "\x00" * 16)))
|
||||||
hwid = s.getvalue()[:20]
|
hwid = s.getvalue()[:20]
|
||||||
|
|
||||||
message = ClientPLatformChallengeResponse()
|
message = ClientPLatformChallengeResponse()
|
||||||
206
rdpy/core/nla/cssp.py
Normal file
206
rdpy/core/nla/cssp.py
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
@summary: Credential Security Support Provider
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226764.aspx
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyasn1.type import namedtype, univ, tag
|
||||||
|
import pyasn1.codec.der.encoder as der_encoder
|
||||||
|
import pyasn1.codec.der.decoder as der_decoder
|
||||||
|
|
||||||
|
from rdpy.core.nla import sspi
|
||||||
|
from rdpy.model.message import Stream
|
||||||
|
from rdpy.model import error
|
||||||
|
from rdpy.security.x509 import X509Certificate
|
||||||
|
|
||||||
|
|
||||||
|
class NegoToken(univ.Sequence):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('negoToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NegoData(univ.SequenceOf):
|
||||||
|
"""
|
||||||
|
@summary: contain spnego ntlm of kerberos data
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226781.aspx
|
||||||
|
"""
|
||||||
|
componentType = NegoToken()
|
||||||
|
|
||||||
|
|
||||||
|
class TSRequest(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: main structure
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226780.aspx
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('version', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||||
|
namedtype.OptionalNamedType('negoTokens', NegoData().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||||
|
namedtype.OptionalNamedType('authInfo', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||||
|
namedtype.OptionalNamedType('pubKeyAuth', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
|
||||||
|
namedtype.OptionalNamedType('errorCode', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TSCredentials(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: contain user information
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226782.aspx
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('credType', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||||
|
namedtype.NamedType('credentials', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TSPasswordCreds(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: contain username and password
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226783.aspx
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('domainName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||||
|
namedtype.NamedType('userName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||||
|
namedtype.NamedType('password', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TSCspDataDetail(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: smart card credentials
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226785.aspx
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('keySpec', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||||
|
namedtype.OptionalNamedType('cardName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||||
|
namedtype.OptionalNamedType('readerName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||||
|
namedtype.OptionalNamedType('containerName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
|
||||||
|
namedtype.OptionalNamedType('cspName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TSSmartCardCreds(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: smart card credentials
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226784.aspx
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('pin', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||||
|
namedtype.NamedType('cspData', TSCspDataDetail().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||||
|
namedtype.OptionalNamedType('userHint', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||||
|
namedtype.OptionalNamedType('domainHint', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def encode_der_trequest(nego_types=[], auth_info=None, pub_key_auth=None):
|
||||||
|
"""
|
||||||
|
@summary: create TSRequest from list of Type
|
||||||
|
@param nego_types: {list(Type)}
|
||||||
|
@param auth_info: {str} authentication info TSCredentials encrypted with authentication protocol
|
||||||
|
@param pub_key_auth: {str} public key encrypted with authentication protocol
|
||||||
|
@return: {str} TRequest der encoded
|
||||||
|
"""
|
||||||
|
negoData = NegoData().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))
|
||||||
|
|
||||||
|
#fill nego data tokens
|
||||||
|
i = 0
|
||||||
|
for negoType in nego_types:
|
||||||
|
s = Stream()
|
||||||
|
s.write_type(negoType)
|
||||||
|
negoToken = NegoToken()
|
||||||
|
negoToken.setComponentByPosition(0, s.getvalue())
|
||||||
|
negoData.setComponentByPosition(i, negoToken)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
request = TSRequest()
|
||||||
|
request.setComponentByName("version", univ.Integer(2).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
|
||||||
|
|
||||||
|
if i > 0:
|
||||||
|
request.setComponentByName("negoTokens", negoData)
|
||||||
|
|
||||||
|
if not auth_info is None:
|
||||||
|
request.setComponentByName("authInfo", univ.OctetString(auth_info).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
|
||||||
|
|
||||||
|
if not pub_key_auth is None:
|
||||||
|
request.setComponentByName("pubKeyAuth", univ.OctetString(pub_key_auth).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||||
|
|
||||||
|
return der_encoder.encode(request)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_der_trequest(s):
|
||||||
|
"""
|
||||||
|
@summary: Decode the stream as
|
||||||
|
@param s: {str}
|
||||||
|
"""
|
||||||
|
return der_decoder.decode(s, asn1Spec=TSRequest())[0]
|
||||||
|
|
||||||
|
|
||||||
|
def getNegoTokens(tRequest):
|
||||||
|
negoData = tRequest.getComponentByName("negoTokens")
|
||||||
|
return [Stream(negoData.getComponentByPosition(i).getComponentByPosition(0).asOctets()) for i in range(len(negoData))]
|
||||||
|
|
||||||
|
|
||||||
|
def encode_der_tcredentials(domain, username, password):
|
||||||
|
passwordCred = TSPasswordCreds()
|
||||||
|
passwordCred.setComponentByName("domainName", domain)
|
||||||
|
passwordCred.setComponentByName("userName", username)
|
||||||
|
passwordCred.setComponentByName("password", password)
|
||||||
|
|
||||||
|
credentials = TSCredentials()
|
||||||
|
credentials.setComponentByName("credType", 1)
|
||||||
|
credentials.setComponentByName("credentials", der_encoder.encode(passwordCred))
|
||||||
|
|
||||||
|
return der_encoder.encode(credentials)
|
||||||
|
|
||||||
|
|
||||||
|
async def connect(reader, writer, authentication_protocol: sspi.IAuthenticationProtocol):
|
||||||
|
"""
|
||||||
|
CSSP connection sequence
|
||||||
|
"""
|
||||||
|
# send negotiate message
|
||||||
|
writer.write(encode_der_trequest(nego_types=[authentication_protocol.getNegotiateMessage()]))
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
|
trequest = decode_der_trequest(await reader.read(1500))
|
||||||
|
message, interface = authentication_protocol.getAuthenticateMessage(getNegoTokens(trequest)[0])
|
||||||
|
|
||||||
|
# get binary certificate
|
||||||
|
# There is no other way to get certificate that have net been validated
|
||||||
|
peer_certificate = writer.transport._ssl_protocol._sslpipe.ssl_object._sslobj.getpeercert(True)
|
||||||
|
x509_certificate = der_decoder.decode(peer_certificate, asn1Spec=X509Certificate())[0]
|
||||||
|
public_key = x509_certificate.getComponentByName("tbsCertificate").getComponentByName("subjectPublicKeyInfo").getComponentByName("subjectPublicKey").asOctets()
|
||||||
|
|
||||||
|
# send back public key encrypted with NTLM
|
||||||
|
writer.write(encode_der_trequest(nego_types=[message], pub_key_auth=interface.GSS_WrapEx(public_key)))
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
|
# now check the increment
|
||||||
|
# sever must respond with the same key incremented to one
|
||||||
|
trequest = decode_der_trequest(await reader.read(1500))
|
||||||
|
public_key_inc = interface.GSS_UnWrapEx(trequest.getComponentByName("pubKeyAuth").asOctets())
|
||||||
|
|
||||||
|
if int.from_bytes(public_key, "little") + 1 != int.from_bytes(public_key_inc, "little"):
|
||||||
|
raise error.InvalidExpectedDataException("CSSP : Invalid public key increment")
|
||||||
|
|
||||||
|
domain, user, password = authentication_protocol.getEncodedCredentials()
|
||||||
|
writer.write(encode_der_trequest(auth_info=interface.GSS_WrapEx(encode_der_tcredentials(domain, user, password))))
|
||||||
|
await writer.drain()
|
||||||
652
rdpy/core/nla/ntlm.py
Normal file
652
rdpy/core/nla/ntlm.py
Normal file
@@ -0,0 +1,652 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 Sylvain Peyrefitte
|
||||||
|
#
|
||||||
|
# This file is part of rdpy.
|
||||||
|
#
|
||||||
|
# rdpy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
@summary: NTLM Authentication
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236621.aspx
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib, hmac, struct, datetime
|
||||||
|
from rdpy.core.nla import sspi
|
||||||
|
import rdpy.security.pyDes as pyDes
|
||||||
|
import rdpy.security.rc4 as rc4
|
||||||
|
from rdpy.security.rsa_wrapper import random
|
||||||
|
from rdpy.model.message import CompositeType, Buffer, UInt8, UInt16Le, UInt24Le, UInt32Le, sizeof, Stream
|
||||||
|
from rdpy.model import filetimes, error
|
||||||
|
|
||||||
|
|
||||||
|
class MajorVersion(object):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236654.aspx
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/a211d894-21bc-4b8b-86ba-b83d0c167b00#id29
|
||||||
|
"""
|
||||||
|
WINDOWS_MAJOR_VERSION_5 = 0x05
|
||||||
|
WINDOWS_MAJOR_VERSION_6 = 0x06
|
||||||
|
|
||||||
|
|
||||||
|
class MinorVersion(object):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236654.aspx
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/a211d894-21bc-4b8b-86ba-b83d0c167b00#id30
|
||||||
|
"""
|
||||||
|
WINDOWS_MINOR_VERSION_0 = 0x00
|
||||||
|
WINDOWS_MINOR_VERSION_1 = 0x01
|
||||||
|
WINDOWS_MINOR_VERSION_2 = 0x02
|
||||||
|
WINDOWS_MINOR_VERSION_3 = 0x03
|
||||||
|
|
||||||
|
|
||||||
|
class NTLMRevision(object):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236654.aspx
|
||||||
|
"""
|
||||||
|
NTLMSSP_REVISION_W2K3 = 0x0F
|
||||||
|
|
||||||
|
|
||||||
|
class Negotiate(object):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236650.aspx
|
||||||
|
"""
|
||||||
|
NTLMSSP_NEGOTIATE_56 = 0x80000000
|
||||||
|
NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000
|
||||||
|
NTLMSSP_NEGOTIATE_128 = 0x20000000
|
||||||
|
NTLMSSP_NEGOTIATE_VERSION = 0x02000000
|
||||||
|
NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000
|
||||||
|
NTLMSSP_REQUEST_NON_NT_SESSION_KEY = 0x00400000
|
||||||
|
NTLMSSP_NEGOTIATE_IDENTIFY = 0x00100000
|
||||||
|
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000
|
||||||
|
NTLMSSP_TARGET_TYPE_SERVER = 0x00020000
|
||||||
|
NTLMSSP_TARGET_TYPE_DOMAIN = 0x00010000
|
||||||
|
NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000
|
||||||
|
NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000
|
||||||
|
NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000
|
||||||
|
NTLMSSP_NEGOTIATE_NTLM = 0x00000200
|
||||||
|
NTLMSSP_NEGOTIATE_LM_KEY = 0x00000080
|
||||||
|
NTLMSSP_NEGOTIATE_DATAGRAM = 0x00000040
|
||||||
|
NTLMSSP_NEGOTIATE_SEAL = 0x00000020
|
||||||
|
NTLMSSP_NEGOTIATE_SIGN = 0x00000010
|
||||||
|
NTLMSSP_REQUEST_TARGET = 0x00000004
|
||||||
|
NTLM_NEGOTIATE_OEM = 0x00000002
|
||||||
|
NTLMSSP_NEGOTIATE_UNICODE = 0x00000001
|
||||||
|
|
||||||
|
|
||||||
|
class AvId(object):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236646.aspx
|
||||||
|
"""
|
||||||
|
MsvAvEOL = 0x0000
|
||||||
|
MsvAvNbComputerName = 0x0001
|
||||||
|
MsvAvNbDomainName = 0x0002
|
||||||
|
MsvAvDnsComputerName = 0x0003
|
||||||
|
MsvAvDnsDomainName = 0x0004
|
||||||
|
MsvAvDnsTreeName = 0x0005
|
||||||
|
MsvAvFlags = 0x0006
|
||||||
|
MsvAvTimestamp = 0x0007
|
||||||
|
MsvAvSingleHost = 0x0008
|
||||||
|
MsvAvTargetName = 0x0009
|
||||||
|
MsvChannelBindings = 0x000A
|
||||||
|
|
||||||
|
def getPayLoadField(message, length, bufferOffset):
|
||||||
|
if length == 0:
|
||||||
|
return None
|
||||||
|
offset = sizeof(message) - sizeof(message.Payload)
|
||||||
|
start = bufferOffset - offset
|
||||||
|
end = start + length
|
||||||
|
return message.Payload.value[start:end]
|
||||||
|
|
||||||
|
class Version(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Version structure as describe in NTLM spec
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236654.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self, conditional):
|
||||||
|
CompositeType.__init__(self, conditional = conditional)
|
||||||
|
self.ProductMajorVersion = UInt8(MajorVersion.WINDOWS_MAJOR_VERSION_6)
|
||||||
|
self.ProductMinorVersion = UInt8(MinorVersion.WINDOWS_MINOR_VERSION_0)
|
||||||
|
self.ProductBuild = UInt16Le(6002)
|
||||||
|
self.Reserved = UInt24Le()
|
||||||
|
self.NTLMRevisionCurrent = UInt8(NTLMRevision.NTLMSSP_REVISION_W2K3)
|
||||||
|
|
||||||
|
class AvPair(CompositeType):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236646.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.AvId = UInt16Le()
|
||||||
|
self.AvLen = UInt16Le(lambda: sizeof(self.Value))
|
||||||
|
self.Value = Buffer(read_len=lambda: self.AvLen.value)
|
||||||
|
|
||||||
|
class MessageSignatureEx(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Signature for message
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc422952.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.Version = UInt32Le(0x00000001, constant = True)
|
||||||
|
self.Checksum = Buffer(read_len=lambda: 8)
|
||||||
|
self.SeqNum = UInt32Le()
|
||||||
|
|
||||||
|
class NegotiateMessage(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Message send from client to server to negotiate capability of NTLM Authentication
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236641.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.Signature = Buffer(b"NTLMSSP\x00", read_len=lambda: 8, constant=True)
|
||||||
|
self.MessageType = UInt32Le(0x00000001, constant=True)
|
||||||
|
|
||||||
|
self.NegotiateFlags = UInt32Le()
|
||||||
|
|
||||||
|
self.DomainNameLen = UInt16Le()
|
||||||
|
self.DomainNameMaxLen = UInt16Le(lambda:self.DomainNameLen.value)
|
||||||
|
self.DomainNameBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.WorkstationLen = UInt16Le()
|
||||||
|
self.WorkstationMaxLen = UInt16Le(lambda:self.WorkstationLen.value)
|
||||||
|
self.WorkstationBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.Version = Version(conditional = lambda:(self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION))
|
||||||
|
|
||||||
|
self.Payload = Buffer()
|
||||||
|
|
||||||
|
class ChallengeMessage(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Message send from server to client contains server challenge
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236642.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.Signature = Buffer(b"NTLMSSP\x00", read_len=lambda: 8, constant=True)
|
||||||
|
self.MessageType = UInt32Le(0x00000002, constant=True)
|
||||||
|
|
||||||
|
self.TargetNameLen = UInt16Le()
|
||||||
|
self.TargetNameMaxLen = UInt16Le(lambda:self.TargetNameLen.value)
|
||||||
|
self.TargetNameBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.NegotiateFlags = UInt32Le()
|
||||||
|
|
||||||
|
self.ServerChallenge = Buffer(read_len=lambda: 8)
|
||||||
|
self.Reserved = Buffer(b"\x00" * 8, read_len=lambda: 8)
|
||||||
|
|
||||||
|
self.TargetInfoLen = UInt16Le()
|
||||||
|
self.TargetInfoMaxLen = UInt16Le(lambda: self.TargetInfoLen.value)
|
||||||
|
self.TargetInfoBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.Version = Version(conditional=lambda: (self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION))
|
||||||
|
self.Payload = Buffer()
|
||||||
|
|
||||||
|
def getTargetName(self):
|
||||||
|
return getPayLoadField(self, self.TargetNameLen.value, self.TargetNameBufferOffset.value)
|
||||||
|
|
||||||
|
def getTargetInfo(self):
|
||||||
|
return getPayLoadField(self, self.TargetInfoLen.value, self.TargetInfoBufferOffset.value)
|
||||||
|
|
||||||
|
def getTargetInfoAsAvPairArray(self):
|
||||||
|
"""
|
||||||
|
@summary: Parse Target info field to retrieve array of AvPair
|
||||||
|
@return: {map(AvId, str)}
|
||||||
|
"""
|
||||||
|
result = {}
|
||||||
|
s = Stream(self.getTargetInfo())
|
||||||
|
while(True):
|
||||||
|
avPair = AvPair()
|
||||||
|
s.read_type(avPair)
|
||||||
|
if avPair.AvId.value == AvId.MsvAvEOL:
|
||||||
|
return result
|
||||||
|
result[avPair.AvId.value] = avPair.Value.value
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticateMessage(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Last message in ntlm authentication
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236643.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.Signature = Buffer(b"NTLMSSP\x00", read_len=lambda: 8, constant = True)
|
||||||
|
self.MessageType = UInt32Le(0x00000003, constant=True)
|
||||||
|
|
||||||
|
self.LmChallengeResponseLen = UInt16Le()
|
||||||
|
self.LmChallengeResponseMaxLen = UInt16Le(lambda:self.LmChallengeResponseLen.value)
|
||||||
|
self.LmChallengeResponseBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.NtChallengeResponseLen = UInt16Le()
|
||||||
|
self.NtChallengeResponseMaxLen = UInt16Le(lambda:self.NtChallengeResponseLen.value)
|
||||||
|
self.NtChallengeResponseBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.DomainNameLen = UInt16Le()
|
||||||
|
self.DomainNameMaxLen = UInt16Le(lambda:self.DomainNameLen.value)
|
||||||
|
self.DomainNameBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.UserNameLen = UInt16Le()
|
||||||
|
self.UserNameMaxLen = UInt16Le(lambda:self.UserNameLen.value)
|
||||||
|
self.UserNameBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.WorkstationLen = UInt16Le()
|
||||||
|
self.WorkstationMaxLen = UInt16Le(lambda:self.WorkstationLen.value)
|
||||||
|
self.WorkstationBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.EncryptedRandomSessionLen = UInt16Le()
|
||||||
|
self.EncryptedRandomSessionMaxLen = UInt16Le(lambda:self.EncryptedRandomSessionLen.value)
|
||||||
|
self.EncryptedRandomSessionBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.NegotiateFlags = UInt32Le()
|
||||||
|
self.Version = Version(conditional = lambda:(self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION))
|
||||||
|
|
||||||
|
self.MIC = Buffer(b"\x00" * 16, read_len=lambda: 16)
|
||||||
|
self.Payload = Buffer()
|
||||||
|
|
||||||
|
def getUserName(self):
|
||||||
|
return getPayLoadField(self, self.UserNameLen.value, self.UserNameBufferOffset.value)
|
||||||
|
|
||||||
|
def getDomainName(self):
|
||||||
|
return getPayLoadField(self, self.DomainNameLen.value, self.DomainNameBufferOffset.value)
|
||||||
|
|
||||||
|
def getLmChallengeResponse(self):
|
||||||
|
return getPayLoadField(self, self.LmChallengeResponseLen.value, self.LmChallengeResponseBufferOffset.value)
|
||||||
|
|
||||||
|
def getNtChallengeResponse(self):
|
||||||
|
return getPayLoadField(self, self.NtChallengeResponseLen.value, self.NtChallengeResponseBufferOffset.value)
|
||||||
|
|
||||||
|
def getEncryptedRandomSession(self):
|
||||||
|
return getPayLoadField(self, self.EncryptedRandomSessionLen.value, self.EncryptedRandomSessionBufferOffset.value)
|
||||||
|
|
||||||
|
def createAuthenticationMessage(NegFlag, domain, user, NtChallengeResponse, LmChallengeResponse, EncryptedRandomSessionKey, Workstation):
|
||||||
|
"""
|
||||||
|
@summary: Create an Authenticate Message
|
||||||
|
@param domain: {str} domain microsoft
|
||||||
|
@param user: {str} user microsoft
|
||||||
|
@param NtChallengeResponse: {str} Challenge response
|
||||||
|
@param LmChallengeResponse: {str} domain microsoft
|
||||||
|
@param EncryptedRandomSessionKey: {str} EncryptedRandomSessionKey
|
||||||
|
"""
|
||||||
|
message = AuthenticateMessage()
|
||||||
|
message.NegotiateFlags.value = NegFlag
|
||||||
|
#fill message
|
||||||
|
offset = sizeof(message)
|
||||||
|
|
||||||
|
message.DomainNameLen.value = len(domain)
|
||||||
|
message.DomainNameBufferOffset.value = offset
|
||||||
|
message.Payload.value += domain
|
||||||
|
offset += len(domain)
|
||||||
|
|
||||||
|
message.UserNameLen.value = len(user)
|
||||||
|
message.UserNameBufferOffset.value = offset
|
||||||
|
message.Payload.value += user
|
||||||
|
offset += len(user)
|
||||||
|
|
||||||
|
message.WorkstationLen.value = len(Workstation)
|
||||||
|
message.WorkstationBufferOffset.value = offset
|
||||||
|
message.Payload.value += Workstation
|
||||||
|
offset += len(Workstation)
|
||||||
|
|
||||||
|
message.LmChallengeResponseLen.value = len(LmChallengeResponse)
|
||||||
|
message.LmChallengeResponseBufferOffset.value = offset
|
||||||
|
message.Payload.value += LmChallengeResponse
|
||||||
|
offset += len(LmChallengeResponse)
|
||||||
|
|
||||||
|
message.NtChallengeResponseLen.value = len(NtChallengeResponse)
|
||||||
|
message.NtChallengeResponseBufferOffset.value = offset
|
||||||
|
message.Payload.value += NtChallengeResponse
|
||||||
|
offset += len(NtChallengeResponse)
|
||||||
|
|
||||||
|
message.EncryptedRandomSessionLen.value = len(EncryptedRandomSessionKey)
|
||||||
|
message.EncryptedRandomSessionBufferOffset.value = offset
|
||||||
|
message.Payload.value += EncryptedRandomSessionKey
|
||||||
|
offset += len(EncryptedRandomSessionKey)
|
||||||
|
|
||||||
|
return message
|
||||||
|
|
||||||
|
def expandDesKey(key):
|
||||||
|
"""
|
||||||
|
@summary: Expand the key from a 7-byte password key into a 8-byte DES key
|
||||||
|
"""
|
||||||
|
s = chr(((ord(key[0]) >> 1) & 0x7f) << 1)
|
||||||
|
s = s + chr(((ord(key[0]) & 0x01) << 6 | ((ord(key[1]) >> 2) & 0x3f)) << 1)
|
||||||
|
s = s + chr(((ord(key[1]) & 0x03) << 5 | ((ord(key[2]) >> 3) & 0x1f)) << 1)
|
||||||
|
s = s + chr(((ord(key[2]) & 0x07) << 4 | ((ord(key[3]) >> 4) & 0x0f)) << 1)
|
||||||
|
s = s + chr(((ord(key[3]) & 0x0f) << 3 | ((ord(key[4]) >> 5) & 0x07)) << 1)
|
||||||
|
s = s + chr(((ord(key[4]) & 0x1f) << 2 | ((ord(key[5]) >> 6) & 0x03)) << 1)
|
||||||
|
s = s + chr(((ord(key[5]) & 0x3f) << 1 | ((ord(key[6]) >> 7) & 0x01)) << 1)
|
||||||
|
s = s + chr((ord(key[6]) & 0x7f) << 1)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def CurrentFileTimes():
|
||||||
|
"""
|
||||||
|
@summary: Current File times in 64 bits
|
||||||
|
@return : {str[8]}
|
||||||
|
"""
|
||||||
|
return struct.pack("Q", filetimes.dt_to_filetime(datetime.datetime.now()))
|
||||||
|
|
||||||
|
def DES(key, data):
|
||||||
|
"""
|
||||||
|
@summary: DES use in microsoft specification
|
||||||
|
@param key: {str} Des key on 56 bits or 7 bytes
|
||||||
|
@param data: {str} data to encrypt
|
||||||
|
"""
|
||||||
|
return pyDes.des(expandDesKey(key)).encrypt(data)
|
||||||
|
|
||||||
|
def DESL(key, data):
|
||||||
|
"""
|
||||||
|
@summary: an krosoft security function (triple des = des + des + des ;-))
|
||||||
|
@param key: {str} Des key
|
||||||
|
@param data: {str} encrypted data
|
||||||
|
"""
|
||||||
|
return DES(key[0:7], data) + DES(key[7:14], data) + DES(key[14:16] + "\x00" * 5, data)
|
||||||
|
|
||||||
|
|
||||||
|
def UNICODE(s):
|
||||||
|
"""
|
||||||
|
@param s: source
|
||||||
|
@return: {str} encoded in unicode
|
||||||
|
"""
|
||||||
|
return s.encode('utf-16le')
|
||||||
|
|
||||||
|
|
||||||
|
def MD4(s):
|
||||||
|
"""
|
||||||
|
@summary: compute the md4 sum
|
||||||
|
@param s: {str} input data
|
||||||
|
@return: {str} MD4(s)
|
||||||
|
"""
|
||||||
|
return hashlib.new('md4', s).digest()
|
||||||
|
|
||||||
|
|
||||||
|
def MD5(s):
|
||||||
|
"""
|
||||||
|
@summary: compute the md5 sum
|
||||||
|
@param s: {str} input data
|
||||||
|
@return: {str} MD5(s)
|
||||||
|
"""
|
||||||
|
return hashlib.new('md5', s).digest()
|
||||||
|
|
||||||
|
|
||||||
|
def Z(m):
|
||||||
|
"""
|
||||||
|
@summary: fill m zero in string
|
||||||
|
@param m: {int} size of string
|
||||||
|
@return: \x00 * m
|
||||||
|
"""
|
||||||
|
return b"\x00" * m
|
||||||
|
|
||||||
|
|
||||||
|
def RC4K(key, plaintext):
|
||||||
|
"""
|
||||||
|
@summary: Context free of rc4 encoding
|
||||||
|
@param key: {str} key
|
||||||
|
@param plaintext: {str} plaintext
|
||||||
|
@return {str} encrypted text
|
||||||
|
"""
|
||||||
|
return rc4.crypt(rc4.RC4Key(key), plaintext)
|
||||||
|
|
||||||
|
|
||||||
|
def KXKEYv2(SessionBaseKey, LmChallengeResponse, ServerChallenge):
|
||||||
|
"""
|
||||||
|
@summary: Key eXchange Key for NTLMv2
|
||||||
|
@param SessionBaseKey: {str} computed by NTLMv1Anthentication or NTLMv2Authenticate function
|
||||||
|
@param LmChallengeResponse : {str} computed by NTLMv1Anthentication or NTLMv2Authenticate function
|
||||||
|
@param ServerChallenge : {str} Server chanllenge come from ChallengeMessage
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236710.aspx
|
||||||
|
"""
|
||||||
|
return SessionBaseKey
|
||||||
|
|
||||||
|
|
||||||
|
def SEALKEY(ExportedSessionKey, client):
|
||||||
|
if client:
|
||||||
|
return MD5(ExportedSessionKey + b"session key to client-to-server sealing key magic constant\0")
|
||||||
|
else:
|
||||||
|
return MD5(ExportedSessionKey + b"session key to server-to-client sealing key magic constant\0")
|
||||||
|
|
||||||
|
|
||||||
|
def SIGNKEY(ExportedSessionKey, client):
|
||||||
|
if client:
|
||||||
|
return MD5(ExportedSessionKey + b"session key to client-to-server signing key magic constant\0")
|
||||||
|
else:
|
||||||
|
return MD5(ExportedSessionKey + b"session key to server-to-client signing key magic constant\0")
|
||||||
|
|
||||||
|
|
||||||
|
def HMAC_MD5(key, data):
|
||||||
|
"""
|
||||||
|
@summary: classic HMAC algorithm with MD5 sum
|
||||||
|
@param key: {str} key
|
||||||
|
@param data: {str} data
|
||||||
|
"""
|
||||||
|
return hmac.new(key, data, hashlib.md5).digest()
|
||||||
|
|
||||||
|
|
||||||
|
def NTOWFv2(Passwd, User, UserDom):
|
||||||
|
"""
|
||||||
|
@summary: Version 2 of NTLM hash function
|
||||||
|
@param Passwd: {str} Password
|
||||||
|
@param User: {str} Username
|
||||||
|
@param UserDom: {str} microsoft domain
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236700.aspx
|
||||||
|
"""
|
||||||
|
return HMAC_MD5(MD4(UNICODE(Passwd)), UNICODE(User.upper() + UserDom))
|
||||||
|
|
||||||
|
|
||||||
|
def LMOWFv2(Passwd, User, UserDom):
|
||||||
|
"""
|
||||||
|
@summary: Same as NTOWFv2
|
||||||
|
@param Passwd: {str} Password
|
||||||
|
@param User: {str} Username
|
||||||
|
@param UserDom: {str} microsoft domain
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236700.aspx
|
||||||
|
"""
|
||||||
|
return NTOWFv2(Passwd, User, UserDom)
|
||||||
|
|
||||||
|
|
||||||
|
def ComputeResponsev2(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ClientChallenge, Time, ServerName):
|
||||||
|
"""
|
||||||
|
@summary: process NTLMv2 Authenticate hash
|
||||||
|
@param NegFlg: {int} Negotiation flags come from challenge message
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236700.aspx
|
||||||
|
"""
|
||||||
|
Responserversion = b"\x01"
|
||||||
|
HiResponserversion = b"\x01"
|
||||||
|
|
||||||
|
temp = Responserversion + HiResponserversion + Z(6) + Time + ClientChallenge + Z(4) + ServerName
|
||||||
|
NTProofStr = HMAC_MD5(ResponseKeyNT, ServerChallenge + temp)
|
||||||
|
NtChallengeResponse = NTProofStr + temp
|
||||||
|
LmChallengeResponse = HMAC_MD5(ResponseKeyLM, ServerChallenge + ClientChallenge) + ClientChallenge
|
||||||
|
|
||||||
|
SessionBaseKey = HMAC_MD5(ResponseKeyNT, NTProofStr)
|
||||||
|
|
||||||
|
return NtChallengeResponse, LmChallengeResponse, SessionBaseKey
|
||||||
|
|
||||||
|
|
||||||
|
def MAC(handle, SigningKey, SeqNum, Message):
|
||||||
|
"""
|
||||||
|
@summary: generate signature for application message
|
||||||
|
@param handle: {rc4.RC4Key} handle on crypt
|
||||||
|
@param SigningKey: {str} Signing key
|
||||||
|
@param SeqNum: {int} Sequence number
|
||||||
|
@param Message: Message to sign
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc422952.aspx
|
||||||
|
"""
|
||||||
|
signature = MessageSignatureEx()
|
||||||
|
signature.SeqNum.value = SeqNum
|
||||||
|
|
||||||
|
#write the SeqNum
|
||||||
|
s = Stream()
|
||||||
|
s.write_type(signature.SeqNum)
|
||||||
|
|
||||||
|
signature.Checksum.value = rc4.crypt(handle, HMAC_MD5(SigningKey, s.getvalue() + Message)[:8])
|
||||||
|
|
||||||
|
return signature
|
||||||
|
|
||||||
|
|
||||||
|
def MIC(ExportedSessionKey, negotiateMessage, challengeMessage, authenticateMessage):
|
||||||
|
"""
|
||||||
|
@summary: Compute MIC signature
|
||||||
|
@param negotiateMessage: {NegotiateMessage}
|
||||||
|
@param challengeMessage: {ChallengeMessage}
|
||||||
|
@param authenticateMessage: {AuthenticateMessage}
|
||||||
|
@return: {str} signature
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236676.aspx
|
||||||
|
"""
|
||||||
|
s = Stream()
|
||||||
|
s.write_type((negotiateMessage, challengeMessage, authenticateMessage))
|
||||||
|
return HMAC_MD5(ExportedSessionKey, s.getvalue())
|
||||||
|
|
||||||
|
|
||||||
|
class NTLMv2(sspi.IAuthenticationProtocol):
|
||||||
|
"""
|
||||||
|
@summary: Handle NTLMv2 Authentication
|
||||||
|
"""
|
||||||
|
def __init__(self, domain: str, user: str, password: str):
|
||||||
|
self._domain = domain
|
||||||
|
self._user = user
|
||||||
|
self._password = password
|
||||||
|
self._enableUnicode = False
|
||||||
|
#https://msdn.microsoft.com/en-us/library/cc236700.aspx
|
||||||
|
self._ResponseKeyNT = NTOWFv2(password, user, domain)
|
||||||
|
self._ResponseKeyLM = LMOWFv2(password, user, domain)
|
||||||
|
|
||||||
|
#For MIC computation
|
||||||
|
self._negotiateMessage = None
|
||||||
|
self._challengeMessage = None
|
||||||
|
self._authenticateMessage = None
|
||||||
|
|
||||||
|
def getNegotiateMessage(self):
|
||||||
|
"""
|
||||||
|
@summary: generate first handshake messgae
|
||||||
|
"""
|
||||||
|
self._negotiateMessage = NegotiateMessage()
|
||||||
|
self._negotiateMessage.NegotiateFlags.value = (Negotiate.NTLMSSP_NEGOTIATE_KEY_EXCH |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_128 |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_NTLM |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_SEAL |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_SIGN |
|
||||||
|
Negotiate.NTLMSSP_REQUEST_TARGET |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_UNICODE)
|
||||||
|
return self._negotiateMessage
|
||||||
|
|
||||||
|
def getAuthenticateMessage(self, s):
|
||||||
|
"""
|
||||||
|
@summary: Client last handshake message
|
||||||
|
@param s: {Stream} challenge message stream
|
||||||
|
@return: {(AuthenticateMessage, NTLMv2SecurityInterface)} Last handshake message and security interface use to encrypt
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236676.aspx
|
||||||
|
"""
|
||||||
|
self._challengeMessage = ChallengeMessage()
|
||||||
|
s.read_type(self._challengeMessage)
|
||||||
|
|
||||||
|
ServerChallenge = self._challengeMessage.ServerChallenge.value
|
||||||
|
ClientChallenge = random(64)
|
||||||
|
|
||||||
|
computeMIC = False
|
||||||
|
ServerName = self._challengeMessage.getTargetInfo()
|
||||||
|
infos = self._challengeMessage.getTargetInfoAsAvPairArray()
|
||||||
|
if AvId.MsvAvTimestamp in infos.keys():
|
||||||
|
Timestamp = infos[AvId.MsvAvTimestamp]
|
||||||
|
computeMIC = True
|
||||||
|
else:
|
||||||
|
Timestamp = CurrentFileTimes()
|
||||||
|
|
||||||
|
|
||||||
|
NtChallengeResponse, LmChallengeResponse, SessionBaseKey = ComputeResponsev2(self._ResponseKeyNT, self._ResponseKeyLM, ServerChallenge, ClientChallenge, Timestamp, ServerName)
|
||||||
|
KeyExchangeKey = KXKEYv2(SessionBaseKey, LmChallengeResponse, ServerChallenge)
|
||||||
|
ExportedSessionKey = random(128)
|
||||||
|
EncryptedRandomSessionKey = RC4K(KeyExchangeKey, ExportedSessionKey)
|
||||||
|
|
||||||
|
domain, user = self._domain, self._user
|
||||||
|
if self._challengeMessage.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_UNICODE:
|
||||||
|
self._enableUnicode = True
|
||||||
|
domain, user = UNICODE(domain), UNICODE(user)
|
||||||
|
self._authenticateMessage = createAuthenticationMessage(self._challengeMessage.NegotiateFlags.value, domain, user, NtChallengeResponse, LmChallengeResponse, EncryptedRandomSessionKey, b"")
|
||||||
|
|
||||||
|
if computeMIC:
|
||||||
|
self._authenticateMessage.MIC.value = MIC(ExportedSessionKey, self._negotiateMessage, self._challengeMessage, self._authenticateMessage)
|
||||||
|
else:
|
||||||
|
self._authenticateMessage.MIC._conditional = lambda:False
|
||||||
|
|
||||||
|
ClientSigningKey = SIGNKEY(ExportedSessionKey, True)
|
||||||
|
ServerSigningKey = SIGNKEY(ExportedSessionKey, False)
|
||||||
|
ClientSealingKey = SEALKEY(ExportedSessionKey, True)
|
||||||
|
ServerSealingKey = SEALKEY(ExportedSessionKey, False)
|
||||||
|
|
||||||
|
interface = NTLMv2SecurityInterface(rc4.RC4Key(ClientSealingKey), rc4.RC4Key(ServerSealingKey), ClientSigningKey, ServerSigningKey)
|
||||||
|
|
||||||
|
return self._authenticateMessage, interface
|
||||||
|
|
||||||
|
def getEncodedCredentials(self):
|
||||||
|
"""
|
||||||
|
@summary: return encoded credentials accorded with authentication protocol nego
|
||||||
|
@return: (domain, username, password)
|
||||||
|
"""
|
||||||
|
if self._enableUnicode:
|
||||||
|
return UNICODE(self._domain), UNICODE(self._user), UNICODE(self._password)
|
||||||
|
else:
|
||||||
|
return self._domain, self._user, self._password
|
||||||
|
|
||||||
|
|
||||||
|
class NTLMv2SecurityInterface(sspi.IGenericSecurityService):
|
||||||
|
"""
|
||||||
|
@summary: Generic Security Service for NTLM session
|
||||||
|
"""
|
||||||
|
def __init__(self, encryptHandle, decryptHandle, signingKey, verifyKey):
|
||||||
|
"""
|
||||||
|
@param encryptHandle: {rc4.RC4Key} rc4 keystream for encrypt phase
|
||||||
|
@param decryptHandle: {rc4.RC4Key} rc4 keystream for decrypt phase
|
||||||
|
@param signingKey: {str} signingKey
|
||||||
|
@param verifyKey: {str} verifyKey
|
||||||
|
"""
|
||||||
|
self._encryptHandle = encryptHandle
|
||||||
|
self._decryptHandle = decryptHandle
|
||||||
|
self._signingKey = signingKey
|
||||||
|
self._verifyKey = verifyKey
|
||||||
|
self._seqNum = 0
|
||||||
|
|
||||||
|
def GSS_WrapEx(self, data):
|
||||||
|
"""
|
||||||
|
@summary: Encrypt function for NTLMv2 security service
|
||||||
|
@param data: data to encrypt
|
||||||
|
@return: {str} encrypted data
|
||||||
|
"""
|
||||||
|
encryptedData = rc4.crypt(self._encryptHandle, data)
|
||||||
|
signature = MAC(self._encryptHandle, self._signingKey, self._seqNum, data)
|
||||||
|
self._seqNum += 1
|
||||||
|
s = Stream()
|
||||||
|
s.write_type(signature)
|
||||||
|
return s.getvalue() + encryptedData
|
||||||
|
|
||||||
|
def GSS_UnWrapEx(self, data):
|
||||||
|
"""
|
||||||
|
@summary: decrypt data with key exchange in Authentication protocol
|
||||||
|
@param data: {str}
|
||||||
|
"""
|
||||||
|
signature, message = Stream(data).read_type((MessageSignatureEx(), Buffer()))
|
||||||
|
|
||||||
|
#decrypt message
|
||||||
|
plaintextMessage = rc4.crypt(self._decryptHandle, message.value)
|
||||||
|
checksum = rc4.crypt(self._decryptHandle, signature.Checksum.value)
|
||||||
|
|
||||||
|
# recompute checksum
|
||||||
|
t = Stream()
|
||||||
|
t.write_type(signature.SeqNum)
|
||||||
|
verify = HMAC_MD5(self._verifyKey, t.getvalue() + plaintextMessage)[:8]
|
||||||
|
if verify != checksum:
|
||||||
|
raise error.InvalidExpectedDataException("NTLMv2SecurityInterface : Invalid checksum")
|
||||||
|
|
||||||
|
return plaintextMessage
|
||||||
69
rdpy/core/nla/sspi.py
Normal file
69
rdpy/core/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.model.error import CallPureVirtualFuntion
|
||||||
|
|
||||||
|
class IAuthenticationProtocol(object):
|
||||||
|
"""
|
||||||
|
@summary: generic class for authentication Protocol (ex: ntlmv2, SPNEGO or kerberos)
|
||||||
|
"""
|
||||||
|
def getNegotiateMessage(self):
|
||||||
|
"""
|
||||||
|
@summary: Client first handshake message for authentication protocol
|
||||||
|
@return: {object} first handshake message
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getNegotiateMessage", "IAuthenticationProtocol"))
|
||||||
|
|
||||||
|
def getAuthenticateMessage(self, s):
|
||||||
|
"""
|
||||||
|
@summary: Client last handshake message
|
||||||
|
@param s: {Stream} challenge message stream
|
||||||
|
@return: {(object, IGenericSecurityService)} Last handshake message and interface for application
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getAuthenticateMessage", "IAuthenticationProtocol"))
|
||||||
|
|
||||||
|
def getEncodedCredentials(self):
|
||||||
|
"""
|
||||||
|
@summary: return encoded credentials accorded with authentication protocol nego
|
||||||
|
@return: (domain, username, password)
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getEncodedCredentials", "IAuthenticationProtocol"))
|
||||||
|
|
||||||
|
class IGenericSecurityService(object):
|
||||||
|
"""
|
||||||
|
@summary: use by application from authentification protocol
|
||||||
|
@see: http://www.rfc-editor.org/rfc/rfc2743.txt
|
||||||
|
"""
|
||||||
|
def GSS_WrapEx(self, data):
|
||||||
|
"""
|
||||||
|
@summary: encrypt data with key exchange in Authentication protocol
|
||||||
|
@param data: {str}
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "GSS_WrapEx", "IGenericSecurityService"))
|
||||||
|
|
||||||
|
def GSS_UnWrapEx(self, data):
|
||||||
|
"""
|
||||||
|
@summary: decrypt data with key exchange in Authentication protocol
|
||||||
|
@param data: {str}
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "GSS_UnWrapEx", "IGenericSecurityService"))
|
||||||
@@ -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,15 +16,16 @@
|
|||||||
# 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.core.error import InvalidExpectedDataException
|
from rdpy.model.error import InvalidExpectedDataException
|
||||||
import rdpy.core.log as log
|
import rdpy.model.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.core.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
|
from rdpy.model.type import CompositeType, CallableValue, Buffer, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
|
||||||
|
|
||||||
|
|
||||||
class CapsType(object):
|
class CapsType(object):
|
||||||
"""
|
"""
|
||||||
@@ -240,7 +241,7 @@ class Capability(CompositeType):
|
|||||||
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
|
||||||
return String(readLen = self.lengthCapability - 4)
|
return Buffer(readLen =self.lengthCapability - 4)
|
||||||
|
|
||||||
if capability is None:
|
if capability is None:
|
||||||
capability = FactoryType(CapabilityFactory)
|
capability = FactoryType(CapabilityFactory)
|
||||||
@@ -308,7 +309,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 = Buffer("\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 +317,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()
|
||||||
@@ -388,7 +389,7 @@ 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 = Buffer("\x00" * 64, readLen = CallableValue(64))
|
||||||
|
|
||||||
class BrushCapability(CompositeType):
|
class BrushCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -412,7 +413,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)
|
||||||
@@ -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,10 @@ 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.core.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
|
from rdpy.model.type import CompositeType, CallableValue, Buffer, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
|
||||||
from rdpy.core.error import InvalidExpectedDataException
|
from rdpy.model.error import InvalidExpectedDataException
|
||||||
import rdpy.core.log as log
|
import rdpy.model.log as log
|
||||||
import caps, order
|
from rdpy.core.pdu import caps, order
|
||||||
|
|
||||||
class PDUType(object):
|
class PDUType(object):
|
||||||
"""
|
"""
|
||||||
@@ -161,6 +161,15 @@ class PointerFlag(object):
|
|||||||
PTRFLAGS_BUTTON2 = 0x2000
|
PTRFLAGS_BUTTON2 = 0x2000
|
||||||
PTRFLAGS_BUTTON3 = 0x4000
|
PTRFLAGS_BUTTON3 = 0x4000
|
||||||
|
|
||||||
|
class PointerExFlag(object):
|
||||||
|
"""
|
||||||
|
@summary: Use in Pointer event
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240587.aspx
|
||||||
|
"""
|
||||||
|
PTRXFLAGS_DOWN = 0x8000
|
||||||
|
PTRXFLAGS_BUTTON1 = 0x0001
|
||||||
|
PTRXFLAGS_BUTTON2 = 0x0002
|
||||||
|
|
||||||
class KeyboardFlag(object):
|
class KeyboardFlag(object):
|
||||||
"""
|
"""
|
||||||
@summary: Use in scan code key event
|
@summary: Use in scan code key event
|
||||||
@@ -202,6 +211,16 @@ class Display(object):
|
|||||||
SUPPRESS_DISPLAY_UPDATES = 0x00
|
SUPPRESS_DISPLAY_UPDATES = 0x00
|
||||||
ALLOW_DISPLAY_UPDATES = 0x01
|
ALLOW_DISPLAY_UPDATES = 0x01
|
||||||
|
|
||||||
|
class ToogleFlag(object):
|
||||||
|
"""
|
||||||
|
@summary: Use to known state of keyboard
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240588.aspx
|
||||||
|
"""
|
||||||
|
TS_SYNC_SCROLL_LOCK = 0x00000001
|
||||||
|
TS_SYNC_NUM_LOCK = 0x00000002
|
||||||
|
TS_SYNC_CAPS_LOCK = 0x00000004
|
||||||
|
TS_SYNC_KANA_LOCK = 0x00000008
|
||||||
|
|
||||||
class ErrorInfo(object):
|
class ErrorInfo(object):
|
||||||
"""
|
"""
|
||||||
@summary: Error code use in Error info PDU
|
@summary: Error code use in Error info PDU
|
||||||
@@ -413,8 +432,6 @@ class ErrorInfo(object):
|
|||||||
ERRINFO_VCDATATOOLONG : "The size of a received Virtual Channel PDU (section 2.2.6.1) exceeds the chunking size specified in the Virtual Channel Capability Set (section 2.2.7.1.10).",
|
ERRINFO_VCDATATOOLONG : "The size of a received Virtual Channel PDU (section 2.2.6.1) exceeds the chunking size specified in the Virtual Channel Capability Set (section 2.2.7.1.10).",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ShareControlHeader(CompositeType):
|
class ShareControlHeader(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: PDU share control header
|
@summary: PDU share control header
|
||||||
@@ -429,7 +446,8 @@ class ShareControlHeader(CompositeType):
|
|||||||
#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):
|
||||||
"""
|
"""
|
||||||
@@ -460,10 +478,10 @@ class PDU(CompositeType):
|
|||||||
"""
|
"""
|
||||||
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
|
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
|
||||||
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
|
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
|
||||||
return c()
|
return c(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
|
||||||
log.debug("unknown PDU type : %s"%hex(self.shareControlHeader.pduType.value))
|
log.debug("unknown PDU type : %s"%hex(self.shareControlHeader.pduType.value))
|
||||||
#read entire packet
|
#read entire packet
|
||||||
return String()
|
return Buffer(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
|
||||||
|
|
||||||
if pduMessage is None:
|
if pduMessage is None:
|
||||||
pduMessage = FactoryType(PDUMessageFactory)
|
pduMessage = FactoryType(PDUMessageFactory)
|
||||||
@@ -480,12 +498,12 @@ class DemandActivePDU(CompositeType):
|
|||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.shareId = UInt32Le()
|
self.shareId = UInt32Le()
|
||||||
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
||||||
self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets)))
|
self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets)))
|
||||||
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
|
self.sourceDescriptor = Buffer("rdpy", readLen = self.lengthSourceDescriptor)
|
||||||
self.numberCapabilities = UInt16Le(lambda:len(self.capabilitySets._array))
|
self.numberCapabilities = UInt16Le(lambda:len(self.capabilitySets._array))
|
||||||
self.pad2Octets = UInt16Le()
|
self.pad2Octets = UInt16Le()
|
||||||
self.capabilitySets = ArrayType(caps.Capability, readLen = self.numberCapabilities)
|
self.capabilitySets = ArrayType(caps.Capability, readLen = self.numberCapabilities)
|
||||||
@@ -499,13 +517,13 @@ class ConfirmActivePDU(CompositeType):
|
|||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.shareId = UInt32Le()
|
self.shareId = UInt32Le()
|
||||||
self.originatorId = UInt16Le(0x03EA, constant = True)
|
self.originatorId = UInt16Le(0x03EA, constant = True)
|
||||||
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
||||||
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)))
|
||||||
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
|
self.sourceDescriptor = Buffer("rdpy", readLen = self.lengthSourceDescriptor)
|
||||||
self.numberCapabilities = UInt16Le(lambda:len(self.capabilitySets._array))
|
self.numberCapabilities = UInt16Le(lambda:len(self.capabilitySets._array))
|
||||||
self.pad2Octets = UInt16Le()
|
self.pad2Octets = UInt16Le()
|
||||||
self.capabilitySets = ArrayType(caps.Capability, readLen = self.numberCapabilities)
|
self.capabilitySets = ArrayType(caps.Capability, readLen = self.numberCapabilities)
|
||||||
@@ -518,11 +536,13 @@ class DeactiveAllPDU(CompositeType):
|
|||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_DEACTIVATEALLPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_DEACTIVATEALLPDU
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
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 = Buffer("rdpy", readLen = self.lengthSourceDescriptor)
|
||||||
|
|
||||||
class DataPDU(CompositeType):
|
class DataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -531,19 +551,19 @@ class DataPDU(CompositeType):
|
|||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
|
||||||
|
|
||||||
def __init__(self, pduData = None, shareId = 0):
|
def __init__(self, pduData = None, shareId = 0, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), lambda:self.pduData.__class__._PDUTYPE2_, shareId)
|
self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), lambda:self.pduData.__class__._PDUTYPE2_, shareId)
|
||||||
|
|
||||||
def PDUDataFactory():
|
def PDUDataFactory():
|
||||||
"""
|
"""
|
||||||
@summary: Create object in accordance self.shareDataHeader.pduType2 value
|
@summary: Create object in accordance self.shareDataHeader.pduType2 value
|
||||||
"""
|
"""
|
||||||
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU]:
|
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU, SaveSessionInfoPDU]:
|
||||||
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
|
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
|
||||||
return c()
|
return c(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
|
||||||
log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value))
|
log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value))
|
||||||
return String()
|
return Buffer(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
|
||||||
|
|
||||||
if pduData is None:
|
if pduData is None:
|
||||||
pduData = FactoryType(PDUDataFactory)
|
pduData = FactoryType(PDUDataFactory)
|
||||||
@@ -652,8 +672,8 @@ class PersistentListPDU(CompositeType):
|
|||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST
|
||||||
|
|
||||||
def __init__(self, userId = 0, shareId = 0):
|
def __init__(self, userId = 0, shareId = 0, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.numEntriesCache0 = UInt16Le()
|
self.numEntriesCache0 = UInt16Le()
|
||||||
self.numEntriesCache1 = UInt16Le()
|
self.numEntriesCache1 = UInt16Le()
|
||||||
self.numEntriesCache2 = UInt16Le()
|
self.numEntriesCache2 = UInt16Le()
|
||||||
@@ -667,7 +687,7 @@ 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):
|
||||||
"""
|
"""
|
||||||
@@ -676,8 +696,8 @@ class ClientInputEventPDU(CompositeType):
|
|||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.numEvents = UInt16Le(lambda:len(self.slowPathInputEvents._array))
|
self.numEvents = UInt16Le(lambda:len(self.slowPathInputEvents._array))
|
||||||
self.pad2Octets = UInt16Le()
|
self.pad2Octets = UInt16Le()
|
||||||
self.slowPathInputEvents = ArrayType(SlowPathInputEvent, readLen = self.numEvents)
|
self.slowPathInputEvents = ArrayType(SlowPathInputEvent, readLen = self.numEvents)
|
||||||
@@ -688,8 +708,8 @@ class ShutdownRequestPDU(CompositeType):
|
|||||||
client -> server
|
client -> server
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
|
||||||
class ShutdownDeniedPDU(CompositeType):
|
class ShutdownDeniedPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -697,8 +717,8 @@ class ShutdownDeniedPDU(CompositeType):
|
|||||||
server -> client
|
server -> client
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
|
||||||
class InclusiveRectangle(CompositeType):
|
class InclusiveRectangle(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -758,9 +778,9 @@ class UpdateDataPDU(CompositeType):
|
|||||||
"""
|
"""
|
||||||
for c in [BitmapUpdateDataPDU]:
|
for c in [BitmapUpdateDataPDU]:
|
||||||
if self.updateType.value == c._UPDATE_TYPE_:
|
if self.updateType.value == c._UPDATE_TYPE_:
|
||||||
return c()
|
return c(readLen = CallableValue(readLen.value - 2))
|
||||||
log.debug("unknown PDU update data type : %s"%hex(self.updateType.value))
|
log.debug("unknown PDU update data type : %s"%hex(self.updateType.value))
|
||||||
return String()
|
return Buffer(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))
|
||||||
@@ -769,6 +789,18 @@ class UpdateDataPDU(CompositeType):
|
|||||||
|
|
||||||
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 = Buffer()
|
||||||
|
|
||||||
class FastPathUpdatePDU(CompositeType):
|
class FastPathUpdatePDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Fast path update PDU packet
|
@summary: Fast path update PDU packet
|
||||||
@@ -786,9 +818,9 @@ class FastPathUpdatePDU(CompositeType):
|
|||||||
"""
|
"""
|
||||||
for c in [FastPathBitmapUpdateDataPDU]:
|
for c in [FastPathBitmapUpdateDataPDU]:
|
||||||
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
|
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
|
||||||
return c()
|
return c(readLen = self.size)
|
||||||
log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf))
|
log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf))
|
||||||
return String()
|
return Buffer(readLen = self.size)
|
||||||
|
|
||||||
if updateData is None:
|
if updateData is None:
|
||||||
updateData = FactoryType(UpdateDataFactory)
|
updateData = FactoryType(UpdateDataFactory)
|
||||||
@@ -818,8 +850,8 @@ class OrderUpdateDataPDU(CompositeType):
|
|||||||
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
|
||||||
@todo: not implemented yet but need it
|
@todo: not implemented yet but need it
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.pad2OctetsA = UInt16Le()
|
self.pad2OctetsA = UInt16Le()
|
||||||
self.numberOrders = UInt16Le(lambda:len(self.orderData._array))
|
self.numberOrders = UInt16Le(lambda:len(self.orderData._array))
|
||||||
self.pad2OctetsB = UInt16Le()
|
self.pad2OctetsB = UInt16Le()
|
||||||
@@ -870,7 +902,7 @@ 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 = Buffer(bitmapDataStream, readLen = CallableValue(lambda:(self.bitmapLength.value if (not self.flags.value & BitmapFlag.BITMAP_COMPRESSION or self.flags.value & BitmapFlag.NO_BITMAP_COMPRESSION_HDR) else self.bitmapComprHdr.cbCompMainBodySize.value)))
|
||||||
|
|
||||||
class FastPathBitmapUpdateDataPDU(CompositeType):
|
class FastPathBitmapUpdateDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -879,8 +911,8 @@ class FastPathBitmapUpdateDataPDU(CompositeType):
|
|||||||
"""
|
"""
|
||||||
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
|
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.header = UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True)
|
self.header = UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True)
|
||||||
self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array))
|
self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array))
|
||||||
self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles)
|
self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles)
|
||||||
@@ -896,11 +928,10 @@ class SlowPathInputEvent(CompositeType):
|
|||||||
self.messageType = UInt16Le(lambda:self.slowPathInputData.__class__._INPUT_MESSAGE_TYPE_)
|
self.messageType = UInt16Le(lambda:self.slowPathInputData.__class__._INPUT_MESSAGE_TYPE_)
|
||||||
|
|
||||||
def SlowPathInputDataFactory():
|
def SlowPathInputDataFactory():
|
||||||
for c in [PointerEvent, ScancodeKeyEvent, UnicodeKeyEvent]:
|
for c in [PointerEvent, PointerExEvent, ScancodeKeyEvent, UnicodeKeyEvent, SynchronizeEvent]:
|
||||||
if self.messageType.value == c._INPUT_MESSAGE_TYPE_:
|
if self.messageType.value == c._INPUT_MESSAGE_TYPE_:
|
||||||
return c()
|
return c()
|
||||||
log.debug("unknown slow path input : %s"%hex(self.messageType.value))
|
raise InvalidExpectedDataException("unknown slow path input : %s"%hex(self.messageType.value))
|
||||||
return String()
|
|
||||||
|
|
||||||
if messageData is None:
|
if messageData is None:
|
||||||
messageData = FactoryType(SlowPathInputDataFactory)
|
messageData = FactoryType(SlowPathInputDataFactory)
|
||||||
@@ -909,6 +940,18 @@ class SlowPathInputEvent(CompositeType):
|
|||||||
|
|
||||||
self.slowPathInputData = messageData
|
self.slowPathInputData = messageData
|
||||||
|
|
||||||
|
class SynchronizeEvent(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Synchronize keyboard
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240588.aspx
|
||||||
|
"""
|
||||||
|
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SYNC
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.pad2Octets = UInt16Le()
|
||||||
|
self.toggleFlags = UInt32Le()
|
||||||
|
|
||||||
class PointerEvent(CompositeType):
|
class PointerEvent(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Event use to communicate mouse position
|
@summary: Event use to communicate mouse position
|
||||||
@@ -922,6 +965,19 @@ class PointerEvent(CompositeType):
|
|||||||
self.xPos = UInt16Le()
|
self.xPos = UInt16Le()
|
||||||
self.yPos = UInt16Le()
|
self.yPos = UInt16Le()
|
||||||
|
|
||||||
|
class PointerExEvent(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Event use to communicate mouse position
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240587.aspx
|
||||||
|
"""
|
||||||
|
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_MOUSEX
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.pointerFlags = UInt16Le()
|
||||||
|
self.xPos = UInt16Le()
|
||||||
|
self.yPos = UInt16Le()
|
||||||
|
|
||||||
class ScancodeKeyEvent(CompositeType):
|
class ScancodeKeyEvent(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Event use to communicate keyboard informations
|
@summary: Event use to communicate keyboard informations
|
||||||
@@ -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,11 +23,13 @@ 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.core.layer import LayerAutomata
|
from rdpy.model.layer import LayerAutomata
|
||||||
from rdpy.core.error import CallPureVirtualFuntion
|
from rdpy.model.error import CallPureVirtualFuntion
|
||||||
import rdpy.core.log as log
|
from rdpy.model.type import ArrayType
|
||||||
import rdpy.protocol.rdp.tpkt as tpkt
|
import rdpy.model.log as log
|
||||||
import data, caps
|
from rdpy.core import tpkt
|
||||||
|
from rdpy.core.pdu import data, caps
|
||||||
|
|
||||||
|
|
||||||
class PDUClientListener(object):
|
class PDUClientListener(object):
|
||||||
"""
|
"""
|
||||||
@@ -39,6 +41,13 @@ class PDUClientListener(object):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener"))
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Event call when Windows session is ready
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "PDUClientListener"))
|
||||||
|
|
||||||
|
|
||||||
def onUpdate(self, rectangles):
|
def onUpdate(self, rectangles):
|
||||||
"""
|
"""
|
||||||
@summary: call when a bitmap data is received from update PDU
|
@summary: call when a bitmap data is received from update PDU
|
||||||
@@ -167,7 +176,7 @@ class Client(PDULayer):
|
|||||||
@param s: Stream
|
@param s: Stream
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdu = data.PDU()
|
||||||
s.readType(pdu)
|
s.read_type(pdu)
|
||||||
|
|
||||||
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DEMANDACTIVEPDU:
|
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DEMANDACTIVEPDU:
|
||||||
#not a blocking error because in deactive reactive sequence
|
#not a blocking error because in deactive reactive sequence
|
||||||
@@ -180,6 +189,9 @@ class Client(PDULayer):
|
|||||||
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
|
||||||
self.sendClientFinalizeSynchronizePDU()
|
self.sendClientFinalizeSynchronizePDU()
|
||||||
@@ -192,7 +204,7 @@ class Client(PDULayer):
|
|||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdu = data.PDU()
|
||||||
s.readType(pdu)
|
s.read_type(pdu)
|
||||||
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_SYNCHRONIZE:
|
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_SYNCHRONIZE:
|
||||||
#not a blocking error because in deactive reactive sequence
|
#not a blocking error because in deactive reactive sequence
|
||||||
#input can be send too but ignored
|
#input can be send too but ignored
|
||||||
@@ -208,7 +220,7 @@ class Client(PDULayer):
|
|||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdu = data.PDU()
|
||||||
s.readType(pdu)
|
s.read_type(pdu)
|
||||||
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_COOPERATE:
|
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_COOPERATE:
|
||||||
#not a blocking error because in deactive reactive sequence
|
#not a blocking error because in deactive reactive sequence
|
||||||
#input can be send too but ignored
|
#input can be send too but ignored
|
||||||
@@ -224,7 +236,7 @@ class Client(PDULayer):
|
|||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdu = data.PDU()
|
||||||
s.readType(pdu)
|
s.read_type(pdu)
|
||||||
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_GRANTED_CONTROL:
|
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_GRANTED_CONTROL:
|
||||||
#not a blocking error because in deactive reactive sequence
|
#not a blocking error because in deactive reactive sequence
|
||||||
#input can be send too but ignored
|
#input can be send too but ignored
|
||||||
@@ -240,7 +252,7 @@ class Client(PDULayer):
|
|||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdu = data.PDU()
|
||||||
s.readType(pdu)
|
s.read_type(pdu)
|
||||||
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_FONTMAP:
|
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_FONTMAP:
|
||||||
#not a blocking error because in deactive reactive sequence
|
#not a blocking error because in deactive reactive sequence
|
||||||
#input can be send too but ignored
|
#input can be send too but ignored
|
||||||
@@ -256,8 +268,9 @@ class Client(PDULayer):
|
|||||||
@summary: Main receive function after connection sequence
|
@summary: Main receive function after connection sequence
|
||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdus = ArrayType(data.PDU)
|
||||||
s.readType(pdu)
|
s.read_type(pdus)
|
||||||
|
for pdu in pdus:
|
||||||
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
|
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
|
||||||
self.readDataPDU(pdu.pduMessage)
|
self.readDataPDU(pdu.pduMessage)
|
||||||
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
|
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
|
||||||
@@ -273,10 +286,11 @@ class Client(PDULayer):
|
|||||||
@param fastPathS: {Stream} that contain fast path data
|
@param fastPathS: {Stream} that contain fast path data
|
||||||
@param secFlag: {SecFlags}
|
@param secFlag: {SecFlags}
|
||||||
"""
|
"""
|
||||||
fastPathPDU = data.FastPathUpdatePDU()
|
updates = ArrayType(data.FastPathUpdatePDU)
|
||||||
fastPathS.readType(fastPathPDU)
|
fastPathS.read_type(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):
|
||||||
"""
|
"""
|
||||||
@@ -284,14 +298,20 @@ class Client(PDULayer):
|
|||||||
@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)
|
||||||
|
|
||||||
@@ -312,7 +332,7 @@ class Client(PDULayer):
|
|||||||
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
|
||||||
|
|
||||||
@@ -399,7 +419,7 @@ class Server(PDULayer):
|
|||||||
@param s: Stream
|
@param s: Stream
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdu = data.PDU()
|
||||||
s.readType(pdu)
|
s.read_type(pdu)
|
||||||
|
|
||||||
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_CONFIRMACTIVEPDU:
|
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_CONFIRMACTIVEPDU:
|
||||||
#not a blocking error because in deactive reactive sequence
|
#not a blocking error because in deactive reactive sequence
|
||||||
@@ -413,6 +433,9 @@ class Server(PDULayer):
|
|||||||
#find use full flag
|
#find use full flag
|
||||||
self._clientFastPathSupported = bool(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):
|
||||||
@@ -422,7 +445,7 @@ class Server(PDULayer):
|
|||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdu = data.PDU()
|
||||||
s.readType(pdu)
|
s.read_type(pdu)
|
||||||
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_SYNCHRONIZE:
|
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_SYNCHRONIZE:
|
||||||
#not a blocking error because in deactive reactive sequence
|
#not a blocking error because in deactive reactive sequence
|
||||||
#input can be send too but ignored
|
#input can be send too but ignored
|
||||||
@@ -437,7 +460,7 @@ class Server(PDULayer):
|
|||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdu = data.PDU()
|
||||||
s.readType(pdu)
|
s.read_type(pdu)
|
||||||
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_COOPERATE:
|
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_COOPERATE:
|
||||||
#not a blocking error because in deactive reactive sequence
|
#not a blocking error because in deactive reactive sequence
|
||||||
#input can be send too but ignored
|
#input can be send too but ignored
|
||||||
@@ -452,7 +475,7 @@ class Server(PDULayer):
|
|||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdu = data.PDU()
|
||||||
s.readType(pdu)
|
s.read_type(pdu)
|
||||||
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_REQUEST_CONTROL:
|
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_REQUEST_CONTROL:
|
||||||
#not a blocking error because in deactive reactive sequence
|
#not a blocking error because in deactive reactive sequence
|
||||||
#input can be send too but ignored
|
#input can be send too but ignored
|
||||||
@@ -468,7 +491,7 @@ class Server(PDULayer):
|
|||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdu = data.PDU()
|
||||||
s.readType(pdu)
|
s.read_type(pdu)
|
||||||
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_FONTLIST:
|
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_FONTLIST:
|
||||||
#not a blocking error because in deactive reactive sequence
|
#not a blocking error because in deactive reactive sequence
|
||||||
#input can be send but ignored
|
#input can be send but ignored
|
||||||
@@ -487,7 +510,7 @@ class Server(PDULayer):
|
|||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdu = data.PDU()
|
||||||
s.readType(pdu)
|
s.read_type(pdu)
|
||||||
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
|
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
|
||||||
self.readDataPDU(pdu.pduMessage)
|
self.readDataPDU(pdu.pduMessage)
|
||||||
|
|
||||||
@@ -525,7 +548,7 @@ class Server(PDULayer):
|
|||||||
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 | caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED
|
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED | caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM
|
||||||
|
|
||||||
inputCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability
|
inputCapability = 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
|
||||||
@@ -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.core import log
|
from rdpy.model import log
|
||||||
from rdpy.core.error import InvalidExpectedDataException
|
from rdpy.model.error import InvalidExpectedDataException
|
||||||
from rdpy.core.type import CompositeType, UInt8, String, FactoryType, SInt8, SInt16Le
|
from rdpy.model.type import CompositeType, UInt8, Buffer, FactoryType, SInt8, SInt16Le
|
||||||
|
|
||||||
class ControlFlag(object):
|
class ControlFlag(object):
|
||||||
"""
|
"""
|
||||||
@@ -100,7 +100,7 @@ class PrimaryDrawingOrder(CompositeType):
|
|||||||
return c(self.controlFlags)
|
return c(self.controlFlags)
|
||||||
log.debug("unknown Order type : %s"%hex(self.orderType.value))
|
log.debug("unknown Order type : %s"%hex(self.orderType.value))
|
||||||
#read entire packet
|
#read entire packet
|
||||||
return String()
|
return Buffer()
|
||||||
|
|
||||||
if order is None:
|
if order is None:
|
||||||
order = FactoryType(OrderFactory)
|
order = FactoryType(OrderFactory)
|
||||||
@@ -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,15 +21,30 @@
|
|||||||
Use to manage RDP stack in twisted
|
Use to manage RDP stack in twisted
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.core import layer
|
from rdpy.model import layer
|
||||||
from rdpy.core.error import CallPureVirtualFuntion, InvalidValue
|
from rdpy.model.error import CallPureVirtualFuntion, InvalidValue
|
||||||
import pdu.layer
|
from rdpy.core.pdu.layer import PDUClientListener, PDUServerListener
|
||||||
import pdu.data
|
from rdpy.core.pdu import data
|
||||||
import pdu.caps
|
from rdpy.core.pdu import caps
|
||||||
import rdpy.core.log as log
|
from rdpy.core.pdu import layer as pdu
|
||||||
import tpkt, x224, mcs, gcc, sec
|
import rdpy.model.log as log
|
||||||
|
import rdpy.core.tpkt as tpkt
|
||||||
|
import rdpy.core.x224 as x224
|
||||||
|
import rdpy.core.sec as sec
|
||||||
|
from rdpy.core.t125 import mcs, gcc
|
||||||
|
from rdpy.core.nla import cssp, ntlm
|
||||||
|
|
||||||
class RDPClientController(pdu.layer.PDUClientListener):
|
|
||||||
|
class SecurityLevel(object):
|
||||||
|
"""
|
||||||
|
@summary: RDP security level
|
||||||
|
"""
|
||||||
|
RDP_LEVEL_RDP = 0
|
||||||
|
RDP_LEVEL_SSL = 1
|
||||||
|
RDP_LEVEL_NLA = 2
|
||||||
|
|
||||||
|
|
||||||
|
class RDPClientController(PDUClientListener):
|
||||||
"""
|
"""
|
||||||
Manage RDP stack as client
|
Manage RDP stack as client
|
||||||
"""
|
"""
|
||||||
@@ -37,7 +52,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
#list of observer
|
#list of observer
|
||||||
self._clientObserver = []
|
self._clientObserver = []
|
||||||
#PDU layer
|
#PDU layer
|
||||||
self._pduLayer = pdu.layer.Client(self)
|
self._pduLayer = pdu.Client(self)
|
||||||
#secure layer
|
#secure layer
|
||||||
self._secLayer = sec.Client(self._pduLayer)
|
self._secLayer = sec.Client(self._pduLayer)
|
||||||
#multi channel service
|
#multi channel service
|
||||||
@@ -57,7 +72,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):
|
||||||
"""
|
"""
|
||||||
@@ -84,13 +99,13 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
@param height: height in pixel of screen
|
@param height: height in pixel of screen
|
||||||
"""
|
"""
|
||||||
#set screen definition in MCS layer
|
#set screen definition in MCS layer
|
||||||
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).desktopHeight.value = height
|
self._mcsLayer._clientSettings.get_block(gcc.MessageType.CS_CORE).desktopHeight.value = height
|
||||||
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).desktopWidth.value = width
|
self._mcsLayer._clientSettings.get_block(gcc.MessageType.CS_CORE).desktopWidth.value = width
|
||||||
|
|
||||||
def setUsername(self, username):
|
def setUsername(self, username):
|
||||||
"""
|
"""
|
||||||
@summary: Set the username for session
|
@summary: Set the username for session
|
||||||
@param username: username of session
|
@param username: {string} username of session
|
||||||
"""
|
"""
|
||||||
#username in PDU info packet
|
#username in PDU info packet
|
||||||
self._secLayer._info.userName.value = username
|
self._secLayer._info.userName.value = username
|
||||||
@@ -99,7 +114,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
def setPassword(self, password):
|
def setPassword(self, password):
|
||||||
"""
|
"""
|
||||||
@summary: Set password for session
|
@summary: Set password for session
|
||||||
@param password: password of session
|
@param password: {string} password of session
|
||||||
"""
|
"""
|
||||||
self.setAutologon()
|
self.setAutologon()
|
||||||
self._secLayer._info.password.value = password
|
self._secLayer._info.password.value = password
|
||||||
@@ -107,7 +122,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
def setDomain(self, domain):
|
def setDomain(self, domain):
|
||||||
"""
|
"""
|
||||||
@summary: Set the windows domain of session
|
@summary: Set the windows domain of session
|
||||||
@param domain: domain of session
|
@param domain: {string} domain of session
|
||||||
"""
|
"""
|
||||||
self._secLayer._info.domain.value = domain
|
self._secLayer._info.domain.value = domain
|
||||||
|
|
||||||
@@ -117,6 +132,13 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
"""
|
"""
|
||||||
self._secLayer._info.flag |= sec.InfoFlag.INFO_AUTOLOGON
|
self._secLayer._info.flag |= sec.InfoFlag.INFO_AUTOLOGON
|
||||||
|
|
||||||
|
def setAlternateShell(self, appName):
|
||||||
|
"""
|
||||||
|
@summary: set application name of app which start at the begining of session
|
||||||
|
@param appName: {string} application name
|
||||||
|
"""
|
||||||
|
self._secLayer._info.alternateShell.value = appName
|
||||||
|
|
||||||
def setKeyboardLayout(self, layout):
|
def setKeyboardLayout(self, layout):
|
||||||
"""
|
"""
|
||||||
@summary: keyboard layout
|
@summary: keyboard layout
|
||||||
@@ -137,12 +159,14 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
def setSecurityLevel(self, level):
|
def setSecurityLevel(self, level):
|
||||||
"""
|
"""
|
||||||
@summary: Request basic security
|
@summary: Request basic security
|
||||||
@param level: {str} (ssl | rdp)
|
@param level: {SecurityLevel}
|
||||||
"""
|
"""
|
||||||
if level == "rdp":
|
if level == SecurityLevel.RDP_LEVEL_RDP:
|
||||||
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP
|
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP
|
||||||
elif level == "ssl":
|
elif level == SecurityLevel.RDP_LEVEL_SSL:
|
||||||
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_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):
|
||||||
"""
|
"""
|
||||||
@@ -180,6 +204,15 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
for observer in self._clientObserver:
|
for observer in self._clientObserver:
|
||||||
observer.onReady()
|
observer.onReady()
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Call when Windows session is ready (connected)
|
||||||
|
"""
|
||||||
|
self._isReady = True
|
||||||
|
#signal all listener
|
||||||
|
for observer in self._clientObserver:
|
||||||
|
observer.onSessionReady()
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: Event call when RDP stack is closed
|
@summary: Event call when RDP stack is closed
|
||||||
@@ -200,6 +233,17 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if button == 4 or button == 5:
|
||||||
|
event = pdu.data.PointerExEvent()
|
||||||
|
if isPressed:
|
||||||
|
event.pointerFlags.value |= pdu.data.PointerExFlag.PTRXFLAGS_DOWN
|
||||||
|
|
||||||
|
if button == 4:
|
||||||
|
event.pointerFlags.value |= pdu.data.PointerExFlag.PTRXFLAGS_BUTTON1
|
||||||
|
elif button == 5:
|
||||||
|
event.pointerFlags.value |= pdu.data.PointerExFlag.PTRXFLAGS_BUTTON2
|
||||||
|
|
||||||
|
else:
|
||||||
event = pdu.data.PointerEvent()
|
event = pdu.data.PointerEvent()
|
||||||
if isPressed:
|
if isPressed:
|
||||||
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN
|
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN
|
||||||
@@ -257,11 +301,12 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
except InvalidValue:
|
except InvalidValue:
|
||||||
log.info("try send wheel event with incorrect position")
|
log.info("try send wheel event with incorrect position")
|
||||||
|
|
||||||
def sendKeyEventScancode(self, code, isPressed):
|
def sendKeyEventScancode(self, code, isPressed, extended = False):
|
||||||
"""
|
"""
|
||||||
@summary: Send a scan code to RDP stack
|
@summary: Send a scan code to RDP stack
|
||||||
@param code: scan code
|
@param code: scan code
|
||||||
@param isPressed: True if key is pressed and false if it's released
|
@param isPressed: True if key is pressed and false if it's released
|
||||||
|
@param extended: {boolean} extended scancode like ctr or win button
|
||||||
"""
|
"""
|
||||||
if not self._isReady:
|
if not self._isReady:
|
||||||
return
|
return
|
||||||
@@ -269,11 +314,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])
|
||||||
|
|
||||||
@@ -324,7 +370,8 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
"""
|
"""
|
||||||
self._pduLayer.close()
|
self._pduLayer.close()
|
||||||
|
|
||||||
class RDPServerController(pdu.layer.PDUServerListener):
|
|
||||||
|
class RDPServerController(PDUServerListener):
|
||||||
"""
|
"""
|
||||||
@summary: Controller use in server side mode
|
@summary: Controller use in server side mode
|
||||||
"""
|
"""
|
||||||
@@ -347,6 +394,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
self._x224Layer = x224.Server(self._mcsLayer, privateKeyFileName, certificateFileName, False)
|
self._x224Layer = x224.Server(self._mcsLayer, privateKeyFileName, certificateFileName, False)
|
||||||
#transport packet (protocol layer)
|
#transport packet (protocol layer)
|
||||||
self._tpktLayer = tpkt.TPKT(self._x224Layer)
|
self._tpktLayer = tpkt.TPKT(self._x224Layer)
|
||||||
|
|
||||||
#fastpath stack
|
#fastpath stack
|
||||||
self._pduLayer.initFastPath(self._secLayer)
|
self._pduLayer.initFastPath(self._secLayer)
|
||||||
self._secLayer.initFastPath(self._tpktLayer)
|
self._secLayer.initFastPath(self._tpktLayer)
|
||||||
@@ -465,11 +513,11 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
for event in slowPathInputEvents:
|
for event in slowPathInputEvents:
|
||||||
#scan code
|
#scan code
|
||||||
if event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_SCANCODE:
|
if event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_SCANCODE:
|
||||||
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
|
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE), bool(event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED))
|
||||||
#unicode
|
#unicode
|
||||||
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_UNICODE:
|
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_UNICODE:
|
||||||
observer.onKeyEventUnicode(event.slowPathInputData.unicode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
|
observer.onKeyEventUnicode(event.slowPathInputData.unicode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
|
||||||
#mouse event
|
#mouse events
|
||||||
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSE:
|
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSE:
|
||||||
isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_DOWN
|
isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_DOWN
|
||||||
button = 0
|
button = 0
|
||||||
@@ -480,6 +528,15 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_BUTTON3:
|
elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_BUTTON3:
|
||||||
button = 3
|
button = 3
|
||||||
observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed)
|
observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed)
|
||||||
|
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSEX:
|
||||||
|
isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_DOWN
|
||||||
|
button = 0
|
||||||
|
if event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_BUTTON1:
|
||||||
|
button = 4
|
||||||
|
elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_BUTTON2:
|
||||||
|
button = 5
|
||||||
|
observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed)
|
||||||
|
|
||||||
|
|
||||||
def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||||
"""
|
"""
|
||||||
@@ -507,8 +564,9 @@ class ClientFactory(layer.RawLayerClientFactory):
|
|||||||
@summary: Factory of Client RDP protocol
|
@summary: Factory of Client RDP protocol
|
||||||
@param reason: twisted reason
|
@param reason: twisted reason
|
||||||
"""
|
"""
|
||||||
def connectionLost(self, tpktLayer, reason):
|
def connectionLost(self, csspLayer, reason):
|
||||||
#retrieve controller
|
#retrieve controller
|
||||||
|
tpktLayer = csspLayer._layer
|
||||||
x224Layer = tpktLayer._presentation
|
x224Layer = tpktLayer._presentation
|
||||||
mcsLayer = x224Layer._presentation
|
mcsLayer = x224Layer._presentation
|
||||||
secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
|
secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
|
||||||
@@ -533,49 +591,50 @@ class ClientFactory(layer.RawLayerClientFactory):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildObserver", "ClientFactory"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildObserver", "ClientFactory"))
|
||||||
|
|
||||||
class ServerFactory(layer.RawLayerServerFactory):
|
|
||||||
"""
|
|
||||||
@summary: Factory of Server RDP protocol
|
|
||||||
"""
|
|
||||||
def __init__(self, colorDepth, privateKeyFileName = None, certificateFileName = None):
|
|
||||||
"""
|
|
||||||
@param colorDepth: color depth of session
|
|
||||||
@param privateKeyFileName: file contain server private key (if none -> back to standard RDP security)
|
|
||||||
@param certficiateFileName: file that contain public key (if none -> back to standard RDP security)
|
|
||||||
"""
|
|
||||||
self._colorDepth = colorDepth
|
|
||||||
self._privateKeyFileName = privateKeyFileName
|
|
||||||
self._certificateFileName = certificateFileName
|
|
||||||
|
|
||||||
def connectionLost(self, tpktLayer, reason):
|
|
||||||
"""
|
|
||||||
@param reason: twisted reason
|
|
||||||
"""
|
|
||||||
#retrieve controller
|
|
||||||
x224Layer = tpktLayer._presentation
|
|
||||||
mcsLayer = x224Layer._presentation
|
|
||||||
secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
|
|
||||||
pduLayer = secLayer._presentation
|
|
||||||
controller = pduLayer._listener
|
|
||||||
controller.onClose()
|
|
||||||
|
|
||||||
def buildRawLayer(self, addr):
|
|
||||||
"""
|
|
||||||
@summary: Function call from twisted and build rdp protocol stack
|
|
||||||
@param addr: destination address
|
|
||||||
"""
|
|
||||||
controller = RDPServerController(self._colorDepth, self._privateKeyFileName, self._certificateFileName)
|
|
||||||
self.buildObserver(controller, addr)
|
|
||||||
return controller.getProtocol()
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
|
||||||
"""
|
|
||||||
@summary: Build observer use for connection
|
|
||||||
@param controller: RDP stack controller
|
|
||||||
@param addr: destination address
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildObserver", "ServerFactory"))
|
|
||||||
|
|
||||||
|
# class ServerFactory(RawLayerServerFactory):
|
||||||
|
# """
|
||||||
|
# @summary: Factory of Server RDP protocol
|
||||||
|
# """
|
||||||
|
# def __init__(self, colorDepth, privateKeyFileName = None, certificateFileName = None):
|
||||||
|
# """
|
||||||
|
# @param colorDepth: color depth of session
|
||||||
|
# @param privateKeyFileName: file contain server private key (if none -> back to standard RDP security)
|
||||||
|
# @param certficiateFileName: file that contain public key (if none -> back to standard RDP security)
|
||||||
|
# """
|
||||||
|
# self._colorDepth = colorDepth
|
||||||
|
# self._privateKeyFileName = privateKeyFileName
|
||||||
|
# self._certificateFileName = certificateFileName
|
||||||
|
#
|
||||||
|
# def connectionLost(self, tpktLayer, reason):
|
||||||
|
# """
|
||||||
|
# @param reason: twisted reason
|
||||||
|
# """
|
||||||
|
# #retrieve controller
|
||||||
|
# x224Layer = tpktLayer._presentation
|
||||||
|
# mcsLayer = x224Layer._presentation
|
||||||
|
# secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
|
||||||
|
# pduLayer = secLayer._presentation
|
||||||
|
# controller = pduLayer._listener
|
||||||
|
# controller.onClose()
|
||||||
|
#
|
||||||
|
# def buildRawLayer(self, addr):
|
||||||
|
# """
|
||||||
|
# @summary: Function call from twisted and build rdp protocol stack
|
||||||
|
# @param addr: destination address
|
||||||
|
# """
|
||||||
|
# controller = RDPServerController(self._colorDepth, self._privateKeyFileName, self._certificateFileName)
|
||||||
|
# self.buildObserver(controller, addr)
|
||||||
|
# return controller.getProtocol()
|
||||||
|
#
|
||||||
|
# def buildObserver(self, controller, addr):
|
||||||
|
# """
|
||||||
|
# @summary: Build observer use for connection
|
||||||
|
# @param controller: RDP stack controller
|
||||||
|
# @param addr: destination address
|
||||||
|
# """
|
||||||
|
# raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildObserver", "ServerFactory"))
|
||||||
|
#
|
||||||
class RDPClientObserver(object):
|
class RDPClientObserver(object):
|
||||||
"""
|
"""
|
||||||
@summary: Class use to inform all RDP event handle by RDPY
|
@summary: Class use to inform all RDP event handle by RDPY
|
||||||
@@ -593,6 +652,12 @@ class RDPClientObserver(object):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver"))
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Windows session is ready
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "RDPClientObserver"))
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: Stack is closes
|
@summary: Stack is closes
|
||||||
@@ -638,11 +703,12 @@ class RDPServerObserver(object):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
|
||||||
|
|
||||||
def onKeyEventScancode(self, code, isPressed):
|
def onKeyEventScancode(self, code, isPressed, isExtended):
|
||||||
"""
|
"""
|
||||||
@summary: Event call when a keyboard event is catch in scan code format
|
@summary: Event call when a keyboard event is catch in scan code format
|
||||||
@param code: scan code of key
|
@param code: {integer} scan code of key
|
||||||
@param isPressed: True if key is down
|
@param isPressed: {boolean} True if key is down
|
||||||
|
@param isExtended: {boolean} True if a special key
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventScanCode", "RDPServerObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventScanCode", "RDPServerObserver"))
|
||||||
|
|
||||||
@@ -659,7 +725,7 @@ class RDPServerObserver(object):
|
|||||||
@summary: Event call on mouse event
|
@summary: Event call on mouse event
|
||||||
@param x: x position
|
@param x: x position
|
||||||
@param y: y position
|
@param y: y position
|
||||||
@param button: 1, 2 or 3 button
|
@param button: 1, 2, 3, 4 or 5 button
|
||||||
@param isPressed: True if mouse button is pressed
|
@param isPressed: True if mouse button is pressed
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onPointerEvent", "RDPServerObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onPointerEvent", "RDPServerObserver"))
|
||||||
@@ -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,14 @@
|
|||||||
RDP Standard security layer
|
RDP Standard security layer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sha, md5
|
from hashlib import sha1 as sha
|
||||||
import gcc, lic, tpkt, mcs
|
from hashlib import md5
|
||||||
from rdpy.core.type import CompositeType, Stream, UInt32Le, UInt16Le, String, sizeof, UInt8
|
from rdpy.core import tpkt, lic
|
||||||
from rdpy.core.layer import LayerAutomata, IStreamSender
|
from rdpy.core.t125 import gcc, mcs
|
||||||
from rdpy.core.error import InvalidExpectedDataException
|
from rdpy.model.type import CompositeType, CallableValue, Stream, UInt32Le, UInt16Le, Buffer, sizeof, UInt8
|
||||||
from rdpy.core import log
|
from rdpy.model.layer import LayerAutomata, IStreamSender
|
||||||
|
from rdpy.model.error import InvalidExpectedDataException
|
||||||
|
from rdpy.model import log
|
||||||
from rdpy.security import rc4
|
from rdpy.security import rc4
|
||||||
import rdpy.security.rsa_wrapper as rsa
|
import rdpy.security.rsa_wrapper as rsa
|
||||||
|
|
||||||
@@ -164,12 +166,12 @@ def macData(macSaltKey, data):
|
|||||||
md5Digest = md5.new()
|
md5Digest = md5.new()
|
||||||
|
|
||||||
#encode length
|
#encode length
|
||||||
s = Stream()
|
dataLength = Stream()
|
||||||
s.writeType(UInt32Le(len(data)))
|
dataLength.write_type(UInt32Le(len(data)))
|
||||||
|
|
||||||
sha1Digest.update(macSaltKey)
|
sha1Digest.update(macSaltKey)
|
||||||
sha1Digest.update("\x36" * 40)
|
sha1Digest.update("\x36" * 40)
|
||||||
sha1Digest.update(s.getvalue())
|
sha1Digest.update(dataLength.getvalue())
|
||||||
sha1Digest.update(data)
|
sha1Digest.update(data)
|
||||||
|
|
||||||
sha1Sig = sha1Digest.digest()
|
sha1Sig = sha1Digest.digest()
|
||||||
@@ -180,6 +182,38 @@ def macData(macSaltKey, data):
|
|||||||
|
|
||||||
return md5Digest.digest()
|
return md5Digest.digest()
|
||||||
|
|
||||||
|
def macSaltedData(macSaltKey, data, encryptionCount):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240789.aspx
|
||||||
|
@param macSaltKey: {str} mac key
|
||||||
|
@param data: {str} data to sign
|
||||||
|
@param encryptionCount: nb encrypted packet
|
||||||
|
@return: {str} signature
|
||||||
|
"""
|
||||||
|
sha1Digest = sha.new()
|
||||||
|
md5Digest = md5.new()
|
||||||
|
|
||||||
|
#encode length
|
||||||
|
dataLengthS = Stream()
|
||||||
|
dataLengthS.write_type(UInt32Le(len(data)))
|
||||||
|
|
||||||
|
encryptionCountS = Stream()
|
||||||
|
encryptionCountS.write_type(UInt32Le(encryptionCount))
|
||||||
|
|
||||||
|
sha1Digest.update(macSaltKey)
|
||||||
|
sha1Digest.update("\x36" * 40)
|
||||||
|
sha1Digest.update(dataLengthS.getvalue())
|
||||||
|
sha1Digest.update(data)
|
||||||
|
sha1Digest.update(encryptionCountS.getvalue())
|
||||||
|
|
||||||
|
sha1Sig = sha1Digest.digest()
|
||||||
|
|
||||||
|
md5Digest.update(macSaltKey)
|
||||||
|
md5Digest.update("\x5c" * 48)
|
||||||
|
md5Digest.update(sha1Sig)
|
||||||
|
|
||||||
|
return md5Digest.digest()
|
||||||
|
|
||||||
def tempKey(initialKey, currentKey):
|
def tempKey(initialKey, currentKey):
|
||||||
"""
|
"""
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240792.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240792.aspx
|
||||||
@@ -276,8 +310,8 @@ class ClientSecurityExchangePDU(CompositeType):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
self.length = UInt32Le(lambda:(sizeof(self) - 4))
|
self.length = UInt32Le(lambda:(sizeof(self) - 4))
|
||||||
self.encryptedClientRandom = String(readLen = UInt8(lambda:(self.length.value - 8)))
|
self.encryptedClientRandom = Buffer(readLen = CallableValue(lambda:(self.length.value - 8)))
|
||||||
self.padding = String("\x00" * 8, readLen = UInt8(8))
|
self.padding = Buffer("\x00" * 8, readLen = CallableValue(8))
|
||||||
|
|
||||||
class RDPInfo(CompositeType):
|
class RDPInfo(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -290,20 +324,20 @@ class RDPInfo(CompositeType):
|
|||||||
#code page
|
#code page
|
||||||
self.codePage = UInt32Le()
|
self.codePage = UInt32Le()
|
||||||
#support flag
|
#support flag
|
||||||
self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL)
|
self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL | InfoFlag.INFO_ENABLEWINDOWSKEY)
|
||||||
self.cbDomain = UInt16Le(lambda:sizeof(self.domain) - 2)
|
self.cbDomain = UInt16Le(lambda:sizeof(self.domain) - 2)
|
||||||
self.cbUserName = UInt16Le(lambda:sizeof(self.userName) - 2)
|
self.cbUserName = UInt16Le(lambda:sizeof(self.userName) - 2)
|
||||||
self.cbPassword = UInt16Le(lambda:sizeof(self.password) - 2)
|
self.cbPassword = UInt16Le(lambda:sizeof(self.password) - 2)
|
||||||
self.cbAlternateShell = UInt16Le(lambda:sizeof(self.alternateShell) - 2)
|
self.cbAlternateShell = UInt16Le(lambda:sizeof(self.alternateShell) - 2)
|
||||||
self.cbWorkingDir = UInt16Le(lambda:sizeof(self.workingDir) - 2)
|
self.cbWorkingDir = UInt16Le(lambda:sizeof(self.workingDir) - 2)
|
||||||
#microsoft domain
|
#microsoft domain
|
||||||
self.domain = String(readLen = UInt16Le(lambda:self.cbDomain.value + 2), unicode = True)
|
self.domain = Buffer(readLen = CallableValue(lambda: self.cbDomain.value + 2), unicode = True)
|
||||||
self.userName = String(readLen = UInt16Le(lambda:self.cbUserName.value + 2), unicode = True)
|
self.userName = Buffer(readLen = CallableValue(lambda: self.cbUserName.value + 2), unicode = True)
|
||||||
self.password = String(readLen = UInt16Le(lambda:self.cbPassword.value + 2), unicode = True)
|
self.password = Buffer(readLen = CallableValue(lambda: self.cbPassword.value + 2), unicode = True)
|
||||||
#shell execute at start of session
|
#shell execute at start of session
|
||||||
self.alternateShell = String(readLen = UInt16Le(lambda:self.cbAlternateShell.value + 2), unicode = True)
|
self.alternateShell = Buffer(readLen = CallableValue(lambda: self.cbAlternateShell.value + 2), unicode = True)
|
||||||
#working directory for session
|
#working directory for session
|
||||||
self.workingDir = String(readLen = UInt16Le(lambda:self.cbWorkingDir.value + 2), unicode = True)
|
self.workingDir = Buffer(readLen = CallableValue(lambda: self.cbWorkingDir.value + 2), unicode = True)
|
||||||
self.extendedInfo = RDPExtendedInfo(conditional = extendedInfoConditional)
|
self.extendedInfo = RDPExtendedInfo(conditional = extendedInfoConditional)
|
||||||
|
|
||||||
class RDPExtendedInfo(CompositeType):
|
class RDPExtendedInfo(CompositeType):
|
||||||
@@ -314,11 +348,11 @@ class RDPExtendedInfo(CompositeType):
|
|||||||
CompositeType.__init__(self, conditional = conditional)
|
CompositeType.__init__(self, conditional = conditional)
|
||||||
self.clientAddressFamily = UInt16Le(AfInet.AF_INET)
|
self.clientAddressFamily = UInt16Le(AfInet.AF_INET)
|
||||||
self.cbClientAddress = UInt16Le(lambda:sizeof(self.clientAddress))
|
self.cbClientAddress = UInt16Le(lambda:sizeof(self.clientAddress))
|
||||||
self.clientAddress = String(readLen = self.cbClientAddress, unicode = True)
|
self.clientAddress = Buffer(readLen = self.cbClientAddress, unicode = True)
|
||||||
self.cbClientDir = UInt16Le(lambda:sizeof(self.clientDir))
|
self.cbClientDir = UInt16Le(lambda:sizeof(self.clientDir))
|
||||||
self.clientDir = String(readLen = self.cbClientDir, unicode = True)
|
self.clientDir = Buffer(readLen = self.cbClientDir, unicode = True)
|
||||||
#TODO make tiomezone
|
#TODO make tiomezone
|
||||||
self.clientTimeZone = String("\x00" * 172)
|
self.clientTimeZone = Buffer("\x00" * 172)
|
||||||
self.clientSessionId = UInt32Le()
|
self.clientSessionId = UInt32Le()
|
||||||
self.performanceFlags = UInt32Le()
|
self.performanceFlags = UInt32Le()
|
||||||
|
|
||||||
@@ -342,6 +376,9 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
|
|||||||
#True if classic encryption is enable
|
#True if classic encryption is enable
|
||||||
self._enableEncryption = False
|
self._enableEncryption = False
|
||||||
|
|
||||||
|
#Enable Secure Mac generation
|
||||||
|
self._enableSecureCheckSum = False
|
||||||
|
|
||||||
#initialise decrypt and encrypt keys
|
#initialise decrypt and encrypt keys
|
||||||
self._macKey = None
|
self._macKey = None
|
||||||
self._initialDecrytKey = None
|
self._initialDecrytKey = None
|
||||||
@@ -358,10 +395,11 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
|
|||||||
self._encryptRc4 = None
|
self._encryptRc4 = None
|
||||||
|
|
||||||
|
|
||||||
def readEncryptedPayload(self, s):
|
def readEncryptedPayload(self, s, saltedMacGeneration):
|
||||||
"""
|
"""
|
||||||
@summary: decrypt basic RDP security payload
|
@summary: decrypt basic RDP security payload
|
||||||
@param s: {Stream} encrypted stream
|
@param s: {Stream} encrypted stream
|
||||||
|
@param saltedMacGeneration: {bool} use salted mac generation
|
||||||
@return: {Stream} decrypted
|
@return: {Stream} decrypted
|
||||||
"""
|
"""
|
||||||
#if update is needed
|
#if update is needed
|
||||||
@@ -372,24 +410,28 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
|
|||||||
self._decryptRc4 = rc4.RC4Key(self._currentDecrytKey)
|
self._decryptRc4 = rc4.RC4Key(self._currentDecrytKey)
|
||||||
self._nbDecryptedPacket = 0
|
self._nbDecryptedPacket = 0
|
||||||
|
|
||||||
signature = String(readLen = UInt8(8))
|
signature = Buffer(readLen = CallableValue(8))
|
||||||
encryptedPayload = String()
|
encryptedPayload = Buffer()
|
||||||
s.readType((signature, encryptedPayload))
|
s.read_type((signature, encryptedPayload))
|
||||||
decrypted = rc4.crypt(self._decryptRc4, encryptedPayload.value)
|
decrypted = rc4.crypt(self._decryptRc4, encryptedPayload.value)
|
||||||
|
|
||||||
#ckeck signature
|
#ckeck signature
|
||||||
if macData(self._macKey, decrypted)[:8] != signature.value:
|
if not saltedMacGeneration and macData(self._macKey, decrypted)[:8] != signature.value:
|
||||||
raise InvalidExpectedDataException("Bad packet signature")
|
raise InvalidExpectedDataException("bad signature")
|
||||||
|
|
||||||
|
if saltedMacGeneration and macSaltedData(self._macKey, decrypted, self._nbDecryptedPacket)[:8] != signature.value:
|
||||||
|
raise InvalidExpectedDataException("bad signature")
|
||||||
|
|
||||||
#count
|
#count
|
||||||
self._nbDecryptedPacket += 1
|
self._nbDecryptedPacket += 1
|
||||||
|
|
||||||
return Stream(decrypted)
|
return Stream(decrypted)
|
||||||
|
|
||||||
def writeEncryptedPayload(self, data):
|
def writeEncryptedPayload(self, data, saltedMacGeneration):
|
||||||
"""
|
"""
|
||||||
@summary: sign and crypt data
|
@summary: sign and crypt data
|
||||||
@param s: {Stream} raw stream
|
@param data: {Type} raw stream
|
||||||
|
@param saltedMacGeneration: {bool} use salted mac generation
|
||||||
@return: {Tuple} (signature, encryptedData)
|
@return: {Tuple} (signature, encryptedData)
|
||||||
"""
|
"""
|
||||||
if self._nbEncryptedPacket == 4096:
|
if self._nbEncryptedPacket == 4096:
|
||||||
@@ -400,9 +442,14 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
|
|||||||
self._nbEncryptedPacket = 0
|
self._nbEncryptedPacket = 0
|
||||||
|
|
||||||
self._nbEncryptedPacket += 1
|
self._nbEncryptedPacket += 1
|
||||||
|
|
||||||
s = Stream()
|
s = Stream()
|
||||||
s.writeType(data)
|
s.write_type(data)
|
||||||
return (String(macData(self._macKey, s.getvalue())[:8]), String(rc4.crypt(self._encryptRc4, s.getvalue())))
|
|
||||||
|
if saltedMacGeneration:
|
||||||
|
return (Buffer(macSaltedData(self._macKey, s.getvalue(), self._nbEncryptedPacket - 1)[:8]), Buffer(rc4.crypt(self._encryptRc4, s.getvalue())))
|
||||||
|
else:
|
||||||
|
return (Buffer(macData(self._macKey, s.getvalue())[:8]), Buffer(rc4.crypt(self._encryptRc4, s.getvalue())))
|
||||||
|
|
||||||
def recv(self, data):
|
def recv(self, data):
|
||||||
"""
|
"""
|
||||||
@@ -416,10 +463,10 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
|
|||||||
|
|
||||||
securityFlag = UInt16Le()
|
securityFlag = UInt16Le()
|
||||||
securityFlagHi = UInt16Le()
|
securityFlagHi = UInt16Le()
|
||||||
data.readType((securityFlag, securityFlagHi))
|
data.read_type((securityFlag, securityFlagHi))
|
||||||
|
|
||||||
if securityFlag.value & SecurityFlag.SEC_ENCRYPT:
|
if securityFlag.value & SecurityFlag.SEC_ENCRYPT:
|
||||||
data = self.readEncryptedPayload(data)
|
data = self.readEncryptedPayload(data, securityFlag.value & SecurityFlag.SEC_SECURE_CHECKSUM)
|
||||||
|
|
||||||
self._presentation.recv(data)
|
self._presentation.recv(data)
|
||||||
|
|
||||||
@@ -433,7 +480,12 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
|
|||||||
self._transport.send(data)
|
self._transport.send(data)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.sendFlagged(SecurityFlag.SEC_ENCRYPT, data)
|
flag = SecurityFlag.SEC_ENCRYPT
|
||||||
|
|
||||||
|
if self._enableSecureCheckSum:
|
||||||
|
flag |= SecurityFlag.SEC_SECURE_CHECKSUM
|
||||||
|
|
||||||
|
self.sendFlagged(flag, data)
|
||||||
|
|
||||||
def sendFlagged(self, flag, data):
|
def sendFlagged(self, flag, data):
|
||||||
"""
|
"""
|
||||||
@@ -444,7 +496,7 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
|
|||||||
@param data: {Type | Tuple}
|
@param data: {Type | Tuple}
|
||||||
"""
|
"""
|
||||||
if flag & SecurityFlag.SEC_ENCRYPT:
|
if flag & SecurityFlag.SEC_ENCRYPT:
|
||||||
data = self.writeEncryptedPayload(data)
|
data = self.writeEncryptedPayload(data, flag & SecurityFlag.SEC_SECURE_CHECKSUM)
|
||||||
self._transport.send((UInt16Le(flag), UInt16Le(), data))
|
self._transport.send((UInt16Le(flag), UInt16Le(), data))
|
||||||
|
|
||||||
def recvFastPath(self, secFlag, fastPathS):
|
def recvFastPath(self, secFlag, fastPathS):
|
||||||
@@ -454,7 +506,7 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
|
|||||||
@param fastPathS: {Stream}
|
@param fastPathS: {Stream}
|
||||||
"""
|
"""
|
||||||
if self._enableEncryption and secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED:
|
if self._enableEncryption and secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED:
|
||||||
fastPathS = self.readEncryptedPayload(fastPathS)
|
fastPathS = self.readEncryptedPayload(fastPathS, secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_SECURE_CHECKSUM)
|
||||||
|
|
||||||
self._fastPathPresentation.recvFastPath(secFlag, fastPathS)
|
self._fastPathPresentation.recvFastPath(secFlag, fastPathS)
|
||||||
|
|
||||||
@@ -472,7 +524,11 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
|
|||||||
"""
|
"""
|
||||||
if self._enableEncryption:
|
if self._enableEncryption:
|
||||||
secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED
|
secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED
|
||||||
fastPathS = self.writeEncryptedPayload(fastPathS)
|
|
||||||
|
if self._enableSecureCheckSum:
|
||||||
|
secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_SECURE_CHECKSUM
|
||||||
|
|
||||||
|
fastPathS = self.writeEncryptedPayload(fastPathS, self._enableSecureCheckSum)
|
||||||
|
|
||||||
self._fastPathTransport.sendFastPath(secFlag, fastPathS)
|
self._fastPathTransport.sendFastPath(secFlag, fastPathS)
|
||||||
|
|
||||||
@@ -575,7 +631,7 @@ class Client(SecLayer):
|
|||||||
#packet preambule
|
#packet preambule
|
||||||
securityFlag = UInt16Le()
|
securityFlag = UInt16Le()
|
||||||
securityFlagHi = UInt16Le()
|
securityFlagHi = UInt16Le()
|
||||||
s.readType((securityFlag, securityFlagHi))
|
s.read_type((securityFlag, securityFlagHi))
|
||||||
|
|
||||||
if not (securityFlag.value & SecurityFlag.SEC_LICENSE_PKT):
|
if not (securityFlag.value & SecurityFlag.SEC_LICENSE_PKT):
|
||||||
raise InvalidExpectedDataException("waiting license packet")
|
raise InvalidExpectedDataException("waiting license packet")
|
||||||
@@ -624,13 +680,13 @@ class Server(SecLayer):
|
|||||||
#packet preambule
|
#packet preambule
|
||||||
securityFlag = UInt16Le()
|
securityFlag = UInt16Le()
|
||||||
securityFlagHi = UInt16Le()
|
securityFlagHi = UInt16Le()
|
||||||
s.readType((securityFlag, securityFlagHi))
|
s.read_type((securityFlag, securityFlagHi))
|
||||||
|
|
||||||
if not (securityFlag.value & SecurityFlag.SEC_EXCHANGE_PKT):
|
if not (securityFlag.value & SecurityFlag.SEC_EXCHANGE_PKT):
|
||||||
raise InvalidExpectedDataException("waiting client random")
|
raise InvalidExpectedDataException("waiting client random")
|
||||||
|
|
||||||
message = ClientSecurityExchangePDU()
|
message = ClientSecurityExchangePDU()
|
||||||
s.readType(message)
|
s.read_type(message)
|
||||||
clientRandom = rsa.decrypt(message.encryptedClientRandom.value[::-1], self._rsaPrivateKey)[::-1]
|
clientRandom = rsa.decrypt(message.encryptedClientRandom.value[::-1], self._rsaPrivateKey)[::-1]
|
||||||
|
|
||||||
self._macKey, self._initialEncryptKey, self._initialDecrytKey = generateKeys( clientRandom,
|
self._macKey, self._initialEncryptKey, self._initialDecrytKey = generateKeys( clientRandom,
|
||||||
@@ -655,15 +711,15 @@ class Server(SecLayer):
|
|||||||
"""
|
"""
|
||||||
securityFlag = UInt16Le()
|
securityFlag = UInt16Le()
|
||||||
securityFlagHi = UInt16Le()
|
securityFlagHi = UInt16Le()
|
||||||
s.readType((securityFlag, securityFlagHi))
|
s.read_type((securityFlag, securityFlagHi))
|
||||||
|
|
||||||
if not (securityFlag.value & SecurityFlag.SEC_INFO_PKT):
|
if not (securityFlag.value & SecurityFlag.SEC_INFO_PKT):
|
||||||
raise InvalidExpectedDataException("Waiting info packet")
|
raise InvalidExpectedDataException("Waiting info packet")
|
||||||
|
|
||||||
if securityFlag.value & SecurityFlag.SEC_ENCRYPT:
|
if securityFlag.value & SecurityFlag.SEC_ENCRYPT:
|
||||||
s = self.readEncryptedPayload(s)
|
s = self.readEncryptedPayload(s, securityFlag.value & SecurityFlag.SEC_SECURE_CHECKSUM)
|
||||||
|
|
||||||
s.readType(self._info)
|
s.read_type(self._info)
|
||||||
#next state send error license
|
#next state send error license
|
||||||
self.sendLicensingErrorMessage()
|
self.sendLicensingErrorMessage()
|
||||||
#reinit state
|
#reinit state
|
||||||
@@ -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,22 +22,25 @@ Basic Encoding Rules use in RDP.
|
|||||||
ASN.1 standard
|
ASN.1 standard
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.core.type import UInt8, UInt16Be, UInt32Be, String
|
from rdpy.model.message import UInt8, UInt16Be, UInt32Be, Buffer
|
||||||
from rdpy.core.error import InvalidExpectedDataException, InvalidSize
|
from rdpy.model.error import InvalidExpectedDataException, InvalidSize
|
||||||
|
|
||||||
class BerPc(object):
|
|
||||||
|
class BerPc:
|
||||||
BER_PC_MASK = 0x20
|
BER_PC_MASK = 0x20
|
||||||
BER_PRIMITIVE = 0x00
|
BER_PRIMITIVE = 0x00
|
||||||
BER_CONSTRUCT = 0x20
|
BER_CONSTRUCT = 0x20
|
||||||
|
|
||||||
class Class(object):
|
|
||||||
|
class Class:
|
||||||
BER_CLASS_MASK = 0xC0
|
BER_CLASS_MASK = 0xC0
|
||||||
BER_CLASS_UNIV = 0x00
|
BER_CLASS_UNIV = 0x00
|
||||||
BER_CLASS_APPL = 0x40
|
BER_CLASS_APPL = 0x40
|
||||||
BER_CLASS_CTXT = 0x80
|
BER_CLASS_CTXT = 0x80
|
||||||
BER_CLASS_PRIV = 0xC0
|
BER_CLASS_PRIV = 0xC0
|
||||||
|
|
||||||
class Tag(object):
|
|
||||||
|
class Tag:
|
||||||
BER_TAG_MASK = 0x1F
|
BER_TAG_MASK = 0x1F
|
||||||
BER_TAG_BOOLEAN = 0x01
|
BER_TAG_BOOLEAN = 0x01
|
||||||
BER_TAG_INTEGER = 0x02
|
BER_TAG_INTEGER = 0x02
|
||||||
@@ -48,6 +51,7 @@ class Tag(object):
|
|||||||
BER_TAG_SEQUENCE = 0x10
|
BER_TAG_SEQUENCE = 0x10
|
||||||
BER_TAG_SEQUENCE_OF = 0x10
|
BER_TAG_SEQUENCE_OF = 0x10
|
||||||
|
|
||||||
|
|
||||||
def berPC(pc):
|
def berPC(pc):
|
||||||
"""
|
"""
|
||||||
@summary: Return BER_CONSTRUCT if true
|
@summary: Return BER_CONSTRUCT if true
|
||||||
@@ -60,6 +64,7 @@ def berPC(pc):
|
|||||||
else:
|
else:
|
||||||
return BerPc.BER_PRIMITIVE
|
return BerPc.BER_PRIMITIVE
|
||||||
|
|
||||||
|
|
||||||
def readLength(s):
|
def readLength(s):
|
||||||
"""
|
"""
|
||||||
@summary: Read length of BER structure
|
@summary: Read length of BER structure
|
||||||
@@ -69,7 +74,7 @@ def readLength(s):
|
|||||||
"""
|
"""
|
||||||
size = None
|
size = None
|
||||||
length = UInt8()
|
length = UInt8()
|
||||||
s.readType(length)
|
s.read_type(length)
|
||||||
byte = length.value
|
byte = length.value
|
||||||
if byte & 0x80:
|
if byte & 0x80:
|
||||||
byte &= ~0x80
|
byte &= ~0x80
|
||||||
@@ -79,11 +84,12 @@ def readLength(s):
|
|||||||
size = UInt16Be()
|
size = UInt16Be()
|
||||||
else:
|
else:
|
||||||
raise InvalidExpectedDataException("BER length may be 1 or 2")
|
raise InvalidExpectedDataException("BER length may be 1 or 2")
|
||||||
s.readType(size)
|
s.read_type(size)
|
||||||
else:
|
else:
|
||||||
size = length
|
size = length
|
||||||
return size.value
|
return size.value
|
||||||
|
|
||||||
|
|
||||||
def writeLength(size):
|
def writeLength(size):
|
||||||
"""
|
"""
|
||||||
@summary: Return structure length as expected in BER specification
|
@summary: Return structure length as expected in BER specification
|
||||||
@@ -95,6 +101,7 @@ def writeLength(size):
|
|||||||
else:
|
else:
|
||||||
return UInt8(size)
|
return UInt8(size)
|
||||||
|
|
||||||
|
|
||||||
def readUniversalTag(s, tag, pc):
|
def readUniversalTag(s, tag, pc):
|
||||||
"""
|
"""
|
||||||
@summary: Read tag of BER packet
|
@summary: Read tag of BER packet
|
||||||
@@ -103,9 +110,10 @@ def readUniversalTag(s, tag, pc):
|
|||||||
@return: true if tag is correctly read
|
@return: true if tag is correctly read
|
||||||
"""
|
"""
|
||||||
byte = UInt8()
|
byte = UInt8()
|
||||||
s.readType(byte)
|
s.read_type(byte)
|
||||||
return byte.value == ((Class.BER_CLASS_UNIV | berPC(pc)) | (Tag.BER_TAG_MASK & tag))
|
return byte.value == ((Class.BER_CLASS_UNIV | berPC(pc)) | (Tag.BER_TAG_MASK & tag))
|
||||||
|
|
||||||
|
|
||||||
def writeUniversalTag(tag, pc):
|
def writeUniversalTag(tag, pc):
|
||||||
"""
|
"""
|
||||||
@summary: Return universal tag byte
|
@summary: Return universal tag byte
|
||||||
@@ -115,6 +123,7 @@ def writeUniversalTag(tag, pc):
|
|||||||
"""
|
"""
|
||||||
return UInt8((Class.BER_CLASS_UNIV | berPC(pc)) | (Tag.BER_TAG_MASK & tag))
|
return UInt8((Class.BER_CLASS_UNIV | berPC(pc)) | (Tag.BER_TAG_MASK & tag))
|
||||||
|
|
||||||
|
|
||||||
def readApplicationTag(s, tag):
|
def readApplicationTag(s, tag):
|
||||||
"""
|
"""
|
||||||
@summary: Read application tag
|
@summary: Read application tag
|
||||||
@@ -123,11 +132,11 @@ def readApplicationTag(s, tag):
|
|||||||
@return: length of application packet
|
@return: length of application packet
|
||||||
"""
|
"""
|
||||||
byte = UInt8()
|
byte = UInt8()
|
||||||
s.readType(byte)
|
s.read_type(byte)
|
||||||
if tag.value > 30:
|
if tag.value > 30:
|
||||||
if byte.value != ((Class.BER_CLASS_APPL | BerPc.BER_CONSTRUCT) | Tag.BER_TAG_MASK):
|
if byte.value != ((Class.BER_CLASS_APPL | BerPc.BER_CONSTRUCT) | Tag.BER_TAG_MASK):
|
||||||
raise InvalidExpectedDataException()
|
raise InvalidExpectedDataException()
|
||||||
s.readType(byte)
|
s.read_type(byte)
|
||||||
if byte.value != tag.value:
|
if byte.value != tag.value:
|
||||||
raise InvalidExpectedDataException("bad tag")
|
raise InvalidExpectedDataException("bad tag")
|
||||||
else:
|
else:
|
||||||
@@ -136,6 +145,7 @@ def readApplicationTag(s, tag):
|
|||||||
|
|
||||||
return readLength(s)
|
return readLength(s)
|
||||||
|
|
||||||
|
|
||||||
def writeApplicationTag(tag, size):
|
def writeApplicationTag(tag, size):
|
||||||
"""
|
"""
|
||||||
@summary: Return structure that represent BER application tag
|
@summary: Return structure that represent BER application tag
|
||||||
@@ -159,7 +169,7 @@ def readBoolean(s):
|
|||||||
if size != 1:
|
if size != 1:
|
||||||
raise InvalidExpectedDataException("bad boolean size")
|
raise InvalidExpectedDataException("bad boolean size")
|
||||||
b = UInt8()
|
b = UInt8()
|
||||||
s.readType(b)
|
s.read_type(b)
|
||||||
return bool(b.value)
|
return bool(b.value)
|
||||||
|
|
||||||
def writeBoolean(b):
|
def writeBoolean(b):
|
||||||
@@ -186,21 +196,21 @@ def readInteger(s):
|
|||||||
|
|
||||||
if size == 1:
|
if size == 1:
|
||||||
integer = UInt8()
|
integer = UInt8()
|
||||||
s.readType(integer)
|
s.read_type(integer)
|
||||||
return integer.value
|
return integer.value
|
||||||
elif size == 2:
|
elif size == 2:
|
||||||
integer = UInt16Be()
|
integer = UInt16Be()
|
||||||
s.readType(integer)
|
s.read_type(integer)
|
||||||
return integer.value
|
return integer.value
|
||||||
elif size == 3:
|
elif size == 3:
|
||||||
integer1 = UInt8()
|
integer1 = UInt8()
|
||||||
integer2 = UInt16Be()
|
integer2 = UInt16Be()
|
||||||
s.readType(integer1)
|
s.read_type(integer1)
|
||||||
s.readType(integer2)
|
s.read_type(integer2)
|
||||||
return integer2.value + (integer1.value << 16)
|
return integer2.value + (integer1.value << 16)
|
||||||
elif size == 4:
|
elif size == 4:
|
||||||
integer = UInt32Be()
|
integer = UInt32Be()
|
||||||
s.readType(integer)
|
s.read_type(integer)
|
||||||
return integer.value
|
return integer.value
|
||||||
else:
|
else:
|
||||||
raise InvalidExpectedDataException("Wrong integer size")
|
raise InvalidExpectedDataException("Wrong integer size")
|
||||||
@@ -235,7 +245,7 @@ def writeOctetstring(value):
|
|||||||
@param value: string
|
@param value: string
|
||||||
@return: BER octet string block
|
@return: BER octet string block
|
||||||
"""
|
"""
|
||||||
return (writeUniversalTag(Tag.BER_TAG_OCTET_STRING, False), writeLength(len(value)), String(value))
|
return (writeUniversalTag(Tag.BER_TAG_OCTET_STRING, False), writeLength(len(value)), Buffer(value))
|
||||||
|
|
||||||
def readEnumerated(s):
|
def readEnumerated(s):
|
||||||
"""
|
"""
|
||||||
@@ -248,7 +258,7 @@ def readEnumerated(s):
|
|||||||
if readLength(s) != 1:
|
if readLength(s) != 1:
|
||||||
raise InvalidSize("enumerate size is wrong")
|
raise InvalidSize("enumerate size is wrong")
|
||||||
enumer = UInt8()
|
enumer = UInt8()
|
||||||
s.readType(enumer)
|
s.read_type(enumer)
|
||||||
return enumer.value
|
return enumer.value
|
||||||
|
|
||||||
def writeEnumerated(enumerated):
|
def writeEnumerated(enumerated):
|
||||||
@@ -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,18 +22,18 @@ 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
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import md5
|
from hashlib import md5
|
||||||
from rdpy.core.type import UInt8, UInt16Le, UInt32Le, CompositeType, String, Stream, sizeof, FactoryType, ArrayType
|
from rdpy.model.message import UInt8, UInt16Le, UInt32Le, CompositeType, Buffer, Stream, sizeof, FactoryType, ArrayType
|
||||||
import per, mcs
|
from rdpy.core.t125 import per, mcs
|
||||||
from rdpy.core.error import InvalidExpectedDataException
|
from rdpy.model.error import InvalidExpectedDataException
|
||||||
from rdpy.core import log
|
from rdpy.model import log
|
||||||
from rdpy.security import x509
|
from rdpy.security import x509
|
||||||
import rdpy.security.rsa_wrapper as rsa
|
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 )
|
||||||
|
|
||||||
h221_cs_key = "Duca";
|
h221_cs_key = b"Duca";
|
||||||
h221_sc_key = "McDn";
|
h221_sc_key = b"McDn";
|
||||||
|
|
||||||
class MessageType(object):
|
class MessageType(object):
|
||||||
"""
|
"""
|
||||||
@@ -209,42 +209,46 @@ class CertificateType(object):
|
|||||||
CERT_CHAIN_VERSION_1 = 0x00000001
|
CERT_CHAIN_VERSION_1 = 0x00000001
|
||||||
CERT_CHAIN_VERSION_2 = 0x00000002
|
CERT_CHAIN_VERSION_2 = 0x00000002
|
||||||
|
|
||||||
|
|
||||||
class DataBlock(CompositeType):
|
class DataBlock(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Block settings
|
@summary: Block settings
|
||||||
"""
|
"""
|
||||||
def __init__(self, dataBlock = None):
|
def __init__(self, data_block=None):
|
||||||
CompositeType.__init__(self)
|
super().__init__()
|
||||||
self.type = UInt16Le(lambda:self.dataBlock.__class__._TYPE_)
|
self.type = UInt16Le(lambda:data_block._TYPE_)
|
||||||
self.length = UInt16Le(lambda: sizeof(self))
|
self.length = UInt16Le(lambda: sizeof(self))
|
||||||
|
|
||||||
def DataBlockFactory():
|
def factory():
|
||||||
"""
|
"""
|
||||||
@summary: build settings in accordance of type self.type.value
|
|
||||||
"""
|
"""
|
||||||
for c in [ClientCoreData, ClientSecurityData, ClientNetworkData, ServerCoreData, ServerNetworkData, ServerSecurityData]:
|
gcc_type = [
|
||||||
|
ClientCoreData, ClientSecurityData, ClientNetworkData,
|
||||||
|
ServerCoreData, ServerNetworkData, ServerSecurityData
|
||||||
|
]
|
||||||
|
|
||||||
|
for c in gcc_type:
|
||||||
if self.type.value == c._TYPE_:
|
if self.type.value == c._TYPE_:
|
||||||
return c(readLen = self.length - 4)
|
return c(read_len=lambda: (self.length.value - 4))
|
||||||
log.debug("unknown GCC block type : %s"%hex(self.type.value))
|
log.debug("unknown GCC block type : %s"%hex(self.type.value))
|
||||||
# read entire packet
|
# read entire packet
|
||||||
return String(readLen = self.length - 4)
|
return Buffer(read_len=lambda: (self.length.value - 4))
|
||||||
|
|
||||||
if dataBlock is None:
|
if data_block is None:
|
||||||
dataBlock = FactoryType(DataBlockFactory)
|
data_block = FactoryType(factory)
|
||||||
elif not "_TYPE_" in dataBlock.__class__.__dict__:
|
elif "_TYPE_" not in data_block.__class__.__dict__:
|
||||||
raise InvalidExpectedDataException("Try to send an invalid GCC blocks")
|
raise InvalidExpectedDataException("Try to send an invalid GCC blocks")
|
||||||
|
|
||||||
self.dataBlock = dataBlock
|
self.dataBlock = data_block
|
||||||
|
|
||||||
|
|
||||||
class ClientCoreData(CompositeType):
|
class ClientCoreData(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Class that represent core setting of client
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
|
|
||||||
"""
|
"""
|
||||||
_TYPE_ = MessageType.CS_CORE
|
_TYPE_ = MessageType.CS_CORE
|
||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, read_len=None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
super().__init__(read_len=read_len)
|
||||||
self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS)
|
self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS)
|
||||||
self.desktopWidth = UInt16Le(1280)
|
self.desktopWidth = UInt16Le(1280)
|
||||||
self.desktopHeight = UInt16Le(800)
|
self.desktopHeight = UInt16Le(800)
|
||||||
@@ -252,69 +256,68 @@ class ClientCoreData(CompositeType):
|
|||||||
self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL)
|
self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL)
|
||||||
self.kbdLayout = UInt32Le(KeyboardLayout.US)
|
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 = Buffer(("rdpy" + "\x00" * 12).encode("utf-16le"), read_len=lambda: 32)
|
||||||
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 = Buffer(b"\x00" * 64, read_len=lambda: 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 = Buffer(b"\x00" * 64, read_len=lambda: 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):
|
||||||
"""
|
"""
|
||||||
@summary: Server side core settings structure
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240517.aspx
|
|
||||||
"""
|
"""
|
||||||
_TYPE_ = MessageType.SC_CORE
|
_TYPE_ = MessageType.SC_CORE
|
||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, read_len=None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
super().__init__(read_len=read_len)
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
@summary: Client security setting
|
|
||||||
@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, read_len=None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
super().__init__(read_len=read_len)
|
||||||
self.encryptionMethods = UInt32Le(EncryptionMethod.ENCRYPTION_FLAG_40BIT | EncryptionMethod.ENCRYPTION_FLAG_56BIT | EncryptionMethod.ENCRYPTION_FLAG_128BIT)
|
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):
|
||||||
"""
|
"""
|
||||||
@summary: Server security settings
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240518.aspx
|
|
||||||
"""
|
"""
|
||||||
_TYPE_ = MessageType.SC_SECURITY
|
_TYPE_ = MessageType.SC_SECURITY
|
||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, read_len=None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
super().__init__(read_len=read_len)
|
||||||
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.serverRandomLen = UInt32Le(0x00000020, constant=True, conditional=lambda: not(self.encryptionMethod.value == 0 and self.encryptionLevel.value == 0))
|
||||||
self.serverCertLen = UInt32Le(lambda:sizeof(self.serverCertificate), conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0))
|
self.serverCertLen = UInt32Le(lambda: sizeof(self.serverCertificate), conditional=lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel.value == 0))
|
||||||
self.serverRandom = String(readLen = self.serverRandomLen, conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0))
|
self.serverRandom = Buffer(read_len=lambda: self.serverRandomLen.value, conditional=lambda: not(self.encryptionMethod.value == 0 and self.encryptionLevel.value == 0))
|
||||||
self.serverCertificate = ServerCertificate(readLen = self.serverCertLen, conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0))
|
self.serverCertificate = ServerCertificate(read_len=lambda: self.serverCertLen.value, conditional=lambda: not(self.encryptionMethod.value == 0 and self.encryptionLevel.value == 0))
|
||||||
|
|
||||||
|
|
||||||
class ServerCertificate(CompositeType):
|
class ServerCertificate(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Server certificate structure
|
@summary: Server certificate structure
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240521.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240521.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self, certData = None, readLen = None, conditional = lambda:True):
|
def __init__(self, certData = None, read_len = None, conditional = lambda:True):
|
||||||
CompositeType.__init__(self, readLen = readLen, conditional = conditional)
|
CompositeType.__init__(self, read_len=read_len, conditional = conditional)
|
||||||
self.dwVersion = UInt32Le(lambda:(self.certData.__class__._TYPE_))
|
self.dwVersion = UInt32Le(lambda:(self.certData.__class__._TYPE_))
|
||||||
|
|
||||||
def CertificateFactory():
|
def CertificateFactory():
|
||||||
@@ -353,9 +356,9 @@ class ProprietaryServerCertificate(CompositeType):
|
|||||||
self.wPublicKeyBlobLen = UInt16Le(lambda:sizeof(self.PublicKeyBlob))
|
self.wPublicKeyBlobLen = UInt16Le(lambda:sizeof(self.PublicKeyBlob))
|
||||||
self.PublicKeyBlob = RSAPublicKey(readLen = self.wPublicKeyBlobLen)
|
self.PublicKeyBlob = RSAPublicKey(readLen = self.wPublicKeyBlobLen)
|
||||||
self.wSignatureBlobType = UInt16Le(0x0008, constant = True)
|
self.wSignatureBlobType = UInt16Le(0x0008, constant = True)
|
||||||
self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) - 8))
|
self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) + sizeof(self.padding)))
|
||||||
self.SignatureBlob = String(readLen = self.wSignatureBlobLen)
|
self.SignatureBlob = Buffer(readLen = CallableValue(lambda:(self.wSignatureBlobLen.value - sizeof(self.padding))))
|
||||||
self.padding = String(b"\x00" * 8, readLen = UInt8(8))
|
self.padding = Buffer(b"\x00" * 8, readLen = CallableValue(8))
|
||||||
|
|
||||||
def getPublicKey(self):
|
def getPublicKey(self):
|
||||||
"""
|
"""
|
||||||
@@ -370,17 +373,17 @@ class ProprietaryServerCertificate(CompositeType):
|
|||||||
@summary: compute hash
|
@summary: compute hash
|
||||||
"""
|
"""
|
||||||
s = Stream()
|
s = Stream()
|
||||||
s.writeType(UInt32Le(self.__class__._TYPE_))
|
s.write_type(UInt32Le(self.__class__._TYPE_))
|
||||||
s.writeType(self.dwSigAlgId)
|
s.write_type(self.dwSigAlgId)
|
||||||
s.writeType(self.dwKeyAlgId)
|
s.write_type(self.dwKeyAlgId)
|
||||||
s.writeType(self.wPublicKeyBlobType)
|
s.write_type(self.wPublicKeyBlobType)
|
||||||
s.writeType(self.wPublicKeyBlobLen)
|
s.write_type(self.wPublicKeyBlobLen)
|
||||||
s.writeType(self.PublicKeyBlob)
|
s.write_type(self.PublicKeyBlob)
|
||||||
|
|
||||||
md5Digest = md5.new()
|
md5Digest = md5.new()
|
||||||
md5Digest.update(s.getvalue())
|
md5Digest.update(s.getvalue())
|
||||||
|
|
||||||
return md5Digest.digest() + "\x00" + "\xff" * 46 + "\x01"
|
return md5Digest.digest() + "\x00" + "\xff" * 45 + "\x01"
|
||||||
|
|
||||||
def sign(self):
|
def sign(self):
|
||||||
"""
|
"""
|
||||||
@@ -404,7 +407,7 @@ class CertBlob(CompositeType):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
self.cbCert = UInt32Le(lambda:sizeof(self.abCert))
|
self.cbCert = UInt32Le(lambda:sizeof(self.abCert))
|
||||||
self.abCert = String(readLen = self.cbCert)
|
self.abCert = Buffer(readLen = self.cbCert)
|
||||||
|
|
||||||
class X509CertificateChain(CompositeType):
|
class X509CertificateChain(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -417,7 +420,7 @@ class X509CertificateChain(CompositeType):
|
|||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
self.NumCertBlobs = UInt32Le()
|
self.NumCertBlobs = UInt32Le()
|
||||||
self.CertBlobArray = ArrayType(CertBlob, readLen = self.NumCertBlobs)
|
self.CertBlobArray = ArrayType(CertBlob, readLen = self.NumCertBlobs)
|
||||||
self.padding = String(readLen = UInt8(lambda:(8 + 4 * self.NumCertBlobs.value)))
|
self.padding = Buffer(readLen = CallableValue(lambda:(8 + 4 * self.NumCertBlobs.value)))
|
||||||
|
|
||||||
def getPublicKey(self):
|
def getPublicKey(self):
|
||||||
"""
|
"""
|
||||||
@@ -446,93 +449,76 @@ class RSAPublicKey(CompositeType):
|
|||||||
self.bitlen = UInt32Le(lambda:((self.keylen.value - 8) * 8))
|
self.bitlen = UInt32Le(lambda:((self.keylen.value - 8) * 8))
|
||||||
self.datalen = UInt32Le(lambda:((self.bitlen.value / 8) - 1))
|
self.datalen = UInt32Le(lambda:((self.bitlen.value / 8) - 1))
|
||||||
self.pubExp = UInt32Le()
|
self.pubExp = UInt32Le()
|
||||||
self.modulus = String(readLen = UInt16Le(lambda:(self.keylen.value - 8)))
|
self.modulus = Buffer(readLen = CallableValue(lambda:(self.keylen.value - 8)))
|
||||||
self.padding = String("\x00" * 8, readLen = UInt8(8))
|
self.padding = Buffer(b"\x00" * 8, readLen = CallableValue(8))
|
||||||
|
|
||||||
|
|
||||||
class ChannelDef(CompositeType):
|
class ChannelDef(CompositeType):
|
||||||
"""
|
"""
|
||||||
Channels structure share between client and server
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240513.aspx
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, name = "", options = 0):
|
def __init__(self, name=b""):
|
||||||
CompositeType.__init__(self)
|
super().__init__()
|
||||||
# name of channel
|
# name of channel
|
||||||
self.name = String(name[0:8] + "\x00" * (8 - len(name)), readLen = UInt8(8))
|
self.name = Buffer(name[0:8] + b"\x00" * (8 - len(name)), read_len=lambda: 8)
|
||||||
# unknown
|
# unknown
|
||||||
self.options = UInt32Le()
|
self.options = UInt32Le()
|
||||||
|
|
||||||
|
|
||||||
class ClientNetworkData(CompositeType):
|
class ClientNetworkData(CompositeType):
|
||||||
"""
|
"""
|
||||||
GCC client network block
|
|
||||||
All channels asked by client are listed here
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240512.aspx
|
|
||||||
"""
|
"""
|
||||||
_TYPE_ = MessageType.CS_NET
|
_TYPE_ = MessageType.CS_NET
|
||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, read_len=None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, read_len=read_len)
|
||||||
self.channelCount = UInt32Le(lambda:len(self.channelDefArray._array))
|
self.channelCount = UInt32Le(lambda: len(self.channelDefArray))
|
||||||
self.channelDefArray = ArrayType(ChannelDef, readLen = self.channelCount)
|
self.channelDefArray = ArrayType(ChannelDef, read_len=lambda: self.channelCount.value)
|
||||||
|
|
||||||
|
|
||||||
class ServerNetworkData(CompositeType):
|
class ServerNetworkData(CompositeType):
|
||||||
"""
|
"""
|
||||||
GCC server network block
|
|
||||||
All channels asked by client are listed here
|
|
||||||
@see: All channels asked by client are listed here
|
|
||||||
"""
|
"""
|
||||||
_TYPE_ = MessageType.SC_NET
|
_TYPE_ = MessageType.SC_NET
|
||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, read_len=None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
super().__init__(read_len=read_len)
|
||||||
self.MCSChannelId = UInt16Le(mcs.Channel.MCS_GLOBAL_CHANNEL)
|
self.MCSChannelId = UInt16Le(mcs.Channel.MCS_GLOBAL_CHANNEL)
|
||||||
self.channelCount = UInt16Le(lambda:len(self.channelIdArray._array))
|
self.channelCount = UInt16Le(lambda: len(self.channelIdArray))
|
||||||
self.channelIdArray = ArrayType(UInt16Le, readLen = self.channelCount)
|
self.channelIdArray = ArrayType(UInt16Le, read_len=lambda: self.channelCount.value)
|
||||||
self.pad = UInt16Le(conditional=lambda: ((self.channelCount.value % 2) == 1))
|
self.pad = UInt16Le(conditional=lambda: ((self.channelCount.value % 2) == 1))
|
||||||
|
|
||||||
|
|
||||||
class Settings(CompositeType):
|
class Settings(CompositeType):
|
||||||
"""
|
"""
|
||||||
Class which group all clients settings supported by RDPY
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, init = [], readLen = None):
|
def __init__(self, init=None, read_len=None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
super().__init__(read_len=read_len)
|
||||||
self.settings = ArrayType(DataBlock, [DataBlock(i) for i in init])
|
self.settings = ArrayType(DataBlock, [DataBlock(i) for i in init or []])
|
||||||
|
|
||||||
def getBlock(self, messageType):
|
def get_block(self, message_type):
|
||||||
"""
|
"""
|
||||||
@param messageType: type of block
|
|
||||||
@return: specific block of type messageType
|
|
||||||
"""
|
"""
|
||||||
for i in self.settings._array:
|
for i in self.settings._array:
|
||||||
if i.type.value == messageType:
|
if i.type.value == message_type:
|
||||||
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 client_settings():
|
||||||
"""
|
"""
|
||||||
Build settings for client
|
|
||||||
@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)
|
||||||
@@ -553,13 +539,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.read_type(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
|
||||||
@@ -577,36 +563,36 @@ 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))
|
server_settings = Settings(read_len=lambda: length)
|
||||||
s.readType(serverSettings)
|
s.read_type(server_settings)
|
||||||
return serverSettings
|
return server_settings
|
||||||
|
|
||||||
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
|
||||||
"""
|
"""
|
||||||
userDataStream = Stream()
|
userDataStream = Stream()
|
||||||
userDataStream.writeType(userData)
|
userDataStream.write_type(userData)
|
||||||
|
|
||||||
return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
|
return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
|
||||||
per.writeLength(len(userDataStream.getvalue()) + 14), per.writeChoice(0),
|
per.writeLength(len(userDataStream.getvalue()) + 14), per.writeChoice(0),
|
||||||
per.writeSelection(0x08), per.writeNumericString("1", 1), per.writePadding(1),
|
per.writeSelection(0x08), per.writeNumericString(b"1", 1), per.writePadding(1),
|
||||||
per.writeNumberOfSet(1), per.writeChoice(0xc0),
|
per.writeNumberOfSet(1), per.writeChoice(0xc0),
|
||||||
per.writeOctetStream(h221_cs_key, 4), per.writeOctetStream(userDataStream.getvalue()))
|
per.writeOctetStream(h221_cs_key, 4), per.writeOctetStream(userDataStream.getvalue()))
|
||||||
|
|
||||||
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
|
||||||
"""
|
"""
|
||||||
serverDataStream = Stream()
|
serverDataStream = Stream()
|
||||||
serverDataStream.writeType(serverData)
|
serverDataStream.write_type(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,25 +24,28 @@ 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.core.layer import LayerAutomata, IStreamSender, Layer
|
from typing import Tuple
|
||||||
from rdpy.core.type import sizeof, Stream, UInt8, UInt16Le, String
|
|
||||||
from rdpy.core.error import InvalidExpectedDataException, InvalidValue, InvalidSize, CallPureVirtualFuntion
|
|
||||||
from rdpy.protocol.rdp.ber import writeLength
|
|
||||||
import rdpy.core.log as log
|
|
||||||
|
|
||||||
import ber, gcc, per
|
from rdpy.core import x224
|
||||||
|
from rdpy.model.layer import LayerAutomata, IStreamSender, Layer
|
||||||
|
from rdpy.model.message import sizeof, Stream, UInt8, UInt16Le, Buffer
|
||||||
|
from rdpy.model.error import InvalidExpectedDataException, InvalidValue, InvalidSize, CallPureVirtualFuntion
|
||||||
|
from rdpy.core.t125.ber import writeLength
|
||||||
|
import rdpy.model.log as log
|
||||||
|
|
||||||
|
from rdpy.core.t125 import ber, gcc, per
|
||||||
import rdpy.security.rsa_wrapper as rsa
|
import rdpy.security.rsa_wrapper as rsa
|
||||||
|
|
||||||
class Message(object):
|
|
||||||
|
class Message:
|
||||||
"""
|
"""
|
||||||
@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:
|
||||||
"""
|
"""
|
||||||
@summary: Domain MCS PDU header
|
|
||||||
"""
|
"""
|
||||||
ERECT_DOMAIN_REQUEST = 1
|
ERECT_DOMAIN_REQUEST = 1
|
||||||
DISCONNECT_PROVIDER_ULTIMATUM = 8
|
DISCONNECT_PROVIDER_ULTIMATUM = 8
|
||||||
@@ -53,17 +56,19 @@ class DomainMCSPDU:
|
|||||||
SEND_DATA_REQUEST = 25
|
SEND_DATA_REQUEST = 25
|
||||||
SEND_DATA_INDICATION = 26
|
SEND_DATA_INDICATION = 26
|
||||||
|
|
||||||
|
|
||||||
class Channel:
|
class Channel:
|
||||||
"""
|
"""
|
||||||
@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):
|
class IGCCConfig(object):
|
||||||
"""
|
"""
|
||||||
@summary: Channel information
|
@summary: Channel information
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def getUserId(self):
|
def getUserId(self):
|
||||||
"""
|
"""
|
||||||
@return: {integer} mcs user id
|
@return: {integer} mcs user id
|
||||||
@@ -83,14 +88,150 @@ class IGCCConfig(object):
|
|||||||
@return: {gcc.Settings} mcs layer gcc client settings
|
@return: {gcc.Settings} mcs layer gcc client settings
|
||||||
@see: mcs.IGCCConfig
|
@see: mcs.IGCCConfig
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getGCCClientSettings", "IGCCConfig"))
|
raise CallPureVirtualFuntion(
|
||||||
|
"%s:%s defined by interface %s" % (self.__class__, "getGCCClientSettings", "IGCCConfig"))
|
||||||
|
|
||||||
def getGCCServerSettings(self):
|
def getGCCServerSettings(self):
|
||||||
"""
|
"""
|
||||||
@return: {gcc.Settings} mcs layer gcc server settings
|
@return: {gcc.Settings} mcs layer gcc server settings
|
||||||
@see: mcs.IGCCConfig
|
@see: mcs.IGCCConfig
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getGCCServerSettings", "IGCCConfig"))
|
raise CallPureVirtualFuntion(
|
||||||
|
"%s:%s defined by interface %s" % (self.__class__, "getGCCServerSettings", "IGCCConfig"))
|
||||||
|
|
||||||
|
|
||||||
|
def write_domain_params(max_channels: int, max_users: int, max_tokens: int, max_pdu_size: int) -> tuple:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
domain_param = (ber.writeInteger(max_channels), ber.writeInteger(max_users), ber.writeInteger(max_tokens),
|
||||||
|
ber.writeInteger(1), ber.writeInteger(0), ber.writeInteger(1),
|
||||||
|
ber.writeInteger(max_pdu_size), ber.writeInteger(2))
|
||||||
|
return ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True), writeLength(sizeof(domain_param)), domain_param
|
||||||
|
|
||||||
|
|
||||||
|
def read_domain_params(stream: Stream) -> Tuple[int, int, int, int]:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
if not ber.readUniversalTag(stream, ber.Tag.BER_TAG_SEQUENCE, True):
|
||||||
|
raise InvalidValue("bad BER tags")
|
||||||
|
ber.readLength(stream) # length
|
||||||
|
max_channels = ber.readInteger(stream)
|
||||||
|
max_users = ber.readInteger(stream)
|
||||||
|
max_tokens = ber.readInteger(stream)
|
||||||
|
ber.readInteger(stream)
|
||||||
|
ber.readInteger(stream)
|
||||||
|
ber.readInteger(stream)
|
||||||
|
max_pdu_size = ber.readInteger(stream)
|
||||||
|
ber.readInteger(stream)
|
||||||
|
return max_channels, max_users, max_tokens, max_pdu_size
|
||||||
|
|
||||||
|
|
||||||
|
def mcs_pdu_header(mcs_pdu: UInt8, options=0) -> UInt8:
|
||||||
|
return (mcs_pdu << 2) | options
|
||||||
|
|
||||||
|
|
||||||
|
def check_mcs_pdu_header(opcode: UInt8, mcs_pdu):
|
||||||
|
return (opcode >> 2) == mcs_pdu
|
||||||
|
|
||||||
|
|
||||||
|
def erect_domain_request() -> Tuple:
|
||||||
|
return mcs_pdu_header(UInt8(DomainMCSPDU.ERECT_DOMAIN_REQUEST)), per.writeInteger(0), per.writeInteger(0)
|
||||||
|
|
||||||
|
|
||||||
|
def read_attach_confirm(data: Stream) -> int:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
opcode = data.read_type(UInt8())
|
||||||
|
|
||||||
|
if not check_mcs_pdu_header(opcode, DomainMCSPDU.ATTACH_USER_CONFIRM):
|
||||||
|
raise InvalidExpectedDataException("Invalid MCS PDU : ATTACH_USER_CONFIRM expected")
|
||||||
|
|
||||||
|
if per.readEnumerates(data) != 0:
|
||||||
|
raise InvalidExpectedDataException("Server reject user")
|
||||||
|
|
||||||
|
return per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
|
||||||
|
|
||||||
|
|
||||||
|
def attach_user_request() -> UInt8:
|
||||||
|
return mcs_pdu_header(UInt8(DomainMCSPDU.ATTACH_USER_REQUEST))
|
||||||
|
|
||||||
|
|
||||||
|
def channel_join_request(user_id: int, channel_id: int):
|
||||||
|
return (mcs_pdu_header(UInt8(DomainMCSPDU.CHANNEL_JOIN_REQUEST)),
|
||||||
|
per.writeInteger16(user_id, Channel.MCS_USERCHANNEL_BASE),
|
||||||
|
per.writeInteger16(channel_id))
|
||||||
|
|
||||||
|
|
||||||
|
def channel_join_confirm(user_id: int, channel_id: int, data: Stream) -> bool:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
opcode = data.read_type(UInt8())
|
||||||
|
|
||||||
|
if not check_mcs_pdu_header(opcode.value, DomainMCSPDU.CHANNEL_JOIN_CONFIRM):
|
||||||
|
raise InvalidExpectedDataException("Invalid MCS PDU : CHANNEL_JOIN_CONFIRM expected")
|
||||||
|
|
||||||
|
confirm = per.readEnumerates(data)
|
||||||
|
|
||||||
|
if user_id != per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE):
|
||||||
|
raise InvalidExpectedDataException("Invalid MCS User Id")
|
||||||
|
|
||||||
|
if channel_id != per.readInteger16(data):
|
||||||
|
raise InvalidExpectedDataException("Invalid MCS channel id")
|
||||||
|
|
||||||
|
return confirm == 0
|
||||||
|
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
def __init__(self, x224_layer: x224.X224):
|
||||||
|
self.x224 = x224_layer
|
||||||
|
self.channel_ids = {}
|
||||||
|
|
||||||
|
async def write_connect_initial(self):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
settings = gcc.client_settings()
|
||||||
|
settings.get_block(gcc.MessageType.CS_CORE).serverSelectedProtocol.value = self.x224.get_selected_protocol()
|
||||||
|
|
||||||
|
cc_req = gcc.writeConferenceCreateRequest(settings)
|
||||||
|
cc_req_stream = Stream()
|
||||||
|
cc_req_stream.write_type(cc_req)
|
||||||
|
|
||||||
|
tmp = (ber.writeOctetstring(b"\x01"), ber.writeOctetstring(b"\x01"), ber.writeBoolean(True),
|
||||||
|
write_domain_params(34, 2, 0, 0xffff),
|
||||||
|
write_domain_params(1, 1, 1, 0x420),
|
||||||
|
write_domain_params(0xffff, 0xfc17, 0xffff, 0xffff),
|
||||||
|
ber.writeOctetstring(cc_req_stream.getvalue()))
|
||||||
|
await self.x224.write((ber.writeApplicationTag(Message.MCS_TYPE_CONNECT_INITIAL, sizeof(tmp)), tmp))
|
||||||
|
|
||||||
|
async def read_connect_response(self):
|
||||||
|
payload = await self.x224.read()
|
||||||
|
ber.readApplicationTag(payload, UInt8(Message.MCS_TYPE_CONNECT_RESPONSE))
|
||||||
|
ber.readEnumerated(payload)
|
||||||
|
ber.readInteger(payload)
|
||||||
|
read_domain_params(payload)
|
||||||
|
if not ber.readUniversalTag(payload, ber.Tag.BER_TAG_OCTET_STRING, False):
|
||||||
|
raise InvalidExpectedDataException("invalid expected BER tag")
|
||||||
|
gccRequestLength = ber.readLength(payload)
|
||||||
|
if payload.data_len() != gccRequestLength:
|
||||||
|
raise InvalidSize("bad size of GCC request")
|
||||||
|
gcc.readConferenceCreateResponse(payload)
|
||||||
|
|
||||||
|
|
||||||
|
async def connect(self):
|
||||||
|
await self.write_connect_initial()
|
||||||
|
await self.read_connect_response()
|
||||||
|
await self.x224.write(erect_domain_request())
|
||||||
|
await self.x224.write(attach_user_request())
|
||||||
|
user_id = read_attach_confirm(await self.x224.read())
|
||||||
|
self.channel_ids["global"] = 1003
|
||||||
|
self.channel_ids["user"] = user_id
|
||||||
|
|
||||||
|
# connect all channels
|
||||||
|
for channel_id in self.channel_ids.values():
|
||||||
|
await self.x224.write(channel_join_request(user_id, channel_id))
|
||||||
|
if not channel_join_confirm(user_id, channel_id, await self.x224.read()):
|
||||||
|
print("Server refused channel %s"%channel_id)
|
||||||
|
|
||||||
|
|
||||||
class MCSLayer(LayerAutomata):
|
class MCSLayer(LayerAutomata):
|
||||||
"""
|
"""
|
||||||
@@ -98,11 +239,13 @@ class MCSLayer(LayerAutomata):
|
|||||||
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, IGCCConfig):
|
class MCSProxySender(Layer, IStreamSender, IGCCConfig):
|
||||||
"""
|
"""
|
||||||
@summary: 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, presentation, mcs, channelId):
|
def __init__(self, presentation, mcs, channelId):
|
||||||
"""
|
"""
|
||||||
@param presentation: {Layer} presentation layer
|
@param presentation: {Layer} presentation layer
|
||||||
@@ -155,7 +298,6 @@ class MCSLayer(LayerAutomata):
|
|||||||
"""
|
"""
|
||||||
return self._mcs._serverSettings
|
return self._mcs._serverSettings
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, presentation, receiveOpcode, sendOpcode, virtualChannels=[]):
|
def __init__(self, presentation, receiveOpcode, sendOpcode, virtualChannels=[]):
|
||||||
"""
|
"""
|
||||||
@param presentation: {Layer} presentation layer
|
@param presentation: {Layer} presentation layer
|
||||||
@@ -182,7 +324,7 @@ class MCSLayer(LayerAutomata):
|
|||||||
@summary: 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), Buffer(b"\x00" * 6)))
|
||||||
self._transport.close()
|
self._transport.close()
|
||||||
|
|
||||||
def allChannelConnected(self):
|
def allChannelConnected(self):
|
||||||
@@ -216,7 +358,7 @@ class MCSLayer(LayerAutomata):
|
|||||||
@param data: {Stream}
|
@param data: {Stream}
|
||||||
"""
|
"""
|
||||||
opcode = UInt8()
|
opcode = UInt8()
|
||||||
data.readType(opcode)
|
data.read_type(opcode)
|
||||||
|
|
||||||
if self.readMCSPDUHeader(opcode.value, DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM):
|
if self.readMCSPDUHeader(opcode.value, DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM):
|
||||||
log.info("MCS DISCONNECT_PROVIDER_ULTIMATUM")
|
log.info("MCS DISCONNECT_PROVIDER_ULTIMATUM")
|
||||||
@@ -294,16 +436,19 @@ class MCSLayer(LayerAutomata):
|
|||||||
ber.readInteger(s)
|
ber.readInteger(s)
|
||||||
return (max_channels, max_users, max_tokens, max_pdu_size)
|
return (max_channels, max_users, max_tokens, max_pdu_size)
|
||||||
|
|
||||||
class Client(MCSLayer):
|
|
||||||
|
class ClientOld(MCSLayer):
|
||||||
"""
|
"""
|
||||||
@summary: 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: {Layer} presentation layer
|
@param presentation: {Layer} presentation layer
|
||||||
@param virtualChannels: {Array(Layer)} 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
|
||||||
self._isGlobalChannelRequested = False
|
self._isGlobalChannelRequested = False
|
||||||
self._isUserChannelRequested = False
|
self._isUserChannelRequested = False
|
||||||
@@ -344,8 +489,8 @@ class Client(MCSLayer):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# static virtual channel
|
# static virtual channel
|
||||||
if self._nbChannelRequested < self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelCount.value:
|
if self._nbChannelRequested < self._serverSettings.get_block(gcc.MessageType.SC_NET).channelCount.value:
|
||||||
channelId = self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray[self._nbChannelRequested]
|
channelId = self._serverSettings.get_block(gcc.MessageType.SC_NET).channelIdArray[self._nbChannelRequested]
|
||||||
self._nbChannelRequested += 1
|
self._nbChannelRequested += 1
|
||||||
self.sendChannelJoinRequest(channelId)
|
self.sendChannelJoinRequest(channelId)
|
||||||
return
|
return
|
||||||
@@ -367,7 +512,7 @@ class Client(MCSLayer):
|
|||||||
if not ber.readUniversalTag(data, ber.Tag.BER_TAG_OCTET_STRING, False):
|
if not ber.readUniversalTag(data, ber.Tag.BER_TAG_OCTET_STRING, False):
|
||||||
raise InvalidExpectedDataException("invalid expected BER tag")
|
raise InvalidExpectedDataException("invalid expected BER tag")
|
||||||
gccRequestLength = ber.readLength(data)
|
gccRequestLength = ber.readLength(data)
|
||||||
if data.dataLen() != gccRequestLength:
|
if data.data_len() != gccRequestLength:
|
||||||
raise InvalidSize("bad size of GCC request")
|
raise InvalidSize("bad size of GCC request")
|
||||||
self._serverSettings = gcc.readConferenceCreateResponse(data)
|
self._serverSettings = gcc.readConferenceCreateResponse(data)
|
||||||
|
|
||||||
@@ -385,7 +530,7 @@ class Client(MCSLayer):
|
|||||||
@param data: {Stream}
|
@param data: {Stream}
|
||||||
"""
|
"""
|
||||||
opcode = UInt8()
|
opcode = UInt8()
|
||||||
data.readType(opcode)
|
data.read_type(opcode)
|
||||||
|
|
||||||
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ATTACH_USER_CONFIRM):
|
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ATTACH_USER_CONFIRM):
|
||||||
raise InvalidExpectedDataException("Invalid MCS PDU : ATTACH_USER_CONFIRM expected")
|
raise InvalidExpectedDataException("Invalid MCS PDU : ATTACH_USER_CONFIRM expected")
|
||||||
@@ -404,7 +549,7 @@ class Client(MCSLayer):
|
|||||||
@param data: {Stream}
|
@param data: {Stream}
|
||||||
"""
|
"""
|
||||||
opcode = UInt8()
|
opcode = UInt8()
|
||||||
data.readType(opcode)
|
data.read_type(opcode)
|
||||||
|
|
||||||
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.CHANNEL_JOIN_CONFIRM):
|
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.CHANNEL_JOIN_CONFIRM):
|
||||||
raise InvalidExpectedDataException("Invalid MCS PDU : CHANNEL_JOIN_CONFIRM expected")
|
raise InvalidExpectedDataException("Invalid MCS PDU : CHANNEL_JOIN_CONFIRM expected")
|
||||||
@@ -421,7 +566,7 @@ class Client(MCSLayer):
|
|||||||
raise InvalidExpectedDataException("Server must confirm static channel")
|
raise InvalidExpectedDataException("Server must confirm static channel")
|
||||||
|
|
||||||
if confirm == 0:
|
if confirm == 0:
|
||||||
serverNet = self._serverSettings.getBlock(gcc.MessageType.SC_NET)
|
serverNet = self._serverSettings.get_block(gcc.MessageType.SC_NET)
|
||||||
for i in range(0, serverNet.channelCount.value):
|
for i in range(0, serverNet.channelCount.value):
|
||||||
if channelId == serverNet.channelIdArray[i].value:
|
if channelId == serverNet.channelIdArray[i].value:
|
||||||
self._channels[channelId] = self._virtualChannels[i][1]
|
self._channels[channelId] = self._virtualChannels[i][1]
|
||||||
@@ -435,7 +580,7 @@ class Client(MCSLayer):
|
|||||||
"""
|
"""
|
||||||
ccReq = gcc.writeConferenceCreateRequest(self._clientSettings)
|
ccReq = gcc.writeConferenceCreateRequest(self._clientSettings)
|
||||||
ccReqStream = Stream()
|
ccReqStream = Stream()
|
||||||
ccReqStream.writeType(ccReq)
|
ccReqStream.write_type(ccReq)
|
||||||
|
|
||||||
tmp = (ber.writeOctetstring("\x01"), ber.writeOctetstring("\x01"), ber.writeBoolean(True),
|
tmp = (ber.writeOctetstring("\x01"), ber.writeOctetstring("\x01"), ber.writeBoolean(True),
|
||||||
self.writeDomainParams(34, 2, 0, 0xffff),
|
self.writeDomainParams(34, 2, 0, 0xffff),
|
||||||
@@ -468,16 +613,19 @@ class Client(MCSLayer):
|
|||||||
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
|
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
|
||||||
per.writeInteger16(channelId)))
|
per.writeInteger16(channelId)))
|
||||||
|
|
||||||
|
|
||||||
class Server(MCSLayer):
|
class Server(MCSLayer):
|
||||||
"""
|
"""
|
||||||
@summary: 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: {Layer} presentation layer
|
@param presentation: {Layer} presentation layer
|
||||||
@param virtualChannels: {List(Layer)} 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
|
||||||
self._nbChannelConfirmed = 0
|
self._nbChannelConfirmed = 0
|
||||||
|
|
||||||
@@ -488,7 +636,6 @@ class Server(MCSLayer):
|
|||||||
"""
|
"""
|
||||||
# basic rdp security layer
|
# basic rdp security layer
|
||||||
if self._transport._selectedProtocol == 0:
|
if self._transport._selectedProtocol == 0:
|
||||||
|
|
||||||
self._serverSettings.SC_SECURITY.encryptionMethod.value = gcc.EncryptionMethod.ENCRYPTION_FLAG_128BIT
|
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.encryptionLevel.value = gcc.EncryptionLevel.ENCRYPTION_LEVEL_HIGH
|
||||||
self._serverSettings.SC_SECURITY.serverRandom.value = rsa.random(256)
|
self._serverSettings.SC_SECURITY.serverRandom.value = rsa.random(256)
|
||||||
@@ -516,9 +663,10 @@ class Server(MCSLayer):
|
|||||||
self.readDomainParams(data)
|
self.readDomainParams(data)
|
||||||
self._clientSettings = gcc.readConferenceCreateRequest(Stream(ber.readOctetString(data)))
|
self._clientSettings = gcc.readConferenceCreateRequest(Stream(ber.readOctetString(data)))
|
||||||
|
|
||||||
|
if not self._clientSettings.CS_NET is None:
|
||||||
i = 1
|
i = 1
|
||||||
for channelDef in self._clientSettings.getBlock(gcc.MessageType.CS_NET).channelDefArray._array:
|
for channelDef in self._clientSettings.CS_NET.channelDefArray._array:
|
||||||
self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray._array.append(UInt16Le(i + Channel.MCS_GLOBAL_CHANNEL))
|
self._serverSettings.SC_NET.channelIdArray._array.append(UInt16Le(i + Channel.MCS_GLOBAL_CHANNEL))
|
||||||
# if channel can be handle by serve add it
|
# if channel can be handle by serve add it
|
||||||
for serverChannelDef, layer in self._virtualChannels:
|
for serverChannelDef, layer in self._virtualChannels:
|
||||||
if channelDef.name == serverChannelDef.name:
|
if channelDef.name == serverChannelDef.name:
|
||||||
@@ -535,7 +683,7 @@ class Server(MCSLayer):
|
|||||||
@param data: {Stream}
|
@param data: {Stream}
|
||||||
"""
|
"""
|
||||||
opcode = UInt8()
|
opcode = UInt8()
|
||||||
data.readType(opcode)
|
data.read_type(opcode)
|
||||||
|
|
||||||
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ERECT_DOMAIN_REQUEST):
|
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ERECT_DOMAIN_REQUEST):
|
||||||
raise InvalidExpectedDataException("Invalid MCS PDU : ERECT_DOMAIN_REQUEST expected")
|
raise InvalidExpectedDataException("Invalid MCS PDU : ERECT_DOMAIN_REQUEST expected")
|
||||||
@@ -553,7 +701,7 @@ class Server(MCSLayer):
|
|||||||
@param data: {Stream}
|
@param data: {Stream}
|
||||||
"""
|
"""
|
||||||
opcode = UInt8()
|
opcode = UInt8()
|
||||||
data.readType(opcode)
|
data.read_type(opcode)
|
||||||
|
|
||||||
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ATTACH_USER_REQUEST):
|
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ATTACH_USER_REQUEST):
|
||||||
raise InvalidExpectedDataException("Invalid MCS PDU : ATTACH_USER_REQUEST expected")
|
raise InvalidExpectedDataException("Invalid MCS PDU : ATTACH_USER_REQUEST expected")
|
||||||
@@ -569,7 +717,7 @@ class Server(MCSLayer):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
opcode = UInt8()
|
opcode = UInt8()
|
||||||
data.readType(opcode)
|
data.read_type(opcode)
|
||||||
|
|
||||||
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.CHANNEL_JOIN_REQUEST):
|
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.CHANNEL_JOIN_REQUEST):
|
||||||
raise InvalidExpectedDataException("Invalid MCS PDU : CHANNEL_JOIN_REQUEST expected")
|
raise InvalidExpectedDataException("Invalid MCS PDU : CHANNEL_JOIN_REQUEST expected")
|
||||||
@@ -583,7 +731,7 @@ class Server(MCSLayer):
|
|||||||
confirm = 0 if channelId in self._channels.keys() or channelId == self._userId else 1
|
confirm = 0 if channelId in self._channels.keys() or channelId == self._userId else 1
|
||||||
self.sendChannelJoinConfirm(channelId, confirm)
|
self.sendChannelJoinConfirm(channelId, confirm)
|
||||||
self._nbChannelConfirmed += 1
|
self._nbChannelConfirmed += 1
|
||||||
if self._nbChannelConfirmed == self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelCount.value + 2:
|
if self._nbChannelConfirmed == self._serverSettings.get_block(gcc.MessageType.SC_NET).channelCount.value + 2:
|
||||||
self.allChannelConnected()
|
self.allChannelConnected()
|
||||||
|
|
||||||
def sendConnectResponse(self):
|
def sendConnectResponse(self):
|
||||||
@@ -592,7 +740,7 @@ class Server(MCSLayer):
|
|||||||
"""
|
"""
|
||||||
ccReq = gcc.writeConferenceCreateResponse(self._serverSettings)
|
ccReq = gcc.writeConferenceCreateResponse(self._serverSettings)
|
||||||
ccReqStream = Stream()
|
ccReqStream = Stream()
|
||||||
ccReqStream.writeType(ccReq)
|
ccReqStream.write_type(ccReq)
|
||||||
|
|
||||||
tmp = (ber.writeEnumerated(0), ber.writeInteger(0), self.writeDomainParams(22, 3, 0, 0xfff8),
|
tmp = (ber.writeEnumerated(0), ber.writeInteger(0), self.writeDomainParams(22, 3, 0, 0xfff8),
|
||||||
ber.writeOctetstring(ccReqStream.getvalue()))
|
ber.writeOctetstring(ccReqStream.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.
|
||||||
#
|
#
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
Per encoded function
|
Per encoded function
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.core.type import UInt8, UInt16Be, UInt32Be, String
|
from rdpy.model.message import UInt8, UInt16Be, UInt32Be, Buffer
|
||||||
from rdpy.core.error import InvalidValue, InvalidExpectedDataException
|
from rdpy.model.error import InvalidValue, InvalidExpectedDataException
|
||||||
|
|
||||||
def readLength(s):
|
def readLength(s):
|
||||||
"""
|
"""
|
||||||
@@ -31,12 +31,12 @@ def readLength(s):
|
|||||||
@return: int python
|
@return: int python
|
||||||
"""
|
"""
|
||||||
byte = UInt8()
|
byte = UInt8()
|
||||||
s.readType(byte)
|
s.read_type(byte)
|
||||||
size = 0
|
size = 0
|
||||||
if byte.value & 0x80:
|
if byte.value & 0x80:
|
||||||
byte.value &= ~0x80
|
byte.value &= ~0x80
|
||||||
size = byte.value << 8
|
size = byte.value << 8
|
||||||
s.readType(byte)
|
s.read_type(byte)
|
||||||
size += byte.value
|
size += byte.value
|
||||||
else:
|
else:
|
||||||
size = byte.value
|
size = byte.value
|
||||||
@@ -60,7 +60,7 @@ def readChoice(s):
|
|||||||
@return: int that represent choice
|
@return: int that represent choice
|
||||||
"""
|
"""
|
||||||
choice = UInt8()
|
choice = UInt8()
|
||||||
s.readType(choice)
|
s.read_type(choice)
|
||||||
return choice.value
|
return choice.value
|
||||||
|
|
||||||
def writeChoice(choice):
|
def writeChoice(choice):
|
||||||
@@ -78,7 +78,7 @@ def readSelection(s):
|
|||||||
@return: int that represent selection
|
@return: int that represent selection
|
||||||
"""
|
"""
|
||||||
choice = UInt8()
|
choice = UInt8()
|
||||||
s.readType(choice)
|
s.read_type(choice)
|
||||||
return choice.value
|
return choice.value
|
||||||
|
|
||||||
def writeSelection(selection):
|
def writeSelection(selection):
|
||||||
@@ -96,7 +96,7 @@ def readNumberOfSet(s):
|
|||||||
@return: int that represent numberOfSet
|
@return: int that represent numberOfSet
|
||||||
"""
|
"""
|
||||||
choice = UInt8()
|
choice = UInt8()
|
||||||
s.readType(choice)
|
s.read_type(choice)
|
||||||
return choice.value
|
return choice.value
|
||||||
|
|
||||||
def writeNumberOfSet(numberOfSet):
|
def writeNumberOfSet(numberOfSet):
|
||||||
@@ -114,7 +114,7 @@ def readEnumerates(s):
|
|||||||
@return: int that represent enumerate
|
@return: int that represent enumerate
|
||||||
"""
|
"""
|
||||||
choice = UInt8()
|
choice = UInt8()
|
||||||
s.readType(choice)
|
s.read_type(choice)
|
||||||
return choice.value
|
return choice.value
|
||||||
|
|
||||||
def writeEnumerates(enumer):
|
def writeEnumerates(enumer):
|
||||||
@@ -142,7 +142,7 @@ def readInteger(s):
|
|||||||
result = UInt32Be()
|
result = UInt32Be()
|
||||||
else:
|
else:
|
||||||
raise InvalidValue("invalid integer size %d"%size)
|
raise InvalidValue("invalid integer size %d"%size)
|
||||||
s.readType(result)
|
s.read_type(result)
|
||||||
return result.value
|
return result.value
|
||||||
|
|
||||||
def writeInteger(value):
|
def writeInteger(value):
|
||||||
@@ -166,7 +166,7 @@ def readInteger16(s, minimum = 0):
|
|||||||
@return: int or long python value
|
@return: int or long python value
|
||||||
"""
|
"""
|
||||||
result = UInt16Be()
|
result = UInt16Be()
|
||||||
s.readType(result)
|
s.read_type(result)
|
||||||
return result.value + minimum
|
return result.value + minimum
|
||||||
|
|
||||||
def writeInteger16(value, minimum = 0):
|
def writeInteger16(value, minimum = 0):
|
||||||
@@ -190,16 +190,16 @@ def readObjectIdentifier(s, oid):
|
|||||||
raise InvalidValue("size of stream oid is wrong %d != 5"%size)
|
raise InvalidValue("size of stream oid is wrong %d != 5"%size)
|
||||||
a_oid = [0, 0, 0, 0, 0, 0]
|
a_oid = [0, 0, 0, 0, 0, 0]
|
||||||
t12 = UInt8()
|
t12 = UInt8()
|
||||||
s.readType(t12)
|
s.read_type(t12)
|
||||||
a_oid[0] = t12.value >> 4
|
a_oid[0] = t12.value >> 4
|
||||||
a_oid[1] = t12.value & 0x0f
|
a_oid[1] = t12.value & 0x0f
|
||||||
s.readType(t12)
|
s.read_type(t12)
|
||||||
a_oid[2] = t12.value
|
a_oid[2] = t12.value
|
||||||
s.readType(t12)
|
s.read_type(t12)
|
||||||
a_oid[3] = t12.value
|
a_oid[3] = t12.value
|
||||||
s.readType(t12)
|
s.read_type(t12)
|
||||||
a_oid[4] = t12.value
|
a_oid[4] = t12.value
|
||||||
s.readType(t12)
|
s.read_type(t12)
|
||||||
a_oid[5] = t12.value
|
a_oid[5] = t12.value
|
||||||
|
|
||||||
if list(oid) != a_oid:
|
if list(oid) != a_oid:
|
||||||
@@ -223,12 +223,9 @@ def readNumericString(s, minValue):
|
|||||||
length = (length + minValue + 1) / 2
|
length = (length + minValue + 1) / 2
|
||||||
s.read(length)
|
s.read(length)
|
||||||
|
|
||||||
|
|
||||||
def writeNumericString(nStr, minValue):
|
def writeNumericString(nStr, minValue):
|
||||||
"""
|
"""
|
||||||
@summary: write string in per format
|
|
||||||
@param str: python string to write
|
|
||||||
@param min: min value
|
|
||||||
@return: String type that contain str encoded in per format
|
|
||||||
"""
|
"""
|
||||||
length = len(nStr)
|
length = len(nStr)
|
||||||
mlength = minValue
|
mlength = minValue
|
||||||
@@ -238,9 +235,9 @@ def writeNumericString(nStr, minValue):
|
|||||||
result = []
|
result = []
|
||||||
|
|
||||||
for i in range(0, length, 2):
|
for i in range(0, length, 2):
|
||||||
c1 = ord(nStr[i])
|
c1 = nStr[i]
|
||||||
if i + 1 < length:
|
if i + 1 < length:
|
||||||
c2 = ord(nStr[i + 1])
|
c2 = nStr[i + 1]
|
||||||
else:
|
else:
|
||||||
c2 = 0x30
|
c2 = 0x30
|
||||||
c1 = (c1 - 0x30) % 10
|
c1 = (c1 - 0x30) % 10
|
||||||
@@ -248,7 +245,7 @@ def writeNumericString(nStr, minValue):
|
|||||||
|
|
||||||
result.append(UInt8((c1 << 4) | c2))
|
result.append(UInt8((c1 << 4) | c2))
|
||||||
|
|
||||||
return (writeLength(mlength), tuple(result))
|
return writeLength(mlength), tuple(result)
|
||||||
|
|
||||||
def readPadding(s, length):
|
def readPadding(s, length):
|
||||||
"""
|
"""
|
||||||
@@ -264,7 +261,7 @@ def writePadding(length):
|
|||||||
@param length: length of padding
|
@param length: length of padding
|
||||||
@return: String with \x00 * length
|
@return: String with \x00 * length
|
||||||
"""
|
"""
|
||||||
return String("\x00"*length)
|
return Buffer(b"\x00" * length)
|
||||||
|
|
||||||
def readOctetStream(s, octetStream, minValue = 0):
|
def readOctetStream(s, octetStream, minValue = 0):
|
||||||
"""
|
"""
|
||||||
@@ -276,11 +273,11 @@ def readOctetStream(s, octetStream, minValue = 0):
|
|||||||
"""
|
"""
|
||||||
size = readLength(s) + minValue
|
size = readLength(s) + minValue
|
||||||
if size != len(octetStream):
|
if size != len(octetStream):
|
||||||
raise InvalidValue("incompatible size %d != %d"(len(octetStream), size))
|
raise InvalidValue("incompatible size %d != %d"%(len(octetStream), size))
|
||||||
for i in range(0, size):
|
for i in range(0, size):
|
||||||
c = UInt8()
|
c = UInt8()
|
||||||
s.readType(c)
|
s.read_type(c)
|
||||||
if ord(octetStream[i]) != c.value:
|
if octetStream[i] != c.value:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -300,6 +297,6 @@ def writeOctetStream(oStr, minValue = 0):
|
|||||||
|
|
||||||
result = []
|
result = []
|
||||||
for i in range(0, length):
|
for i in range(0, length):
|
||||||
result.append(UInt8(ord(oStr[i])))
|
result.append(UInt8(oStr[i]))
|
||||||
|
|
||||||
return (writeLength(mlength), tuple(result))
|
return writeLength(mlength), tuple(result)
|
||||||
@@ -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,11 +22,15 @@ 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.core.layer import RawLayer
|
import asyncio
|
||||||
from rdpy.core.type import UInt8, UInt16Be, sizeof
|
import ssl
|
||||||
from rdpy.core.error import CallPureVirtualFuntion
|
|
||||||
|
|
||||||
class Action(object):
|
from rdpy.core.nla import cssp, sspi
|
||||||
|
from rdpy.model.layer import RawLayer
|
||||||
|
from rdpy.model.message import UInt8, UInt16Be, sizeof, Stream
|
||||||
|
|
||||||
|
|
||||||
|
class Action:
|
||||||
"""
|
"""
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240621.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240621.aspx
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240589.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240589.aspx
|
||||||
@@ -34,102 +38,25 @@ class Action(object):
|
|||||||
FASTPATH_ACTION_FASTPATH = 0x0
|
FASTPATH_ACTION_FASTPATH = 0x0
|
||||||
FASTPATH_ACTION_X224 = 0x3
|
FASTPATH_ACTION_X224 = 0x3
|
||||||
|
|
||||||
class SecFlags(object):
|
|
||||||
|
class SecFlags:
|
||||||
"""
|
"""
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240621.aspx
|
@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_SECURE_CHECKSUM = 0x1
|
||||||
FASTPATH_OUTPUT_ENCRYPTED = 0x2
|
FASTPATH_OUTPUT_ENCRYPTED = 0x2
|
||||||
|
|
||||||
class IFastPathListener(object):
|
|
||||||
"""
|
|
||||||
@summary: Fast path packet listener
|
|
||||||
Usually X224 layer
|
|
||||||
"""
|
|
||||||
def recvFastPath(self, secFlag, fastPathS):
|
|
||||||
"""
|
|
||||||
@summary: Call when fast path packet is received
|
|
||||||
@param secFlag: {SecFlags}
|
|
||||||
@param fastPathS: {Stream}
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recvFastPath", "IFastPathListener"))
|
|
||||||
|
|
||||||
def initFastPath(self, fastPathSender):
|
class Tpkt:
|
||||||
"""
|
|
||||||
@summary: initialize stack
|
|
||||||
@param fastPathSender: {IFastPathSender}
|
|
||||||
"""
|
|
||||||
self.setFastPathSender(fastPathSender)
|
|
||||||
fastPathSender.setFastPathListener(self)
|
|
||||||
|
|
||||||
def setFastPathSender(self, fastPathSender):
|
|
||||||
"""
|
|
||||||
@param fastPathSender : {IFastPathSender}
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "setFastPathSender", "IFastPathListener"))
|
|
||||||
|
|
||||||
class IFastPathSender(object):
|
|
||||||
"""
|
|
||||||
@summary: Fast path send capability
|
|
||||||
"""
|
|
||||||
def sendFastPath(self, secFlag, fastPathS):
|
|
||||||
"""
|
|
||||||
@summary: Send fastPathS Type as fast path packet
|
|
||||||
@param secFlag: {integer} Security flag for fastpath packet
|
|
||||||
@param fastPathS: {Type | Tuple} type transform to stream and send as fastpath
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendFastPath", "IFastPathSender"))
|
|
||||||
|
|
||||||
def initFastPath(self, fastPathListener):
|
|
||||||
"""
|
|
||||||
@summary: initialize stack
|
|
||||||
@param fastPathListener: {IFastPathListener}
|
|
||||||
"""
|
|
||||||
self.setFastPathListener(fastPathListener)
|
|
||||||
fastPathListener.setFastPathSender(self)
|
|
||||||
|
|
||||||
def setFastPathListener(self, fastPathListener):
|
|
||||||
"""
|
|
||||||
@param fastPathListener: {IFastPathListener}
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "setFastPathListener", "IFastPathSender"))
|
|
||||||
|
|
||||||
class TPKT(RawLayer, IFastPathSender):
|
|
||||||
"""
|
"""
|
||||||
@summary: TPKT layer in RDP protocol stack
|
@summary: TPKT layer in RDP protocol stack
|
||||||
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):
|
|
||||||
"""
|
|
||||||
@param presentation: {Layer} presentation layer, in RDP case is x224 layer
|
|
||||||
"""
|
|
||||||
RawLayer.__init__(self, presentation)
|
|
||||||
#length may be coded on more than 1 bytes
|
|
||||||
self._lastShortLength = UInt8()
|
|
||||||
#fast path listener
|
|
||||||
self._fastPathListener = None
|
|
||||||
#last secure flag
|
|
||||||
self._secFlag = 0
|
|
||||||
|
|
||||||
def setFastPathListener(self, fastPathListener):
|
def __init__(self, reader, writer):
|
||||||
"""
|
self.reader = reader
|
||||||
@param fastPathListener : {IFastPathListener}
|
self.writer = writer
|
||||||
@note: implement IFastPathSender
|
|
||||||
"""
|
|
||||||
self._fastPathListener = fastPathListener
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
"""
|
|
||||||
@summary: Call when transport layer connection
|
|
||||||
is made (inherit from RawLayer)
|
|
||||||
"""
|
|
||||||
#header is on two bytes
|
|
||||||
self.expect(2, self.readHeader)
|
|
||||||
#no connection automata on this layer
|
|
||||||
if not self._presentation is None:
|
|
||||||
self._presentation.connect()
|
|
||||||
|
|
||||||
def readHeader(self, data):
|
def readHeader(self, data):
|
||||||
"""
|
"""
|
||||||
@@ -138,17 +65,17 @@ class TPKT(RawLayer, IFastPathSender):
|
|||||||
"""
|
"""
|
||||||
#first read packet version
|
#first read packet version
|
||||||
version = UInt8()
|
version = UInt8()
|
||||||
data.readType(version)
|
data.read_type(version)
|
||||||
#classic packet
|
#classic packet
|
||||||
if version.value == Action.FASTPATH_ACTION_X224:
|
if version.value == Action.FASTPATH_ACTION_X224:
|
||||||
#padding
|
#padding
|
||||||
data.readType(UInt8())
|
data.read_type(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)
|
self._secFlag = ((version.value >> 6) & 0x3)
|
||||||
data.readType(self._lastShortLength)
|
data.read_type(self._lastShortLength)
|
||||||
if self._lastShortLength.value & 0x80:
|
if self._lastShortLength.value & 0x80:
|
||||||
#size is 1 byte more
|
#size is 1 byte more
|
||||||
self.expect(1, self.readExtendedFastPathHeader)
|
self.expect(1, self.readExtendedFastPathHeader)
|
||||||
@@ -163,7 +90,7 @@ class TPKT(RawLayer, IFastPathSender):
|
|||||||
"""
|
"""
|
||||||
#next state is read data
|
#next state is read data
|
||||||
size = UInt16Be()
|
size = UInt16Be()
|
||||||
data.readType(size)
|
data.read_type(size)
|
||||||
self.expect(size.value - 4, self.readData)
|
self.expect(size.value - 4, self.readData)
|
||||||
|
|
||||||
def readExtendedFastPathHeader(self, data):
|
def readExtendedFastPathHeader(self, data):
|
||||||
@@ -172,7 +99,7 @@ class TPKT(RawLayer, IFastPathSender):
|
|||||||
@param data: {Stream} from twisted layer
|
@param data: {Stream} from twisted layer
|
||||||
"""
|
"""
|
||||||
leftPart = UInt8()
|
leftPart = UInt8()
|
||||||
data.readType(leftPart)
|
data.read_type(leftPart)
|
||||||
self._lastShortLength.value &= ~0x80
|
self._lastShortLength.value &= ~0x80
|
||||||
packetSize = (self._lastShortLength.value << 8) + leftPart.value
|
packetSize = (self._lastShortLength.value << 8) + leftPart.value
|
||||||
#next state is fast patn data
|
#next state is fast patn data
|
||||||
@@ -195,16 +122,58 @@ class TPKT(RawLayer, IFastPathSender):
|
|||||||
self._presentation.recv(data)
|
self._presentation.recv(data)
|
||||||
self.expect(2, self.readHeader)
|
self.expect(2, self.readHeader)
|
||||||
|
|
||||||
def send(self, message):
|
async def write(self, message):
|
||||||
"""
|
"""
|
||||||
@summary: 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))
|
s = Stream()
|
||||||
|
s.write_type((UInt8(Action.FASTPATH_ACTION_X224), UInt8(0), UInt16Be(sizeof(message) + 4), message))
|
||||||
|
self.writer.write(s.getvalue())
|
||||||
|
await self.writer.drain()
|
||||||
|
|
||||||
|
async def read(self):
|
||||||
|
"""
|
||||||
|
Read an entire payload from the reader stream
|
||||||
|
"""
|
||||||
|
header = Stream(await self.reader.readexactly(2))
|
||||||
|
action = UInt8()
|
||||||
|
header.read_type(action)
|
||||||
|
if action.value == Action.FASTPATH_ACTION_X224:
|
||||||
|
# read padding
|
||||||
|
header.read_type(UInt8())
|
||||||
|
|
||||||
|
size = UInt16Be()
|
||||||
|
Stream(await self.reader.readexactly(2)).read_type(size)
|
||||||
|
return Stream(await self.reader.readexactly(size.value - 4))
|
||||||
|
|
||||||
|
|
||||||
def sendFastPath(self, secFlag, fastPathS):
|
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
|
@param secFlag: {integer} Security flag for fastpath packet
|
||||||
"""
|
"""
|
||||||
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH | ((secFlag & 0x3) << 6)), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS))
|
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH | ((secFlag & 0x3) << 6)), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS))
|
||||||
|
|
||||||
|
async def start_tls(self):
|
||||||
|
"""
|
||||||
|
Start TLS protocol
|
||||||
|
"""
|
||||||
|
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||||
|
ssl_ctx.check_hostname = False
|
||||||
|
ssl_ctx.verify_mode = ssl.VerifyMode.CERT_NONE
|
||||||
|
|
||||||
|
reader, writer = await asyncio.open_connection(sock=self.writer.transport._sock, ssl=ssl_ctx, server_hostname="")
|
||||||
|
return Tpkt(reader, writer)
|
||||||
|
|
||||||
|
async def start_nla(self, authentication_protocol: sspi.IAuthenticationProtocol):
|
||||||
|
"""
|
||||||
|
use to start NLA (NTLM over SSL) protocol
|
||||||
|
must be called after startTLS function
|
||||||
|
|
||||||
|
:ivar authentication_protocol: Authentication protocol use by CSSP to authenticate user
|
||||||
|
and transfert credentials
|
||||||
|
"""
|
||||||
|
tpkt = await self.start_tls()
|
||||||
|
await cssp.connect(tpkt.reader, tpkt.writer, authentication_protocol)
|
||||||
|
return tpkt
|
||||||
@@ -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,13 +23,15 @@ 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 supported only on client side
|
RDP basic security is supported only on client side
|
||||||
"""
|
"""
|
||||||
from rdpy.core import log
|
from rdpy.core import tpkt
|
||||||
|
from rdpy.core.nla import sspi
|
||||||
|
from rdpy.model import log
|
||||||
|
|
||||||
from rdpy.core.layer import LayerAutomata, IStreamSender
|
from rdpy.model.message import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, Buffer, Stream
|
||||||
from rdpy.core.type import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, String
|
from rdpy.model.error import InvalidExpectedDataException, RDPSecurityNegoFail
|
||||||
from rdpy.core.error import InvalidExpectedDataException, RDPSecurityNegoFail
|
|
||||||
|
|
||||||
class MessageType(object):
|
|
||||||
|
class MessageType:
|
||||||
"""
|
"""
|
||||||
@summary: Message type
|
@summary: Message type
|
||||||
"""
|
"""
|
||||||
@@ -39,7 +41,8 @@ class MessageType(object):
|
|||||||
X224_TPDU_DATA = 0xF0
|
X224_TPDU_DATA = 0xF0
|
||||||
X224_TPDU_ERROR = 0x70
|
X224_TPDU_ERROR = 0x70
|
||||||
|
|
||||||
class NegociationType(object):
|
|
||||||
|
class NegociationType:
|
||||||
"""
|
"""
|
||||||
@summary: Negotiation header
|
@summary: Negotiation header
|
||||||
"""
|
"""
|
||||||
@@ -47,16 +50,19 @@ class NegociationType(object):
|
|||||||
TYPE_RDP_NEG_RSP = 0x02
|
TYPE_RDP_NEG_RSP = 0x02
|
||||||
TYPE_RDP_NEG_FAILURE = 0x03
|
TYPE_RDP_NEG_FAILURE = 0x03
|
||||||
|
|
||||||
class Protocols(object):
|
|
||||||
|
class Protocols:
|
||||||
"""
|
"""
|
||||||
@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
|
||||||
PROTOCOL_HYBRID = 0x00000002
|
PROTOCOL_HYBRID = 0x00000002
|
||||||
PROTOCOL_HYBRID_EX = 0x00000008
|
PROTOCOL_HYBRID_EX = 0x00000008
|
||||||
|
|
||||||
class NegotiationFailureCode(object):
|
|
||||||
|
class NegotiationFailureCode:
|
||||||
"""
|
"""
|
||||||
@summary: Protocol negotiation failure code
|
@summary: Protocol negotiation failure code
|
||||||
"""
|
"""
|
||||||
@@ -67,22 +73,24 @@ class NegotiationFailureCode(object):
|
|||||||
HYBRID_REQUIRED_BY_SERVER = 0x00000005
|
HYBRID_REQUIRED_BY_SERVER = 0x00000005
|
||||||
SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 0x00000006
|
SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 0x00000006
|
||||||
|
|
||||||
class ClientConnectionRequestPDU(CompositeType):
|
|
||||||
|
class ConnectionRequestPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Connection request
|
Connection Request PDU
|
||||||
client -> server
|
Use to send protocol security level available for the client
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240470.aspx
|
:see: http://msdn.microsoft.com/en-us/library/cc240470.aspx
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
self.len = UInt8(lambda:sizeof(self) - 1)
|
self.len = UInt8(lambda:sizeof(self) - 1)
|
||||||
self.code = UInt8(MessageType.X224_TPDU_CONNECTION_REQUEST, constant=True)
|
self.code = UInt8(MessageType.X224_TPDU_CONNECTION_REQUEST, constant=True)
|
||||||
self.padding = (UInt16Be(), UInt16Be(), UInt8())
|
self.padding = (UInt16Be(), UInt16Be(), UInt8())
|
||||||
self.cookie = String(until = "\x0d\x0a", conditional = lambda:(self.len._is_readed and self.len.value > 14))
|
self.cookie = Buffer(until=b"\x0d\x0a", conditional=lambda: (self.len._is_readed and self.len.value > 14))
|
||||||
# read if there is enough data
|
# read if there is enough data
|
||||||
self.protocolNeg = Negotiation(optional = True)
|
self.protocolNeg = Negotiation(optional = True)
|
||||||
|
|
||||||
class ServerConnectionConfirm(CompositeType):
|
|
||||||
|
class ConnectionConfirmPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Server response
|
@summary: Server response
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240506.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc240506.aspx
|
||||||
@@ -95,6 +103,7 @@ class ServerConnectionConfirm(CompositeType):
|
|||||||
#read if there is enough data
|
#read if there is enough data
|
||||||
self.protocolNeg = Negotiation(optional = True)
|
self.protocolNeg = Negotiation(optional = True)
|
||||||
|
|
||||||
|
|
||||||
class X224DataHeader(CompositeType):
|
class X224DataHeader(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Header send when x224 exchange application data
|
@summary: Header send when x224 exchange application data
|
||||||
@@ -105,6 +114,7 @@ class X224DataHeader(CompositeType):
|
|||||||
self.messageType = UInt8(MessageType.X224_TPDU_DATA, constant = True)
|
self.messageType = UInt8(MessageType.X224_TPDU_DATA, constant = True)
|
||||||
self.separator = UInt8(0x80, constant = True)
|
self.separator = UInt8(0x80, constant = True)
|
||||||
|
|
||||||
|
|
||||||
class Negotiation(CompositeType):
|
class Negotiation(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Negociate request message
|
@summary: Negociate request message
|
||||||
@@ -121,104 +131,68 @@ class Negotiation(CompositeType):
|
|||||||
self.selectedProtocol = UInt32Le(conditional = lambda: (self.code.value != NegociationType.TYPE_RDP_NEG_FAILURE))
|
self.selectedProtocol = UInt32Le(conditional = lambda: (self.code.value != NegociationType.TYPE_RDP_NEG_FAILURE))
|
||||||
self.failureCode = UInt32Le(conditional = lambda: (self.code.value == NegociationType.TYPE_RDP_NEG_FAILURE))
|
self.failureCode = UInt32Le(conditional = lambda: (self.code.value == NegociationType.TYPE_RDP_NEG_FAILURE))
|
||||||
|
|
||||||
class X224Layer(LayerAutomata, IStreamSender):
|
|
||||||
"""
|
|
||||||
@summary: x224 layer management
|
|
||||||
there is an connection automata
|
|
||||||
"""
|
|
||||||
def __init__(self, presentation):
|
|
||||||
"""
|
|
||||||
@param presentation: upper layer, MCS layer in RDP case
|
|
||||||
"""
|
|
||||||
LayerAutomata.__init__(self, presentation)
|
|
||||||
#client requested selectedProtocol
|
|
||||||
self._requestedProtocol = Protocols.PROTOCOL_SSL
|
|
||||||
#server selected selectedProtocol
|
|
||||||
self._selectedProtocol = Protocols.PROTOCOL_SSL
|
|
||||||
|
|
||||||
def recvData(self, data):
|
class X224:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
def __init__(self, tpkt: tpkt.Tpkt, selected_protocol: int):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
self.tpkt = tpkt
|
||||||
|
self.selected_protocol = selected_protocol
|
||||||
|
|
||||||
|
async def read(self) -> Stream:
|
||||||
"""
|
"""
|
||||||
@summary: Read data header from packet
|
|
||||||
And pass to presentation layer
|
|
||||||
@param data: Stream
|
|
||||||
"""
|
"""
|
||||||
header = X224DataHeader()
|
header = X224DataHeader()
|
||||||
data.readType(header)
|
payload = await self.tpkt.read()
|
||||||
self._presentation.recv(data)
|
payload.read_type(header)
|
||||||
|
return payload
|
||||||
|
|
||||||
def send(self, message):
|
async def write(self, message):
|
||||||
"""
|
"""
|
||||||
@summary: Write message packet for TPDU layer
|
|
||||||
Add TPDU header
|
|
||||||
@param message: network.Type message
|
|
||||||
"""
|
"""
|
||||||
self._transport.send((X224DataHeader(), message))
|
await self.tpkt.write((X224DataHeader(), message))
|
||||||
|
|
||||||
class Client(X224Layer):
|
def get_selected_protocol(self):
|
||||||
"""
|
return self.selected_protocol
|
||||||
@summary: Client automata of TPDU layer
|
|
||||||
"""
|
|
||||||
def __init__(self, presentation):
|
|
||||||
"""
|
|
||||||
@param presentation: upper layer, MCS layer in RDP case
|
|
||||||
"""
|
|
||||||
X224Layer.__init__(self, presentation)
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
|
async def connect(tpkt: tpkt.Tpkt, authentication_protocol: sspi.IAuthenticationProtocol) -> X224:
|
||||||
"""
|
"""
|
||||||
@summary: Connection request for client send a connection request packet
|
Negotiate the security level and generate a X224 configured layer
|
||||||
|
|
||||||
|
:ivar tpkt: this is the tpkt layer use to negotiate the security level
|
||||||
|
:ivar authentication_protocol: Authentication protocol is used by NLA authentication
|
||||||
|
Actually only NTLMv2 is available
|
||||||
|
|
||||||
|
:see: http://msdn.microsoft.com/en-us/library/cc240500.aspx
|
||||||
"""
|
"""
|
||||||
self.sendConnectionRequest()
|
request = ConnectionRequestPDU()
|
||||||
|
request.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_REQ
|
||||||
|
request.protocolNeg.selectedProtocol.value = Protocols.PROTOCOL_HYBRID | Protocols.PROTOCOL_SSL
|
||||||
|
await tpkt.write(request)
|
||||||
|
|
||||||
def sendConnectionRequest(self):
|
respond = (await tpkt.read()).read_type(ConnectionConfirmPDU())
|
||||||
"""
|
if respond.protocolNeg.failureCode._is_readed:
|
||||||
@summary: Write connection request message
|
raise RDPSecurityNegoFail("negotiation failure code %x"%respond.protocolNeg.failureCode.value)
|
||||||
Next state is recvConnectionConfirm
|
|
||||||
@see: http://msdn.microsoft.com/en-us/library/cc240500.aspx
|
|
||||||
"""
|
|
||||||
message = ClientConnectionRequestPDU()
|
|
||||||
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_REQ
|
|
||||||
message.protocolNeg.selectedProtocol.value = self._requestedProtocol
|
|
||||||
self._transport.send(message)
|
|
||||||
self.setNextState(self.recvConnectionConfirm)
|
|
||||||
|
|
||||||
def recvConnectionConfirm(self, data):
|
selected_protocol = Protocols.PROTOCOL_RDP
|
||||||
"""
|
if respond.protocolNeg._is_readed:
|
||||||
@summary: Receive connection confirm message
|
selected_protocol = respond.protocolNeg.selectedProtocol.value
|
||||||
Next state is recvData
|
|
||||||
Call connect on presentation layer if all is good
|
|
||||||
@param data: Stream that contain connection confirm
|
|
||||||
@see: response -> http://msdn.microsoft.com/en-us/library/cc240506.aspx
|
|
||||||
@see: failure ->http://msdn.microsoft.com/en-us/library/cc240507.aspx
|
|
||||||
"""
|
|
||||||
message = ServerConnectionConfirm()
|
|
||||||
data.readType(message)
|
|
||||||
|
|
||||||
if message.protocolNeg.failureCode._is_readed:
|
if selected_protocol in [Protocols.PROTOCOL_HYBRID_EX]:
|
||||||
raise RDPSecurityNegoFail("negotiation failure code %x"%message.protocolNeg.failureCode.value)
|
raise InvalidExpectedDataException("RDPY doesn't support PROTOCOL_HYBRID_EX security Layer")
|
||||||
|
|
||||||
#check presence of negotiation response
|
if selected_protocol == Protocols.PROTOCOL_RDP:
|
||||||
if message.protocolNeg._is_readed:
|
return X224(tpkt, selected_protocol)
|
||||||
self._selectedProtocol = message.protocolNeg.selectedProtocol.value
|
elif selected_protocol == Protocols.PROTOCOL_SSL:
|
||||||
else:
|
return X224(await tpkt.start_tls(), selected_protocol)
|
||||||
self._selectedProtocol = Protocols.PROTOCOL_RDP
|
elif selected_protocol == Protocols.PROTOCOL_HYBRID:
|
||||||
|
return X224(await tpkt.start_nla(authentication_protocol), selected_protocol)
|
||||||
|
|
||||||
#NLA protocol doesn't support in actual version of RDPY
|
|
||||||
if self._selectedProtocol in [ Protocols.PROTOCOL_HYBRID, Protocols.PROTOCOL_HYBRID_EX ]:
|
|
||||||
raise InvalidExpectedDataException("RDPY doesn't support NLA security Layer")
|
|
||||||
|
|
||||||
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
class Server(X224):
|
||||||
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
|
|
||||||
#_transport is TPKT and transport is TCP layer of twisted
|
|
||||||
self._transport.transport.startTLS(ClientTLSContext())
|
|
||||||
|
|
||||||
#now i'm ready to receive data
|
|
||||||
self.setNextState(self.recvData)
|
|
||||||
|
|
||||||
#connection is done send to presentation
|
|
||||||
self._presentation.connect()
|
|
||||||
|
|
||||||
class Server(X224Layer):
|
|
||||||
"""
|
"""
|
||||||
@summary: Server automata of X224 layer
|
@summary: Server automata of X224 layer
|
||||||
"""
|
"""
|
||||||
@@ -248,8 +222,8 @@ class Server(X224Layer):
|
|||||||
@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 = ConnectionRequestPDU()
|
||||||
data.readType(message)
|
data.read_type(message)
|
||||||
|
|
||||||
if not message.protocolNeg._is_readed:
|
if not message.protocolNeg._is_readed:
|
||||||
self._requestedProtocol = Protocols.PROTOCOL_RDP
|
self._requestedProtocol = Protocols.PROTOCOL_RDP
|
||||||
@@ -266,7 +240,7 @@ class Server(X224Layer):
|
|||||||
if not self._selectedProtocol & Protocols.PROTOCOL_SSL and self._forceSSL:
|
if not self._selectedProtocol & Protocols.PROTOCOL_SSL and self._forceSSL:
|
||||||
log.warning("server reject client because doesn't support SSL")
|
log.warning("server reject client because doesn't support SSL")
|
||||||
#send error message and quit
|
#send error message and quit
|
||||||
message = ServerConnectionConfirm()
|
message = ConnectionConfirmPDU()
|
||||||
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)
|
||||||
@@ -282,45 +256,15 @@ class Server(X224Layer):
|
|||||||
Next state is recvData
|
Next state is recvData
|
||||||
@see : http://msdn.microsoft.com/en-us/library/cc240501.aspx
|
@see : http://msdn.microsoft.com/en-us/library/cc240501.aspx
|
||||||
"""
|
"""
|
||||||
message = ServerConnectionConfirm()
|
message = ConnectionConfirmPDU()
|
||||||
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_RSP
|
message.protocolNeg.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)
|
||||||
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
||||||
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
|
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
|
||||||
#_transport is TPKT and transport is TCP layer of twisted
|
#_transport is TPKT and transport is TCP layer of twisted
|
||||||
self._transport.transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
|
#self._transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
|
||||||
|
|
||||||
#connection is done send to presentation
|
#connection is done send to presentation
|
||||||
self.setNextState(self.recvData)
|
self.setNextState(self.recvData)
|
||||||
self._presentation.connect()
|
self._presentation.connect()
|
||||||
|
|
||||||
#open ssl needed
|
|
||||||
from twisted.internet import ssl
|
|
||||||
from OpenSSL import SSL
|
|
||||||
|
|
||||||
class ClientTLSContext(ssl.ClientContextFactory):
|
|
||||||
"""
|
|
||||||
@summary: client context factory for open ssl
|
|
||||||
"""
|
|
||||||
def getContext(self):
|
|
||||||
context = SSL.Context(SSL.TLSv1_METHOD)
|
|
||||||
context.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
|
|
||||||
context.set_options(SSL.OP_TLS_BLOCK_PADDING_BUG)
|
|
||||||
return context
|
|
||||||
|
|
||||||
class ServerTLSContext(ssl.DefaultOpenSSLContextFactory):
|
|
||||||
"""
|
|
||||||
@summary: Server context factory for open ssl
|
|
||||||
@param privateKeyFileName: Name of a file containing a private key
|
|
||||||
@param certificateFileName: Name of a file containing a certificate
|
|
||||||
"""
|
|
||||||
def __init__(self, privateKeyFileName, certificateFileName):
|
|
||||||
class TPDUSSLContext(SSL.Context):
|
|
||||||
def __init__(self, method):
|
|
||||||
SSL.Context.__init__(self, method)
|
|
||||||
self.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
|
|
||||||
self.set_options(SSL.OP_TLS_BLOCK_PADDING_BUG)
|
|
||||||
|
|
||||||
ssl.DefaultOpenSSLContextFactory.__init__(self, privateKeyFileName, certificateFileName, SSL.SSLv23_METHOD, TPDUSSLContext)
|
|
||||||
|
|
||||||
@@ -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.
|
||||||
#
|
#
|
||||||
98
rdpy/model/filetimes.py
Normal file
98
rdpy/model/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.
|
||||||
#
|
#
|
||||||
@@ -22,8 +22,9 @@ Join RDPY design with twisted design
|
|||||||
|
|
||||||
RDPY use Layer Protocol design (like twisted)
|
RDPY use Layer Protocol design (like twisted)
|
||||||
"""
|
"""
|
||||||
|
import asyncio
|
||||||
|
from rdpy.model.error import CallPureVirtualFuntion
|
||||||
|
|
||||||
from rdpy.core.error import CallPureVirtualFuntion
|
|
||||||
|
|
||||||
class IStreamListener(object):
|
class IStreamListener(object):
|
||||||
"""
|
"""
|
||||||
@@ -36,6 +37,7 @@ class IStreamListener(object):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
|
||||||
|
|
||||||
|
|
||||||
class IStreamSender(object):
|
class IStreamSender(object):
|
||||||
"""
|
"""
|
||||||
@summary: Interface use to inform stream sender capability
|
@summary: Interface use to inform stream sender capability
|
||||||
@@ -47,6 +49,7 @@ class IStreamSender(object):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "send", "IStreamSender"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "send", "IStreamSender"))
|
||||||
|
|
||||||
|
|
||||||
class Layer(object):
|
class Layer(object):
|
||||||
"""
|
"""
|
||||||
@summary: A simple double linked list with presentation and transport layer
|
@summary: A simple double linked list with presentation and transport layer
|
||||||
@@ -80,6 +83,7 @@ class Layer(object):
|
|||||||
if not self._transport is None:
|
if not self._transport is None:
|
||||||
self._transport.close()
|
self._transport.close()
|
||||||
|
|
||||||
|
|
||||||
class LayerAutomata(Layer, IStreamListener):
|
class LayerAutomata(Layer, IStreamListener):
|
||||||
"""
|
"""
|
||||||
@summary: Layer with automata callback
|
@summary: Layer with automata callback
|
||||||
@@ -103,13 +107,8 @@ class LayerAutomata(Layer, IStreamListener):
|
|||||||
|
|
||||||
self.recv = callback
|
self.recv = callback
|
||||||
|
|
||||||
#twisted layer concept
|
|
||||||
from twisted.internet import protocol
|
|
||||||
from twisted.internet.abstract import FileDescriptor
|
|
||||||
#first that handle stream
|
|
||||||
from type import Stream
|
|
||||||
|
|
||||||
class RawLayerClientFactory(protocol.ClientFactory):
|
class RawLayerClientFactory(asyncio.Protocol):
|
||||||
"""
|
"""
|
||||||
@summary: Abstract class for Raw layer client factory
|
@summary: Abstract class for Raw layer client factory
|
||||||
"""
|
"""
|
||||||
@@ -137,36 +136,37 @@ class RawLayerClientFactory(protocol.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
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):
|
|
||||||
"""
|
|
||||||
@summary: Abstract class for Raw layer server factory
|
|
||||||
"""
|
|
||||||
def buildProtocol(self, addr):
|
|
||||||
"""
|
|
||||||
@summary: Function call from twisted
|
|
||||||
@param addr: destination address
|
|
||||||
"""
|
|
||||||
rawLayer = self.buildRawLayer(addr)
|
|
||||||
rawLayer.setFactory(self)
|
|
||||||
return rawLayer
|
|
||||||
|
|
||||||
def buildRawLayer(self, addr):
|
# class RawLayerServerFactory(protocol.ServerFactory):
|
||||||
"""
|
# """
|
||||||
@summary: Override this function to build raw layer
|
# @summary: Abstract class for Raw layer server factory
|
||||||
@param addr: destination address
|
# """
|
||||||
"""
|
# def buildProtocol(self, addr):
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
|
# """
|
||||||
|
# @summary: Function call from twisted
|
||||||
def connectionLost(self, rawlayer, reason):
|
# @param addr: destination address
|
||||||
"""
|
# """
|
||||||
@summary: Override this method to handle connection lost
|
# rawLayer = self.buildRawLayer(addr)
|
||||||
@param rawlayer: rawLayer that cause connectionLost event
|
# rawLayer.setFactory(self)
|
||||||
@param reason: twisted reason
|
# return rawLayer
|
||||||
"""
|
#
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
|
# def buildRawLayer(self, addr):
|
||||||
|
# """
|
||||||
|
# @summary: Override this function to build raw layer
|
||||||
class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
|
# @param addr: destination address
|
||||||
|
# """
|
||||||
|
# raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
|
||||||
|
#
|
||||||
|
# def connectionLost(self, rawlayer, reason):
|
||||||
|
# """
|
||||||
|
# @summary: Override this method to handle connection lost
|
||||||
|
# @param rawlayer: rawLayer that cause connectionLost event
|
||||||
|
# @param reason: twisted reason
|
||||||
|
# """
|
||||||
|
# raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
|
||||||
|
#
|
||||||
|
#
|
||||||
|
class RawLayer(asyncio.Protocol, LayerAutomata, IStreamSender):
|
||||||
"""
|
"""
|
||||||
@summary: Wait event from twisted engine
|
@summary: Wait event from twisted engine
|
||||||
And format correct size packet
|
And format correct size packet
|
||||||
@@ -200,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
|
||||||
@@ -222,13 +222,19 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
|
|||||||
"""
|
"""
|
||||||
self._factory.connectionLost(self, reason)
|
self._factory.connectionLost(self, reason)
|
||||||
|
|
||||||
|
def getDescriptor(self):
|
||||||
|
"""
|
||||||
|
@return: the twited file descriptor
|
||||||
|
"""
|
||||||
|
return self.transport
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
@summary: Close raw layer
|
@summary: Close raw layer
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
@@ -250,5 +256,5 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
|
|||||||
@param message: (tuple | Type)
|
@param message: (tuple | Type)
|
||||||
"""
|
"""
|
||||||
s = Stream()
|
s = Stream()
|
||||||
s.writeType(message)
|
s.write_type(message)
|
||||||
self.transport.write(s.getvalue())
|
self.transport.write(s.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.
|
||||||
#
|
#
|
||||||
@@ -33,13 +33,18 @@ class Level(object):
|
|||||||
NONE = 4
|
NONE = 4
|
||||||
|
|
||||||
_LOG_LEVEL = Level.DEBUG
|
_LOG_LEVEL = Level.DEBUG
|
||||||
|
_LOG_FILE = False
|
||||||
|
|
||||||
def log(message):
|
def log(message):
|
||||||
"""
|
"""
|
||||||
@summary: Main log function
|
@summary: Main log function
|
||||||
@param message: string to print
|
@param message: string to print
|
||||||
"""
|
"""
|
||||||
print message
|
if _LOG_FILE:
|
||||||
|
f = open(_LOG_FILE, "a+")
|
||||||
|
f.write("%s\n"%message)
|
||||||
|
f.close()
|
||||||
|
print("[*] %s"%message)
|
||||||
|
|
||||||
def error(message):
|
def error(message):
|
||||||
"""
|
"""
|
||||||
@@ -48,7 +53,7 @@ def error(message):
|
|||||||
"""
|
"""
|
||||||
if _LOG_LEVEL > Level.ERROR:
|
if _LOG_LEVEL > Level.ERROR:
|
||||||
return
|
return
|
||||||
log("ERROR : %s"%message)
|
log("ERROR:\t%s"%message)
|
||||||
|
|
||||||
def warning(message):
|
def warning(message):
|
||||||
"""
|
"""
|
||||||
@@ -57,7 +62,7 @@ def warning(message):
|
|||||||
"""
|
"""
|
||||||
if _LOG_LEVEL > Level.WARNING:
|
if _LOG_LEVEL > Level.WARNING:
|
||||||
return
|
return
|
||||||
log("WARNING : %s"%message)
|
log("WARNING:\t%s"%message)
|
||||||
|
|
||||||
def info(message):
|
def info(message):
|
||||||
"""
|
"""
|
||||||
@@ -66,7 +71,7 @@ def info(message):
|
|||||||
"""
|
"""
|
||||||
if _LOG_LEVEL > Level.INFO:
|
if _LOG_LEVEL > Level.INFO:
|
||||||
return
|
return
|
||||||
log("INFO : %s"%message)
|
log("INFO:\t%s"%message)
|
||||||
|
|
||||||
def debug(message):
|
def debug(message):
|
||||||
"""
|
"""
|
||||||
@@ -75,4 +80,4 @@ def debug(message):
|
|||||||
"""
|
"""
|
||||||
if _LOG_LEVEL > Level.DEBUG:
|
if _LOG_LEVEL > Level.DEBUG:
|
||||||
return
|
return
|
||||||
log("DEBUG : %s"%message)
|
log("DEBUG:\t%s"%message)
|
||||||
@@ -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,9 +26,10 @@ We are in python!
|
|||||||
|
|
||||||
import struct
|
import struct
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from StringIO import StringIO
|
from io import BytesIO
|
||||||
from rdpy.core.error import InvalidExpectedDataException, InvalidSize, CallPureVirtualFuntion, InvalidValue
|
from rdpy.model.error import InvalidExpectedDataException, InvalidSize, CallPureVirtualFuntion, InvalidValue
|
||||||
import rdpy.core.log as log
|
import rdpy.model.log as log
|
||||||
|
|
||||||
|
|
||||||
def sizeof(element):
|
def sizeof(element):
|
||||||
"""
|
"""
|
||||||
@@ -42,22 +43,18 @@ def sizeof(element):
|
|||||||
for i in element:
|
for i in element:
|
||||||
size += sizeof(i)
|
size += sizeof(i)
|
||||||
return size
|
return size
|
||||||
elif isinstance(element, Type) and element._conditional():
|
elif isinstance(element, Message) and element._conditional():
|
||||||
return element.__sizeof__()
|
return element.__sizeof__()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
class Type(object):
|
|
||||||
|
class Message:
|
||||||
"""
|
"""
|
||||||
@summary: Root type object inheritance
|
@summary: Root type object inheritance
|
||||||
Record conditional optional of constant mechanism
|
Record conditional optional of constant mechanism
|
||||||
"""
|
"""
|
||||||
def __init__(self, conditional=lambda: True, optional=False, constant=False):
|
def __init__(self, conditional=lambda: True, optional=False, constant=False):
|
||||||
"""
|
"""
|
||||||
@param conditional : Callable object
|
|
||||||
Read and Write operation depend on return of this function
|
|
||||||
@param optional: If there is no enough byte in current stream
|
|
||||||
And optional is True, read type is ignored
|
|
||||||
@param constant: Check if object value doesn't change after read operation
|
|
||||||
"""
|
"""
|
||||||
self._conditional = conditional
|
self._conditional = conditional
|
||||||
self._optional = optional
|
self._optional = optional
|
||||||
@@ -103,7 +100,7 @@ class Type(object):
|
|||||||
# check constant value
|
# check constant value
|
||||||
if old != self:
|
if old != self:
|
||||||
# rollback read value
|
# rollback read value
|
||||||
s.pos -= sizeof(self)
|
s.seek(-sizeof(self), 1)
|
||||||
raise InvalidExpectedDataException("%s const value expected %s != %s"%(self.__class__, old.value, self.value))
|
raise InvalidExpectedDataException("%s const value expected %s != %s"%(self.__class__, old.value, self.value))
|
||||||
|
|
||||||
def __read__(self, s):
|
def __read__(self, s):
|
||||||
@@ -127,22 +124,23 @@ class Type(object):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "__sizeof__", "Type"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "__sizeof__", "Type"))
|
||||||
|
|
||||||
class CallableValue(object):
|
|
||||||
|
class DynMessage(Message):
|
||||||
"""
|
"""
|
||||||
@summary: Expression evaluate when is get or set
|
Expression evaluate when is get or set
|
||||||
Ex: Type contain length of array and array
|
Ex: Type contain length of array and array
|
||||||
To know the size of array you need to read
|
To know the size of array you need to read
|
||||||
length field before. At ctor time no length was read.
|
length field before. At ctor time no length was read.
|
||||||
You need a callable object that will be evaluate when it will be used
|
You need a callable object that will be evaluate when it will be used
|
||||||
"""
|
"""
|
||||||
def __init__(self, value):
|
def __init__(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
@param value: value will be wrapped (raw python type | lambda | function)
|
@param value: value will be wrapped (raw python type | lambda | function)
|
||||||
"""
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
self._value = None
|
self._value = None
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def __getValue__(self):
|
def get_value(self):
|
||||||
"""
|
"""
|
||||||
@summary: Call when value is get -> Evaluate inner expression
|
@summary: Call when value is get -> Evaluate inner expression
|
||||||
Can be overwritten to add specific check before
|
Can be overwritten to add specific check before
|
||||||
@@ -151,7 +149,7 @@ class CallableValue(object):
|
|||||||
"""
|
"""
|
||||||
return self._value()
|
return self._value()
|
||||||
|
|
||||||
def __setValue__(self, value):
|
def set_value(self, value):
|
||||||
"""
|
"""
|
||||||
@summary: Call when value is set
|
@summary: Call when value is set
|
||||||
Can be overwritten to add specific check before
|
Can be overwritten to add specific check before
|
||||||
@@ -164,23 +162,10 @@ class CallableValue(object):
|
|||||||
|
|
||||||
self._value = value_callable
|
self._value = value_callable
|
||||||
|
|
||||||
@property
|
value = property(get_value, set_value)
|
||||||
def value(self):
|
|
||||||
"""
|
|
||||||
@summary: Evaluate callable expression
|
|
||||||
@return: result of callable value
|
|
||||||
"""
|
|
||||||
return self.__getValue__()
|
|
||||||
|
|
||||||
@value.setter
|
|
||||||
def value(self, value):
|
|
||||||
"""
|
|
||||||
@summary: Setter of value
|
|
||||||
@param value: new value encompass in value type object
|
|
||||||
"""
|
|
||||||
self.__setValue__(value)
|
|
||||||
|
|
||||||
class SimpleType(Type, CallableValue):
|
class SimpleType(DynMessage):
|
||||||
"""
|
"""
|
||||||
@summary: Non composite type
|
@summary: Non composite type
|
||||||
leaf in type tree
|
leaf in type tree
|
||||||
@@ -198,46 +183,11 @@ class SimpleType(Type, CallableValue):
|
|||||||
And optional is True, read type is ignored
|
And optional is True, read type is ignored
|
||||||
@param constant: Check if object value doesn't change after read operation
|
@param constant: Check if object value doesn't change after read operation
|
||||||
"""
|
"""
|
||||||
|
super().__init__(conditional=conditional, optional=optional, constant=constant)
|
||||||
self._signed = signed
|
self._signed = signed
|
||||||
self._typeSize = typeSize
|
self._typeSize = typeSize
|
||||||
self._structFormat = structFormat
|
self._structFormat = structFormat
|
||||||
Type.__init__(self, conditional = conditional, optional = optional, constant = constant)
|
self.value = value
|
||||||
CallableValue.__init__(self, value)
|
|
||||||
|
|
||||||
def __getValue__(self):
|
|
||||||
"""
|
|
||||||
@summary: Check value if match range of type
|
|
||||||
And apply sign
|
|
||||||
Ex: UInt8 can be > 255
|
|
||||||
@return: Python value wrap into type
|
|
||||||
@raise InvalidValue: if value doesn't respect type range
|
|
||||||
@see: CallableValue.__getValue__
|
|
||||||
"""
|
|
||||||
value = CallableValue.__getValue__(self)
|
|
||||||
|
|
||||||
#check value now because it can be an callable value
|
|
||||||
#and evaluate a this time
|
|
||||||
|
|
||||||
if not self.isInRange(value):
|
|
||||||
raise InvalidValue("value is out of range for %s"%self.__class__)
|
|
||||||
if self._signed:
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
return value & self.mask()
|
|
||||||
|
|
||||||
def __setValue__(self, value):
|
|
||||||
"""
|
|
||||||
@summary: Check if new value respect type declaration
|
|
||||||
Ex: UInt8 can be > 256
|
|
||||||
@param value: new value (raw python type | lambda | function)
|
|
||||||
@raise InvalidValue: if value doesn't respect type range
|
|
||||||
@see: CallableValue.__setValue__
|
|
||||||
"""
|
|
||||||
#check static value range
|
|
||||||
if not callable(value) and not self.isInRange(value):
|
|
||||||
raise InvalidValue("value is out of range for %s"%self.__class__)
|
|
||||||
|
|
||||||
CallableValue.__setValue__(self, value)
|
|
||||||
|
|
||||||
def __write__(self, s):
|
def __write__(self, s):
|
||||||
"""
|
"""
|
||||||
@@ -256,33 +206,11 @@ class SimpleType(Type, CallableValue):
|
|||||||
@param s: Stream that will be read
|
@param s: Stream that will be read
|
||||||
@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.data_len() < 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]
|
value = struct.unpack(self._structFormat, s.read(self._typeSize))[0]
|
||||||
|
|
||||||
def mask(self):
|
self.value = value
|
||||||
"""
|
|
||||||
@summary: Compute bit mask for type
|
|
||||||
Because in Python all numbers are Int long or float
|
|
||||||
Cache result in self._mask field
|
|
||||||
"""
|
|
||||||
if not self.__dict__.has_key("_mask"):
|
|
||||||
mask = 0xff
|
|
||||||
for _ in range(1, self._typeSize):
|
|
||||||
mask = mask << 8 | 0xff
|
|
||||||
self._mask = mask
|
|
||||||
return self._mask
|
|
||||||
|
|
||||||
def isInRange(self, value):
|
|
||||||
"""
|
|
||||||
@summary: Check if value is in range represented by mask
|
|
||||||
@param value: Python value
|
|
||||||
@return: true if value is in type range
|
|
||||||
"""
|
|
||||||
if self._signed:
|
|
||||||
return not (value < -(self.mask() >> 1) or value > (self.mask() >> 1))
|
|
||||||
else:
|
|
||||||
return not (value < 0 or value > self.mask())
|
|
||||||
|
|
||||||
def __sizeof__(self):
|
def __sizeof__(self):
|
||||||
"""
|
"""
|
||||||
@@ -291,7 +219,7 @@ class SimpleType(Type, CallableValue):
|
|||||||
"""
|
"""
|
||||||
return self._typeSize
|
return self._typeSize
|
||||||
|
|
||||||
def __cmp__(self, other):
|
def __eq__(self, other):
|
||||||
"""
|
"""
|
||||||
@summary: Compare two simple type
|
@summary: Compare two simple type
|
||||||
Call inner value compare operator
|
Call inner value compare operator
|
||||||
@@ -301,7 +229,19 @@ class SimpleType(Type, CallableValue):
|
|||||||
"""
|
"""
|
||||||
if not isinstance(other, SimpleType):
|
if not isinstance(other, SimpleType):
|
||||||
other = self.__class__(other)
|
other = self.__class__(other)
|
||||||
return self.value.__cmp__(other.value)
|
return self.value.__eq__(other.value)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
"""
|
||||||
|
@summary: Compare two simple type
|
||||||
|
Call inner value compare operator
|
||||||
|
@param other: SimpleType value or try to build same type as self
|
||||||
|
around value
|
||||||
|
@return: python value compare
|
||||||
|
"""
|
||||||
|
if not isinstance(other, SimpleType):
|
||||||
|
other = self.__class__(other)
|
||||||
|
return self.value.__ne__(other.value)
|
||||||
|
|
||||||
def __invert__(self):
|
def __invert__(self):
|
||||||
"""
|
"""
|
||||||
@@ -412,57 +352,37 @@ class SimpleType(Type, CallableValue):
|
|||||||
return bool(self.value)
|
return bool(self.value)
|
||||||
|
|
||||||
|
|
||||||
class CompositeType(Type):
|
class CompositeType(Message):
|
||||||
"""
|
"""
|
||||||
@summary: Type node in Type tree
|
|
||||||
Track type field declared in __init__ function
|
|
||||||
Ex: self.lengthOfPacket = UInt16Le() -> record lengthOfPacket as sub type of node
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, conditional = lambda:True, optional = False, constant = False, readLen = None):
|
def __init__(self, read_len=None, conditional=lambda: True, optional=False, constant=False):
|
||||||
"""
|
"""
|
||||||
@param conditional : Callable object
|
|
||||||
Read and Write operation depend on return of this function
|
|
||||||
@param optional: If there is no enough byte in current stream
|
|
||||||
And optional is True, read type is ignored
|
|
||||||
@param constant: Check if object value doesn't change after read operation
|
|
||||||
@param readLen: Max length in bytes can be readed from stream
|
|
||||||
Use to check length information
|
|
||||||
"""
|
"""
|
||||||
Type.__init__(self, conditional = conditional, optional = optional, constant = constant)
|
super().__init__(conditional=conditional, optional=optional, constant=constant)
|
||||||
#list of ordoned type
|
|
||||||
self._typeName = []
|
# list of ordorred type
|
||||||
self._readLen = readLen
|
self._type_name = []
|
||||||
|
self._read_len = read_len
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
"""
|
"""
|
||||||
@summary: Track Type field
|
|
||||||
For Type field record it in same order as declared
|
|
||||||
Keep other but bot handle in read or write function
|
|
||||||
@param name: name of new attribute
|
|
||||||
@param value: value of new attribute
|
|
||||||
"""
|
"""
|
||||||
if name[0] != '_' and (isinstance(value, Type) or isinstance(value, tuple)) and not name in self._typeName:
|
if name[0] != '_' and (isinstance(value, Message) or isinstance(value, tuple)) and name not in self._type_name:
|
||||||
self._typeName.append(name)
|
self._type_name.append(name)
|
||||||
self.__dict__[name] = value
|
self.__dict__[name] = value
|
||||||
|
|
||||||
def __read__(self, s):
|
def __read__(self, s):
|
||||||
"""
|
"""
|
||||||
@summary: Read composite type
|
|
||||||
Call read on each ordered sub-type
|
|
||||||
And check read length parameter
|
|
||||||
If an error occurred rollback type already read
|
|
||||||
@param s: Stream
|
|
||||||
@raise InvalidSize: if stream is greater than readLen parameter
|
|
||||||
"""
|
"""
|
||||||
readLen = 0
|
read_len = 0
|
||||||
for name in self._typeName:
|
for name in self._type_name:
|
||||||
try:
|
try:
|
||||||
s.readType(self.__dict__[name])
|
s.read_type(self.__dict__[name])
|
||||||
readLen += sizeof(self.__dict__[name])
|
read_len += sizeof(self.__dict__[name])
|
||||||
# read is ok but read out of bound
|
# read is ok but read out of bound
|
||||||
if not self._readLen is None and readLen > self._readLen.value:
|
if self._read_len is not None and read_len > self._read_len():
|
||||||
# roll back
|
# roll back
|
||||||
s.pos -= sizeof(self.__dict__[name])
|
s.seek(-sizeof(self.__dict__[name]), 1)
|
||||||
# and notify if not optional
|
# and notify if not optional
|
||||||
if not self.__dict__[name]._optional:
|
if not self.__dict__[name]._optional:
|
||||||
raise InvalidSize("Impossible to read type %s : read length is too small"%(self.__class__))
|
raise InvalidSize("Impossible to read type %s : read length is too small"%(self.__class__))
|
||||||
@@ -470,15 +390,15 @@ class CompositeType(Type):
|
|||||||
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))
|
||||||
# roll back already read
|
# roll back already read
|
||||||
for tmpName in self._typeName:
|
for tmp_name in self._type_name:
|
||||||
if tmpName == name:
|
if tmp_name == name:
|
||||||
break
|
break
|
||||||
s.pos -= sizeof(self.__dict__[tmpName])
|
s.seek(-sizeof(self.__dict__[tmp_name]), 1)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
if not self._readLen is None and readLen < self._readLen.value:
|
if self._read_len is not None and read_len < self._read_len():
|
||||||
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._read_len() - read_len))
|
||||||
s.read(self._readLen.value - readLen)
|
s.read(self._read_len() - read_len)
|
||||||
|
|
||||||
def __write__(self, s):
|
def __write__(self, s):
|
||||||
"""
|
"""
|
||||||
@@ -486,9 +406,9 @@ class CompositeType(Type):
|
|||||||
Call write on each ordered sub type
|
Call write on each ordered sub type
|
||||||
@param s: Stream
|
@param s: Stream
|
||||||
"""
|
"""
|
||||||
for name in self._typeName:
|
for name in self._type_name:
|
||||||
try:
|
try:
|
||||||
s.writeType(self.__dict__[name])
|
s.write_type(self.__dict__[name])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("Error during write %s::%s"%(self.__class__, name))
|
log.error("Error during write %s::%s"%(self.__class__, name))
|
||||||
raise e
|
raise e
|
||||||
@@ -498,8 +418,11 @@ 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._read_len is None:
|
||||||
|
return self._read_len()
|
||||||
|
|
||||||
size = 0
|
size = 0
|
||||||
for name in self._typeName:
|
for name in self._type_name:
|
||||||
size += sizeof(self.__dict__[name])
|
size += sizeof(self.__dict__[name])
|
||||||
return size
|
return size
|
||||||
|
|
||||||
@@ -510,9 +433,9 @@ class CompositeType(Type):
|
|||||||
@param other: CompositeType
|
@param other: CompositeType
|
||||||
@return: True if each sub-type are equals
|
@return: True if each sub-type are equals
|
||||||
"""
|
"""
|
||||||
if self._typeName != other._typeName:
|
if self._type_name != other._typeName:
|
||||||
return False
|
return False
|
||||||
for name in self._typeName:
|
for name in self._type_name:
|
||||||
if self.__dict__[name] != other.__dict__[name]:
|
if self.__dict__[name] != other.__dict__[name]:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -702,6 +625,7 @@ class UInt24Be(SimpleType):
|
|||||||
"""
|
"""
|
||||||
self.value = struct.unpack(self._structFormat, '\x00' + s.read(self._typeSize))[0]
|
self.value = struct.unpack(self._structFormat, '\x00' + s.read(self._typeSize))[0]
|
||||||
|
|
||||||
|
|
||||||
class UInt24Le(SimpleType):
|
class UInt24Le(SimpleType):
|
||||||
"""
|
"""
|
||||||
@summary: unsigned 24 bit integer
|
@summary: unsigned 24 bit integer
|
||||||
@@ -731,90 +655,64 @@ class UInt24Le(SimpleType):
|
|||||||
@summary: special read for a special type
|
@summary: special read for a special type
|
||||||
@param s: Stream
|
@param s: Stream
|
||||||
"""
|
"""
|
||||||
self.value = struct.unpack(self._structFormat, s.read(self._typeSize) + '\x00')[0]
|
self.value = struct.unpack(self._structFormat, s.read(self._typeSize) + b'\x00')[0]
|
||||||
|
|
||||||
class String(Type, CallableValue):
|
|
||||||
|
class Buffer(DynMessage):
|
||||||
"""
|
"""
|
||||||
@summary: String type
|
This a raw binary bytes data
|
||||||
Leaf in Type tree
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, value = "", readLen = None, conditional = lambda:True, optional = False, constant = False, unicode = False, until = None):
|
def __init__(self, value: bytes = b"", read_len=None, conditional=lambda:True, optional: bool = False, constant: bool = False, until: bytes = None):
|
||||||
"""
|
"""
|
||||||
@param value: python string use for inner value
|
|
||||||
@param readLen: length use to read in stream (SimpleType) if 0 read entire stream
|
|
||||||
@param conditional : Callable object
|
|
||||||
Read and Write operation depend on return of this function
|
|
||||||
@param optional: If there is no enough byte in current stream
|
|
||||||
And optional is True, read type is ignored
|
|
||||||
@param constant: Check if object value doesn't change after read operation
|
|
||||||
@param unicode: Encode and decode value as unicode
|
|
||||||
@param until: read until sequence is readed or write sequence at the end of string
|
|
||||||
"""
|
"""
|
||||||
Type.__init__(self, conditional = conditional, optional = optional, constant = constant)
|
super().__init__(conditional=conditional, optional=optional, constant=constant)
|
||||||
CallableValue.__init__(self, value)
|
|
||||||
# type use to know read length
|
# type use to know read length
|
||||||
self._readLen = readLen
|
self._read_len = read_len
|
||||||
self._unicode = unicode
|
|
||||||
self._until = until
|
self._until = until
|
||||||
|
self.value = value
|
||||||
|
|
||||||
def __cmp__(self, other):
|
def __eq__(self, other):
|
||||||
"""
|
"""
|
||||||
@summary: call raw compare value
|
|
||||||
@param other: other String parameter
|
|
||||||
@return: if two inner value are equals
|
|
||||||
"""
|
"""
|
||||||
return cmp(self.value, other.value)
|
return self.value.__eq__(other.value)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
return self.value.__ne__(other.value)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
"""
|
"""
|
||||||
@summary: hash function to treat simple type in hash collection
|
|
||||||
@return: hash of inner value
|
|
||||||
"""
|
"""
|
||||||
return hash(self.value)
|
return hash(self.value)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""
|
"""
|
||||||
@summary: call when str function is call
|
|
||||||
@return: inner python string
|
|
||||||
"""
|
"""
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
def __write__(self, s):
|
def __write__(self, s):
|
||||||
"""
|
"""
|
||||||
@summary: Write the inner value after evaluation
|
|
||||||
Append until sequence if present
|
|
||||||
Encode in unicode format if asked
|
|
||||||
@param s: Stream
|
|
||||||
"""
|
"""
|
||||||
toWrite = self.value
|
to_write = self.value
|
||||||
|
|
||||||
if not self._until is None:
|
if not self._until is None:
|
||||||
toWrite += self._until
|
to_write += self._until
|
||||||
|
s.write(to_write)
|
||||||
if self._unicode:
|
|
||||||
s.write(encodeUnicode(self.value))
|
|
||||||
else:
|
|
||||||
s.write(self.value)
|
|
||||||
|
|
||||||
def __read__(self, s):
|
def __read__(self, s):
|
||||||
"""
|
"""
|
||||||
@summary: Read readLen bytes as string
|
|
||||||
If readLen is None read until 'until' sequence match
|
|
||||||
If until sequence is None read until end of stream
|
|
||||||
@param s: Stream
|
|
||||||
"""
|
"""
|
||||||
if self._readLen is None:
|
if self._read_len is None:
|
||||||
if self._until is None:
|
if self._until is None:
|
||||||
self.value = s.getvalue()[s.pos:]
|
self.value = s.getvalue()[s.tell():]
|
||||||
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.data_len() != 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._read_len())
|
||||||
|
|
||||||
if self._unicode:
|
|
||||||
self.value = decodeUnicode(self.value)
|
|
||||||
|
|
||||||
def __sizeof__(self):
|
def __sizeof__(self):
|
||||||
"""
|
"""
|
||||||
@@ -822,11 +720,9 @@ class String(Type, CallableValue):
|
|||||||
if string is unicode encode return 2*len(str) + 2
|
if string is unicode encode return 2*len(str) + 2
|
||||||
@return: length of inner string
|
@return: length of inner string
|
||||||
"""
|
"""
|
||||||
if self._unicode:
|
|
||||||
return 2 * len(self.value) + 2
|
|
||||||
else:
|
|
||||||
return len(self.value)
|
return len(self.value)
|
||||||
|
|
||||||
|
|
||||||
def encodeUnicode(s):
|
def encodeUnicode(s):
|
||||||
"""
|
"""
|
||||||
@summary: Encode string in unicode
|
@summary: Encode string in unicode
|
||||||
@@ -849,35 +745,36 @@ def decodeUnicode(s):
|
|||||||
i += 1
|
i += 1
|
||||||
return r
|
return r
|
||||||
|
|
||||||
class Stream(StringIO):
|
|
||||||
|
class Stream(BytesIO):
|
||||||
"""
|
"""
|
||||||
@summary: Stream use to read all types
|
@summary: Stream use to read all types
|
||||||
"""
|
"""
|
||||||
def dataLen(self):
|
def data_len(self) -> int:
|
||||||
"""
|
"""
|
||||||
@return: not yet read length
|
:returns: not yet read length
|
||||||
"""
|
"""
|
||||||
return self.len - self.pos
|
return len(self.getvalue()) - self.tell()
|
||||||
|
|
||||||
def readLen(self):
|
def read_len(self) -> int:
|
||||||
"""
|
"""
|
||||||
@summary: compute already read size
|
Compute already read size
|
||||||
@return: read size of stream
|
:returns: read size of stream
|
||||||
"""
|
"""
|
||||||
return self.pos
|
return self.seek()
|
||||||
|
|
||||||
def readType(self, value):
|
def read_type(self, value: Message):
|
||||||
"""
|
"""
|
||||||
@summary: call specific read on type object
|
Call specific read on type object
|
||||||
or iterate over tuple elements
|
or iterate over tuple elements
|
||||||
rollback read if error occurred during read value
|
rollback read if error occurred during read value
|
||||||
@param value: (tuple | Type) object
|
:ivar tuple | Type object
|
||||||
"""
|
"""
|
||||||
# read each tuple
|
# read each tuple
|
||||||
if isinstance(value, tuple) or isinstance(value, list):
|
if isinstance(value, tuple) or isinstance(value, list):
|
||||||
for element in value:
|
for element in value:
|
||||||
try:
|
try:
|
||||||
self.readType(element)
|
self.read_type(element)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# rollback already readed elements
|
# rollback already readed elements
|
||||||
for tmpElement in value:
|
for tmpElement in value:
|
||||||
@@ -885,69 +782,61 @@ class Stream(StringIO):
|
|||||||
break
|
break
|
||||||
self.pos -= sizeof(tmpElement)
|
self.pos -= sizeof(tmpElement)
|
||||||
raise e
|
raise e
|
||||||
return
|
return value
|
||||||
|
|
||||||
# optional value not present
|
# optional value not present
|
||||||
if self.dataLen() == 0 and value._optional:
|
if self.data_len() == 0 and value._optional:
|
||||||
return
|
return
|
||||||
|
|
||||||
value.read(self)
|
value.read(self)
|
||||||
|
return value
|
||||||
|
|
||||||
def readNextType(self, t):
|
def readNextType(self, t):
|
||||||
"""
|
"""
|
||||||
@summary: read next type but didn't consume it
|
@summary: read next type but didn't consume it
|
||||||
@param t: Type element
|
@param t: Type element
|
||||||
"""
|
"""
|
||||||
self.readType(t)
|
self.read_type(t)
|
||||||
self.pos -= sizeof(t)
|
self.pos -= sizeof(t)
|
||||||
|
|
||||||
def writeType(self, value):
|
def write_type(self, value: Message):
|
||||||
"""
|
"""
|
||||||
@summary: Call specific write on type object
|
Call specific write on type object
|
||||||
or iterate over tuple element
|
or iterate over tuple element
|
||||||
@param value: (tuple | Type)
|
|
||||||
|
:ivar Type: Type to write
|
||||||
"""
|
"""
|
||||||
# write each element of tuple
|
# write each element of tuple
|
||||||
if isinstance(value, tuple) or isinstance(value, list):
|
if isinstance(value, tuple) or isinstance(value, list):
|
||||||
for element in value:
|
for element in value:
|
||||||
self.writeType(element)
|
self.write_type(element)
|
||||||
return
|
return self
|
||||||
value.write(self)
|
value.write(self)
|
||||||
|
return self
|
||||||
|
|
||||||
class ArrayType(Type):
|
|
||||||
|
class ArrayType(Message):
|
||||||
"""
|
"""
|
||||||
@summary: Factory af n element
|
@summary: Factory af n element
|
||||||
"""
|
"""
|
||||||
def __init__(self, typeFactory, init = None, readLen = None, conditional = lambda:True, optional = False, constant = False):
|
def __init__(self, type_factory, init=None, read_len=None, conditional=lambda:True, optional=False, constant=False):
|
||||||
"""
|
"""
|
||||||
@param typeFactory: class use to init new element on read
|
|
||||||
@param init: init array
|
|
||||||
@param readLen: number of element in sequence
|
|
||||||
@param conditional : Callable object
|
|
||||||
Read and Write operation depend on return of this function
|
|
||||||
@param optional: If there is no enough byte in current stream
|
|
||||||
And optional is True, read type is ignored
|
|
||||||
@param constant: Check if object value doesn't change after read operation
|
|
||||||
"""
|
"""
|
||||||
Type.__init__(self, conditional, optional, constant)
|
super().__init__(conditional, optional, constant)
|
||||||
self._typeFactory = typeFactory
|
self._type_factory = type_factory
|
||||||
self._readLen = readLen
|
self._read_len = read_len
|
||||||
self._array = []
|
self._array = init or []
|
||||||
if not init is None:
|
|
||||||
self._array = init
|
|
||||||
|
|
||||||
def __read__(self, s):
|
def __read__(self, s):
|
||||||
"""
|
"""
|
||||||
@summary: Create readLen new object and read it
|
|
||||||
@param s: Stream
|
|
||||||
"""
|
"""
|
||||||
self._array = []
|
self._array = []
|
||||||
i = 0
|
i = 0
|
||||||
#self._readLen is None means that array will be read until end of stream
|
# self._read_len is None means that array will be read until end of stream
|
||||||
while self._readLen is None or i < self._readLen.value:
|
while self._read_len is None or i < self._read_len():
|
||||||
element = self._typeFactory()
|
element = self._type_factory()
|
||||||
element._optional = self._readLen is None
|
element._optional = self._read_len is None
|
||||||
s.readType(element)
|
s.read_type(element)
|
||||||
if not element._is_readed:
|
if not element._is_readed:
|
||||||
break
|
break
|
||||||
self._array.append(element)
|
self._array.append(element)
|
||||||
@@ -955,10 +844,8 @@ class ArrayType(Type):
|
|||||||
|
|
||||||
def __write__(self, s):
|
def __write__(self, s):
|
||||||
"""
|
"""
|
||||||
@summary: Just write array
|
|
||||||
@param s: Stream
|
|
||||||
"""
|
"""
|
||||||
s.writeType(self._array)
|
s.write_type(self._array)
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
"""
|
"""
|
||||||
@@ -973,7 +860,10 @@ class ArrayType(Type):
|
|||||||
"""
|
"""
|
||||||
return sizeof(self._array)
|
return sizeof(self._array)
|
||||||
|
|
||||||
class FactoryType(Type):
|
def __len__(self):
|
||||||
|
return len(self._array)
|
||||||
|
|
||||||
|
class FactoryType(Message):
|
||||||
"""
|
"""
|
||||||
@summary: Call a factory callback at read or write time
|
@summary: Call a factory callback at read or write time
|
||||||
Wrapp attribute access to inner type
|
Wrapp attribute access to inner type
|
||||||
@@ -987,7 +877,7 @@ class FactoryType(Type):
|
|||||||
And optional is True, read type is ignored
|
And optional is True, read type is ignored
|
||||||
@param constant: Check if object value doesn't change after read operation
|
@param constant: Check if object value doesn't change after read operation
|
||||||
"""
|
"""
|
||||||
Type.__init__(self, conditional, optional, constant)
|
Message.__init__(self, conditional, optional, constant)
|
||||||
self._factory = factory
|
self._factory = factory
|
||||||
if not callable(factory):
|
if not callable(factory):
|
||||||
self._factory = lambda:factory
|
self._factory = lambda:factory
|
||||||
@@ -1000,7 +890,7 @@ class FactoryType(Type):
|
|||||||
@param s: Stream
|
@param s: Stream
|
||||||
"""
|
"""
|
||||||
self._value = self._factory()
|
self._value = self._factory()
|
||||||
s.readType(self._value)
|
s.read_type(self._value)
|
||||||
|
|
||||||
def __write__(self, s):
|
def __write__(self, s):
|
||||||
"""
|
"""
|
||||||
@@ -1008,7 +898,7 @@ class FactoryType(Type):
|
|||||||
@param s: Stream
|
@param s: Stream
|
||||||
"""
|
"""
|
||||||
self._value = self._factory()
|
self._value = self._factory()
|
||||||
s.writeType(self._value)
|
s.write_type(self._value)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
"""
|
"""
|
||||||
300
rdpy/model/rss.py
Normal file
300
rdpy/model/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.model.type import CompositeType, FactoryType, UInt8, UInt16Le, UInt32Le, Buffer, sizeof, Stream
|
||||||
|
from rdpy.model 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 Buffer(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 = Buffer(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 = Buffer(readLen = self.lenUsername)
|
||||||
|
self.lenPassword = UInt16Le(lambda:sizeof(self.password))
|
||||||
|
self.password = Buffer(readLen = self.lenPassword)
|
||||||
|
self.lenDomain = UInt16Le(lambda:sizeof(self.domain))
|
||||||
|
self.domain = Buffer(readLen = self.lenDomain)
|
||||||
|
self.lenHostname = UInt16Le(lambda:sizeof(self.hostname))
|
||||||
|
self.hostname = Buffer(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.write_type(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.data_len() == 0:
|
||||||
|
return None
|
||||||
|
e = Event()
|
||||||
|
self._s.read_type(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/model/scancode.py
Normal file
60
rdpy/model/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,765 +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/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Implement Remote FrameBuffer protocol use in VNC client and server
|
|
||||||
@see: http://www.realvnc.com/docs/rfbproto.pdf
|
|
||||||
|
|
||||||
@todo: server side of protocol
|
|
||||||
@todo: more encoding rectangle
|
|
||||||
"""
|
|
||||||
|
|
||||||
from rdpy.core.layer import RawLayer, RawLayerClientFactory
|
|
||||||
from rdpy.core.type import UInt8, UInt16Be, UInt32Be, SInt32Be, String, CompositeType
|
|
||||||
from rdpy.core.error import InvalidValue, CallPureVirtualFuntion
|
|
||||||
from rdpy.security.pyDes import des
|
|
||||||
import rdpy.core.log as log
|
|
||||||
|
|
||||||
class ProtocolVersion(object):
|
|
||||||
"""
|
|
||||||
@summary: Different protocol version
|
|
||||||
"""
|
|
||||||
UNKNOWN = ""
|
|
||||||
RFB003003 = "RFB 003.003\n"
|
|
||||||
RFB003007 = "RFB 003.007\n"
|
|
||||||
RFB003008 = "RFB 003.008\n"
|
|
||||||
|
|
||||||
class SecurityType(object):
|
|
||||||
"""
|
|
||||||
@summary: Security type supported
|
|
||||||
"""
|
|
||||||
INVALID = 0
|
|
||||||
NONE = 1
|
|
||||||
VNC = 2
|
|
||||||
|
|
||||||
class Pointer(object):
|
|
||||||
"""
|
|
||||||
@summary: Mouse event code (which button)
|
|
||||||
actually in RFB specification only
|
|
||||||
three buttons are supported
|
|
||||||
"""
|
|
||||||
BUTTON1 = 0x1
|
|
||||||
BUTTON2 = 0x2
|
|
||||||
BUTTON3 = 0x4
|
|
||||||
|
|
||||||
class Encoding(object):
|
|
||||||
"""
|
|
||||||
@summary: Encoding types of FrameBuffer update
|
|
||||||
"""
|
|
||||||
RAW = 0
|
|
||||||
|
|
||||||
class ClientToServerMessages(object):
|
|
||||||
"""
|
|
||||||
@summary: Client to server messages types
|
|
||||||
"""
|
|
||||||
PIXEL_FORMAT = 0
|
|
||||||
ENCODING = 2
|
|
||||||
FRAME_BUFFER_UPDATE_REQUEST = 3
|
|
||||||
KEY_EVENT = 4
|
|
||||||
POINTER_EVENT = 5
|
|
||||||
CUT_TEXT = 6
|
|
||||||
|
|
||||||
class PixelFormat(CompositeType):
|
|
||||||
"""
|
|
||||||
@summary: Pixel format structure
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.BitsPerPixel = UInt8(32)
|
|
||||||
self.Depth = UInt8(24)
|
|
||||||
self.BigEndianFlag = UInt8(False)
|
|
||||||
self.TrueColorFlag = UInt8(True)
|
|
||||||
self.RedMax = UInt16Be(255)
|
|
||||||
self.GreenMax = UInt16Be(255)
|
|
||||||
self.BlueMax = UInt16Be(255)
|
|
||||||
self.RedShift = UInt8(16)
|
|
||||||
self.GreenShift = UInt8(8)
|
|
||||||
self.BlueShift = UInt8(0)
|
|
||||||
self.padding = (UInt16Be(), UInt8())
|
|
||||||
|
|
||||||
class ServerInit(CompositeType):
|
|
||||||
"""
|
|
||||||
@summary: Server init structure
|
|
||||||
FrameBuffer configuration
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.width = UInt16Be()
|
|
||||||
self.height = UInt16Be()
|
|
||||||
self.pixelFormat = PixelFormat()
|
|
||||||
|
|
||||||
class FrameBufferUpdateRequest(CompositeType):
|
|
||||||
"""
|
|
||||||
@summary: FrameBuffer update request send from client to server
|
|
||||||
Incremental means that server send update with a specific
|
|
||||||
order, and client must draw orders in same order
|
|
||||||
"""
|
|
||||||
def __init__(self, incremental = False, x = 0, y = 0, width = 0, height = 0):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.incremental = UInt8(incremental)
|
|
||||||
self.x = UInt16Be(x)
|
|
||||||
self.y = UInt16Be(y)
|
|
||||||
self.width = UInt16Be(width)
|
|
||||||
self.height = UInt16Be(height)
|
|
||||||
|
|
||||||
|
|
||||||
class Rectangle(CompositeType):
|
|
||||||
"""
|
|
||||||
@summary: Header message of update rectangle
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.x = UInt16Be()
|
|
||||||
self.y = UInt16Be()
|
|
||||||
self.width = UInt16Be()
|
|
||||||
self.height = UInt16Be()
|
|
||||||
self.encoding = SInt32Be()
|
|
||||||
|
|
||||||
class KeyEvent(CompositeType):
|
|
||||||
"""
|
|
||||||
@summary: Key event structure message
|
|
||||||
Use to send a keyboard event
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.downFlag = UInt8(False)
|
|
||||||
self.padding = UInt16Be()
|
|
||||||
self.key = UInt32Be()
|
|
||||||
|
|
||||||
class PointerEvent(CompositeType):
|
|
||||||
"""
|
|
||||||
@summary: Pointer event structure message
|
|
||||||
Use to send mouse event
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.mask = UInt8()
|
|
||||||
self.x = UInt16Be()
|
|
||||||
self.y = UInt16Be()
|
|
||||||
|
|
||||||
class ClientCutText(CompositeType):
|
|
||||||
"""
|
|
||||||
@summary: Client cut text message message
|
|
||||||
Use to simulate copy paste (ctrl-c ctrl-v) only for text
|
|
||||||
"""
|
|
||||||
def __init__(self, text = ""):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.padding = (UInt16Be(), UInt8())
|
|
||||||
self.size = UInt32Be(len(text))
|
|
||||||
self.message = String(text)
|
|
||||||
|
|
||||||
class ServerCutTextHeader(CompositeType):
|
|
||||||
"""
|
|
||||||
@summary: Cut text header send from server to client
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
CompositeType.__init__(self)
|
|
||||||
self.padding = (UInt16Be(), UInt8())
|
|
||||||
self.size = UInt32Be()
|
|
||||||
|
|
||||||
class RFB(RawLayer):
|
|
||||||
"""
|
|
||||||
@summary: Implement RFB protocol
|
|
||||||
"""
|
|
||||||
def __init__(self, listener):
|
|
||||||
"""
|
|
||||||
@param listener: listener use to inform new orders
|
|
||||||
"""
|
|
||||||
RawLayer.__init__(self)
|
|
||||||
#set client listener
|
|
||||||
self._clientListener = listener
|
|
||||||
#useful for RFB protocol
|
|
||||||
self._callbackBody = None
|
|
||||||
#protocol version negotiated
|
|
||||||
self._version = String(ProtocolVersion.RFB003008)
|
|
||||||
#number security launch by server
|
|
||||||
self._securityLevel = UInt8(SecurityType.INVALID)
|
|
||||||
#shared FrameBuffer client init message
|
|
||||||
self._sharedFlag = UInt8(False)
|
|
||||||
#server init message
|
|
||||||
#which contain FrameBuffer dim and pixel format
|
|
||||||
self._serverInit = ServerInit()
|
|
||||||
#client pixel format
|
|
||||||
self._pixelFormat = PixelFormat()
|
|
||||||
#server name
|
|
||||||
self._serverName = String()
|
|
||||||
#nb rectangle
|
|
||||||
self._nbRect = 0
|
|
||||||
#current rectangle header
|
|
||||||
self._currentRect = Rectangle()
|
|
||||||
#for vnc security type
|
|
||||||
self._password = '\0' * 8
|
|
||||||
|
|
||||||
def expectWithHeader(self, expectedHeaderLen, callbackBody):
|
|
||||||
"""
|
|
||||||
2nd level of waiting event
|
|
||||||
read expectedHeaderLen that contain body size
|
|
||||||
@param expectedHeaderLen: contains the number of bytes, which body length needs to be encoded
|
|
||||||
@param callbackBody: next state use when expected date from expectedHeaderLen
|
|
||||||
are received
|
|
||||||
"""
|
|
||||||
self._callbackBody = callbackBody
|
|
||||||
self.expect(expectedHeaderLen, self.expectedBody)
|
|
||||||
|
|
||||||
def expectedBody(self, data):
|
|
||||||
"""
|
|
||||||
Read header and wait header value to call next state
|
|
||||||
@param data: Stream that length are to header length (1|2|4 bytes)
|
|
||||||
set next state to callBack body when length read from header
|
|
||||||
are received
|
|
||||||
"""
|
|
||||||
bodyLen = None
|
|
||||||
if data.len == 1:
|
|
||||||
bodyLen = UInt8()
|
|
||||||
elif data.len == 2:
|
|
||||||
bodyLen = UInt16Be()
|
|
||||||
elif data.len == 4:
|
|
||||||
bodyLen = UInt32Be()
|
|
||||||
else:
|
|
||||||
log.error("invalid header length")
|
|
||||||
return
|
|
||||||
data.readType(bodyLen)
|
|
||||||
self.expect(bodyLen.value, self._callbackBody)
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
"""
|
|
||||||
Call when transport layer connection is made
|
|
||||||
in Client mode -> wait protocol version
|
|
||||||
"""
|
|
||||||
self.expect(12, self.recvProtocolVersion)
|
|
||||||
|
|
||||||
def readProtocolVersion(self, data):
|
|
||||||
"""
|
|
||||||
Read protocol version
|
|
||||||
@param data: Stream may contain protocol version string (ProtocolVersion)
|
|
||||||
"""
|
|
||||||
data.readType(self._version)
|
|
||||||
if not self._version.value in [ProtocolVersion.RFB003003, ProtocolVersion.RFB003007, ProtocolVersion.RFB003008]:
|
|
||||||
self._version.value = ProtocolVersion.UNKNOWN
|
|
||||||
|
|
||||||
def recvProtocolVersion(self, data):
|
|
||||||
"""
|
|
||||||
Read handshake packet
|
|
||||||
If protocol receive from client is unknown
|
|
||||||
try best version of protocol version (ProtocolVersion.RFB003008)
|
|
||||||
@param data: Stream
|
|
||||||
"""
|
|
||||||
self.readProtocolVersion(data)
|
|
||||||
if self._version.value == ProtocolVersion.UNKNOWN:
|
|
||||||
log.info("Unknown protocol version %s send 003.008"%data.getvalue())
|
|
||||||
#protocol version is unknown try best version we can handle
|
|
||||||
self._version.value = ProtocolVersion.RFB003008
|
|
||||||
#send same version of
|
|
||||||
self.send(self._version)
|
|
||||||
|
|
||||||
#next state read security
|
|
||||||
if self._version.value == ProtocolVersion.RFB003003:
|
|
||||||
self.expect(4, self.recvSecurityServer)
|
|
||||||
else:
|
|
||||||
self.expectWithHeader(1, self.recvSecurityList)
|
|
||||||
|
|
||||||
def recvSecurityServer(self, data):
|
|
||||||
"""
|
|
||||||
Security handshake for 33 RFB version
|
|
||||||
Server imposed security level
|
|
||||||
@param data: well formed packet
|
|
||||||
"""
|
|
||||||
#TODO!!!
|
|
||||||
pass
|
|
||||||
|
|
||||||
def recvSecurityList(self, data):
|
|
||||||
"""
|
|
||||||
Read security list packet send from server to client
|
|
||||||
@param data: Stream that contains well formed packet
|
|
||||||
"""
|
|
||||||
securityList = []
|
|
||||||
while data.dataLen() > 0:
|
|
||||||
securityElement = UInt8()
|
|
||||||
data.readType(securityElement)
|
|
||||||
securityList.append(securityElement)
|
|
||||||
#select high security level
|
|
||||||
for s in securityList:
|
|
||||||
if s.value in [SecurityType.NONE, SecurityType.VNC] and s > self._securityLevel:
|
|
||||||
self._securityLevel = s
|
|
||||||
break
|
|
||||||
#send back security level choosen
|
|
||||||
self.send(self._securityLevel)
|
|
||||||
if self._securityLevel.value == SecurityType.VNC:
|
|
||||||
self.expect(16, self.recvVNCChallenge)
|
|
||||||
else:
|
|
||||||
self.expect(4, self.recvSecurityResult)
|
|
||||||
|
|
||||||
def recvVNCChallenge(self, data):
|
|
||||||
"""
|
|
||||||
@summary: receive challenge in VNC authentication case
|
|
||||||
@param data: Stream that contain well formed packet
|
|
||||||
"""
|
|
||||||
key = (self._password + '\0' * 8)[:8]
|
|
||||||
newkey = []
|
|
||||||
for ki in range(len(key)):
|
|
||||||
bsrc = ord(key[ki])
|
|
||||||
btgt = 0
|
|
||||||
for i in range(8):
|
|
||||||
if bsrc & (1 << i):
|
|
||||||
btgt = btgt | (1 << 7-i)
|
|
||||||
newkey.append(chr(btgt))
|
|
||||||
|
|
||||||
algo = des(newkey)
|
|
||||||
self.send(String(algo.encrypt(data.getvalue())))
|
|
||||||
self.expect(4, self.recvSecurityResult)
|
|
||||||
|
|
||||||
def recvSecurityResult(self, data):
|
|
||||||
"""
|
|
||||||
Read security result packet
|
|
||||||
Use by server to inform connection status of client
|
|
||||||
@param data: Stream that contain well formed packet
|
|
||||||
"""
|
|
||||||
result = UInt32Be()
|
|
||||||
data.readType(result)
|
|
||||||
if result == UInt32Be(1):
|
|
||||||
log.info("Authentification failed")
|
|
||||||
if self._version.value == ProtocolVersion.RFB003008:
|
|
||||||
self.expectWithHeader(4, self.recvSecurityFailed)
|
|
||||||
else:
|
|
||||||
log.debug("Authentification OK")
|
|
||||||
self.sendClientInit()
|
|
||||||
|
|
||||||
def recvSecurityFailed(self, data):
|
|
||||||
"""
|
|
||||||
Send by server to inform reason of why it's refused client
|
|
||||||
@param data: Stream that contains well formed packet
|
|
||||||
"""
|
|
||||||
log.info("Security failed cause to %s"%data.getvalue())
|
|
||||||
|
|
||||||
def recvServerInit(self, data):
|
|
||||||
"""
|
|
||||||
Read server init packet
|
|
||||||
@param data: Stream that contains well formed packet
|
|
||||||
"""
|
|
||||||
data.readType(self._serverInit)
|
|
||||||
self.expectWithHeader(4, self.recvServerName)
|
|
||||||
|
|
||||||
def recvServerName(self, data):
|
|
||||||
"""
|
|
||||||
@summary: Read server name
|
|
||||||
@param data: Stream that contains well formed packet
|
|
||||||
"""
|
|
||||||
data.readType(self._serverName)
|
|
||||||
log.info("Server name %s"%str(self._serverName))
|
|
||||||
#end of handshake
|
|
||||||
#send pixel format
|
|
||||||
self.sendPixelFormat(self._pixelFormat)
|
|
||||||
#write encoding
|
|
||||||
self.sendSetEncoding()
|
|
||||||
#request entire zone
|
|
||||||
self.sendFramebufferUpdateRequest(False, 0, 0, self._serverInit.width.value, self._serverInit.height.value)
|
|
||||||
#now i'm ready to send event
|
|
||||||
self._clientListener.onReady()
|
|
||||||
self.expect(1, self.recvServerOrder)
|
|
||||||
|
|
||||||
def recvServerOrder(self, data):
|
|
||||||
"""
|
|
||||||
@summary: Read order receive from server
|
|
||||||
Main function for bitmap update from server to client
|
|
||||||
@param data: Stream that contains well formed packet
|
|
||||||
"""
|
|
||||||
packetType = UInt8()
|
|
||||||
data.readType(packetType)
|
|
||||||
if packetType.value == 0:
|
|
||||||
self.expect(3, self.recvFrameBufferUpdateHeader)
|
|
||||||
elif packetType.value == 2:
|
|
||||||
self._clientListener.onBell()
|
|
||||||
elif packetType.value == 3:
|
|
||||||
self.expect(7, self.recvServerCutTextHeader)
|
|
||||||
else:
|
|
||||||
log.error("Unknown message type %s"%packetType.value)
|
|
||||||
|
|
||||||
def recvFrameBufferUpdateHeader(self, data):
|
|
||||||
"""
|
|
||||||
@summary: Read frame buffer update packet header
|
|
||||||
@param data: Stream that contains well formed packet
|
|
||||||
"""
|
|
||||||
#padding
|
|
||||||
nbRect = UInt16Be()
|
|
||||||
self._nbRect = data.readType((UInt8(), nbRect))
|
|
||||||
self._nbRect = nbRect.value
|
|
||||||
self.expect(12, self.recvRectHeader)
|
|
||||||
|
|
||||||
def recvRectHeader(self, data):
|
|
||||||
"""
|
|
||||||
@summary: Read rectangle header
|
|
||||||
@param data: Stream that contains well formed packet
|
|
||||||
"""
|
|
||||||
data.readType(self._currentRect)
|
|
||||||
if self._currentRect.encoding.value == Encoding.RAW:
|
|
||||||
self.expect(self._currentRect.width.value * self._currentRect.height.value * (self._pixelFormat.BitsPerPixel.value / 8), self.recvRectBody)
|
|
||||||
|
|
||||||
def recvRectBody(self, data):
|
|
||||||
"""
|
|
||||||
@summary: Read body of rectangle update
|
|
||||||
@param data: Stream that contains well formed packet
|
|
||||||
"""
|
|
||||||
self._clientListener.recvRectangle(self._currentRect, self._pixelFormat, data.getvalue())
|
|
||||||
|
|
||||||
self._nbRect -= 1
|
|
||||||
#if there is another rect to read
|
|
||||||
if self._nbRect == 0:
|
|
||||||
#job is finish send a request
|
|
||||||
self.sendFramebufferUpdateRequest(True, 0, 0, self._serverInit.width.value, self._serverInit.height.value)
|
|
||||||
self.expect(1, self.recvServerOrder)
|
|
||||||
else:
|
|
||||||
self.expect(12, self.recvRectHeader)
|
|
||||||
|
|
||||||
def recvServerCutTextHeader(self, data):
|
|
||||||
"""
|
|
||||||
@summary: callback when expect server cut text message
|
|
||||||
@param data: Stream that contains well formed packet
|
|
||||||
"""
|
|
||||||
header = ServerCutTextHeader()
|
|
||||||
data.readType(header)
|
|
||||||
self.expect(header.size.value, self.recvServerCutTextBody)
|
|
||||||
|
|
||||||
def recvServerCutTextBody(self, data):
|
|
||||||
"""
|
|
||||||
@summary: Receive server cut text body
|
|
||||||
@param data: Stream that contains well formed packet
|
|
||||||
"""
|
|
||||||
self._clientListener.onCutText(data.getvalue())
|
|
||||||
self.expect(1, self.recvServerOrder)
|
|
||||||
|
|
||||||
def sendClientInit(self):
|
|
||||||
"""
|
|
||||||
@summary: Send client init packet
|
|
||||||
"""
|
|
||||||
self.send(self._sharedFlag)
|
|
||||||
self.expect(20, self.recvServerInit)
|
|
||||||
|
|
||||||
def sendPixelFormat(self, pixelFormat):
|
|
||||||
"""
|
|
||||||
@summary: Send pixel format structure
|
|
||||||
Very important packet that inform the image struct supported by the client
|
|
||||||
@param pixelFormat: PixelFormat struct
|
|
||||||
"""
|
|
||||||
self.send((UInt8(ClientToServerMessages.PIXEL_FORMAT), UInt16Be(), UInt8(), pixelFormat))
|
|
||||||
|
|
||||||
def sendSetEncoding(self):
|
|
||||||
"""
|
|
||||||
@summary: Send set encoding packet
|
|
||||||
Actually only RAW bitmap encoding are used
|
|
||||||
"""
|
|
||||||
self.send((UInt8(ClientToServerMessages.ENCODING), UInt8(), UInt16Be(1), SInt32Be(Encoding.RAW)))
|
|
||||||
|
|
||||||
def sendFramebufferUpdateRequest(self, incremental, x, y, width, height):
|
|
||||||
"""
|
|
||||||
@summary: Request server the specified zone
|
|
||||||
incremental means request only change before last update
|
|
||||||
"""
|
|
||||||
self.send((UInt8(ClientToServerMessages.FRAME_BUFFER_UPDATE_REQUEST), FrameBufferUpdateRequest(incremental, x, y, width, height)))
|
|
||||||
|
|
||||||
def sendKeyEvent(self, keyEvent):
|
|
||||||
"""
|
|
||||||
@summary: Write key event packet
|
|
||||||
@param keyEvent: KeyEvent struct to send
|
|
||||||
"""
|
|
||||||
self.send((UInt8(ClientToServerMessages.KEY_EVENT), keyEvent))
|
|
||||||
|
|
||||||
def sendPointerEvent(self, pointerEvent):
|
|
||||||
"""
|
|
||||||
@summary: Write pointer event packet
|
|
||||||
@param pointerEvent: PointerEvent struct use
|
|
||||||
"""
|
|
||||||
self.send((UInt8(ClientToServerMessages.POINTER_EVENT), pointerEvent))
|
|
||||||
|
|
||||||
def sendClientCutText(self, text):
|
|
||||||
"""
|
|
||||||
@summary: write client cut text event packet
|
|
||||||
"""
|
|
||||||
self.send((UInt8(ClientToServerMessages.CUT_TEXT), ClientCutText(text)))
|
|
||||||
|
|
||||||
class RFBClientListener(object):
|
|
||||||
"""
|
|
||||||
@summary: Interface use to expose event receive from RFB layer
|
|
||||||
"""
|
|
||||||
def recvRectangle(self, rectangle, pixelFormat, data):
|
|
||||||
"""
|
|
||||||
@summary: Receive rectangle order
|
|
||||||
Main update order type
|
|
||||||
@param rectangle: Rectangle type header of packet
|
|
||||||
@param pixelFormat: pixelFormat struct of current session
|
|
||||||
@param data: image data
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recvRectangle", "RFBClientListener"))
|
|
||||||
|
|
||||||
def onBell(self):
|
|
||||||
"""
|
|
||||||
@summary: receive bip from server
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onBell", "RFBClientListener"))
|
|
||||||
|
|
||||||
def onCutText(self, text):
|
|
||||||
"""
|
|
||||||
@summary: Receive cut text from server
|
|
||||||
@param text: text inner cut text event
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onCutText", "RFBClientListener"))
|
|
||||||
|
|
||||||
|
|
||||||
class RFBClientController(RFBClientListener):
|
|
||||||
"""
|
|
||||||
@summary: Class use to manage RFB order and dispatch throw observers for client side
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self._clientObservers = []
|
|
||||||
#rfb layer to send client orders
|
|
||||||
self._rfbLayer = RFB(self)
|
|
||||||
self._isReady = False
|
|
||||||
|
|
||||||
def getProtocol(self):
|
|
||||||
"""
|
|
||||||
@return: RFB layer build by controller
|
|
||||||
"""
|
|
||||||
return self._rfbLayer
|
|
||||||
|
|
||||||
def addClientObserver(self, observer):
|
|
||||||
"""
|
|
||||||
@summary: Add new observer for this protocol
|
|
||||||
@param observer: new observer
|
|
||||||
"""
|
|
||||||
self._clientObservers.append(observer)
|
|
||||||
observer._clientListener = self
|
|
||||||
|
|
||||||
def getWidth(self):
|
|
||||||
"""
|
|
||||||
@return: width of framebuffer
|
|
||||||
"""
|
|
||||||
return self._rfbLayer._serverInit.width.value
|
|
||||||
|
|
||||||
def getHeight(self):
|
|
||||||
"""
|
|
||||||
@return: height of framebuffer
|
|
||||||
"""
|
|
||||||
return self._rfbLayer._serverInit.height.value
|
|
||||||
|
|
||||||
def getScreen(self):
|
|
||||||
"""
|
|
||||||
@return: (width, height) of screen
|
|
||||||
"""
|
|
||||||
return (self.getWidth(), self.getHeight())
|
|
||||||
|
|
||||||
def setPassword(self, password):
|
|
||||||
"""
|
|
||||||
@summary: set password for vnc authentication type
|
|
||||||
@param password: password for session
|
|
||||||
"""
|
|
||||||
self._rfbLayer._password = password
|
|
||||||
|
|
||||||
def onReady(self):
|
|
||||||
"""
|
|
||||||
@summary: rfb stack is reday to send or receive event
|
|
||||||
"""
|
|
||||||
self._isReady = True
|
|
||||||
for observer in self._clientObservers:
|
|
||||||
observer.onReady()
|
|
||||||
|
|
||||||
def recvRectangle(self, rectangle, pixelFormat, data):
|
|
||||||
"""
|
|
||||||
@summary: Receive rectangle order
|
|
||||||
Main update order type
|
|
||||||
@param rectangle: Rectangle type header of packet
|
|
||||||
@param pixelFormat: pixelFormat struct of current session
|
|
||||||
@param data: image data
|
|
||||||
"""
|
|
||||||
for observer in self._clientObservers:
|
|
||||||
observer.onUpdate(rectangle.width.value, rectangle.height.value, rectangle.x.value, rectangle.y.value, pixelFormat, rectangle.encoding, data)
|
|
||||||
|
|
||||||
def onBell(self):
|
|
||||||
"""
|
|
||||||
@summary: biiiip event
|
|
||||||
"""
|
|
||||||
for observer in self._clientObservers:
|
|
||||||
observer.onBell()
|
|
||||||
|
|
||||||
def onCutText(self, text):
|
|
||||||
"""
|
|
||||||
@summary: receive cut text event
|
|
||||||
@param text: text in cut text event
|
|
||||||
"""
|
|
||||||
for observer in self._clientObservers:
|
|
||||||
observer.onCutText(text)
|
|
||||||
|
|
||||||
def onClose(self):
|
|
||||||
"""
|
|
||||||
@summary: handle on close events
|
|
||||||
"""
|
|
||||||
if not self._isReady:
|
|
||||||
log.debug("Close on non ready layer means authentication error")
|
|
||||||
return
|
|
||||||
for observer in self._clientObservers:
|
|
||||||
observer.onClose()
|
|
||||||
|
|
||||||
def sendKeyEvent(self, isDown, key):
|
|
||||||
"""
|
|
||||||
@summary: Send a key event throw RFB protocol
|
|
||||||
@param isDown: boolean notify if key is pressed or not (True if key is pressed)
|
|
||||||
@param key: ASCII code of key
|
|
||||||
"""
|
|
||||||
if not self._isReady:
|
|
||||||
log.info("Try to send key event on non ready layer")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
event = KeyEvent()
|
|
||||||
event.downFlag.value = isDown
|
|
||||||
event.key.value = key
|
|
||||||
|
|
||||||
self._rfbLayer.sendKeyEvent(event)
|
|
||||||
except InvalidValue:
|
|
||||||
log.debug("Try to send an invalid key event")
|
|
||||||
|
|
||||||
def sendPointerEvent(self, mask, x, y):
|
|
||||||
"""
|
|
||||||
@summary: Send a pointer event throw RFB protocol
|
|
||||||
@param mask: mask of button if button 1 and 3 are pressed then mask is 00000101
|
|
||||||
@param x: x coordinate of mouse pointer
|
|
||||||
@param y: y pointer of mouse pointer
|
|
||||||
"""
|
|
||||||
if not self._isReady:
|
|
||||||
log.info("Try to send pointer event on non ready layer")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
event = PointerEvent()
|
|
||||||
event.mask.value = mask
|
|
||||||
event.x.value = x
|
|
||||||
event.y.value = y
|
|
||||||
|
|
||||||
self._rfbLayer.sendPointerEvent(event)
|
|
||||||
except InvalidValue:
|
|
||||||
log.debug("Try to send an invalid pointer event")
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""
|
|
||||||
@summary: close rfb stack
|
|
||||||
"""
|
|
||||||
self._rfbLayer.close()
|
|
||||||
|
|
||||||
|
|
||||||
class ClientFactory(RawLayerClientFactory):
|
|
||||||
"""
|
|
||||||
@summary: Twisted Factory of RFB protocol
|
|
||||||
"""
|
|
||||||
def buildRawLayer(self, addr):
|
|
||||||
"""
|
|
||||||
@summary: Function call by twisted on connection
|
|
||||||
@param addr: address where client try to connect
|
|
||||||
"""
|
|
||||||
controller = RFBClientController()
|
|
||||||
self.buildObserver(controller, addr)
|
|
||||||
return controller.getProtocol()
|
|
||||||
|
|
||||||
def connectionLost(self, rfblayer, reason):
|
|
||||||
"""
|
|
||||||
@summary: Override this method to handle connection lost
|
|
||||||
@param rfblayer: rfblayer that cause connectionLost event
|
|
||||||
@param reason: twisted reason
|
|
||||||
"""
|
|
||||||
#call controller
|
|
||||||
rfblayer._clientListener.onClose()
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
|
||||||
"""
|
|
||||||
@summary: Build an RFB observer object
|
|
||||||
@param controller: controller use for rfb session
|
|
||||||
@param addr: destination
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildObserver", "ClientFactory"))
|
|
||||||
|
|
||||||
|
|
||||||
class RFBClientObserver(object):
|
|
||||||
"""
|
|
||||||
@summary: RFB client protocol observer
|
|
||||||
"""
|
|
||||||
def __init__(self, controller):
|
|
||||||
self._controller = controller
|
|
||||||
self._controller.addClientObserver(self)
|
|
||||||
|
|
||||||
def getController(self):
|
|
||||||
"""
|
|
||||||
@return: RFB controller use by observer
|
|
||||||
"""
|
|
||||||
return self._controller
|
|
||||||
|
|
||||||
def keyEvent(self, isPressed, key):
|
|
||||||
"""
|
|
||||||
@summary: Send a key event
|
|
||||||
@param isPressed: state of key
|
|
||||||
@param key: ASCII code of key
|
|
||||||
"""
|
|
||||||
self._controller.sendKeyEvent(isPressed, key)
|
|
||||||
|
|
||||||
def mouseEvent(self, button, x, y):
|
|
||||||
"""
|
|
||||||
@summary: Send a mouse event to RFB Layer
|
|
||||||
@param button: button number which is pressed (0,1,2,3,4,5,6,7)
|
|
||||||
@param x: x coordinate of mouse pointer
|
|
||||||
@param y: y coordinate of mouse pointer
|
|
||||||
"""
|
|
||||||
mask = 0
|
|
||||||
if button == 1:
|
|
||||||
mask = 1
|
|
||||||
elif button > 1:
|
|
||||||
mask = 1 << button - 1
|
|
||||||
|
|
||||||
self._controller.sendPointerEvent(mask, x, y)
|
|
||||||
|
|
||||||
def onReady(self):
|
|
||||||
"""
|
|
||||||
@summary: Event when network stack is ready to receive or send event
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RFBClientObserver"))
|
|
||||||
|
|
||||||
def onClose(self):
|
|
||||||
"""
|
|
||||||
@summary: On close event
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RFBClientObserver"))
|
|
||||||
|
|
||||||
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
|
|
||||||
"""
|
|
||||||
@summary: Receive FrameBuffer update
|
|
||||||
@param width : width of image
|
|
||||||
@param height : height of image
|
|
||||||
@param x : x position
|
|
||||||
@param y : y position
|
|
||||||
@param pixelFormat : pixel format struct from rfb.types
|
|
||||||
@param encoding : encoding struct from rfb.types
|
|
||||||
@param data : in respect of dataFormat and pixelFormat
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onUpdate", "RFBClientObserver"))
|
|
||||||
|
|
||||||
def onCutText(self, text):
|
|
||||||
"""
|
|
||||||
@summary: event when server send cut text event
|
|
||||||
@param text: text received
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onCutText", "RFBClientObserver"))
|
|
||||||
|
|
||||||
def onBell(self):
|
|
||||||
"""
|
|
||||||
@summary: event when server send biiip
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onBell", "RFBClientObserver"))
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
def KSA(key):
|
def KSA(key):
|
||||||
keylength = len(key)
|
keylength = len(key)
|
||||||
|
|
||||||
S = range(256)
|
S = list(range(256))
|
||||||
|
|
||||||
j = 0
|
j = 0
|
||||||
for i in range(256):
|
for i in range(256):
|
||||||
@@ -50,8 +50,10 @@ def RC4(key):
|
|||||||
S = KSA(key)
|
S = KSA(key)
|
||||||
return PRGA(S)
|
return PRGA(S)
|
||||||
|
|
||||||
def RC4Key(key):
|
|
||||||
return RC4([ord(c) for c in key])
|
|
||||||
|
|
||||||
def crypt(keystream, plaintext):
|
def RC4Key(key):
|
||||||
return "".join([chr(ord(c) ^ keystream.next()) for c in plaintext])
|
return RC4(key)
|
||||||
|
|
||||||
|
|
||||||
|
def crypt(keystream, plaintext: bytes) -> bytes:
|
||||||
|
return bytes([c ^ next(keystream) for c in plaintext])
|
||||||
|
|||||||
@@ -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.
|
||||||
#
|
#
|
||||||
@@ -148,13 +148,10 @@ def extractRSAKey(certificate):
|
|||||||
"""
|
"""
|
||||||
#http://www.alvestrand.no/objectid/1.2.840.113549.1.1.1.html
|
#http://www.alvestrand.no/objectid/1.2.840.113549.1.1.1.html
|
||||||
|
|
||||||
#extract binary data
|
binaryTuple = certificate.getComponentByName('tbsCertificate').getComponentByName('subjectPublicKeyInfo').getComponentByName('subjectPublicKey')
|
||||||
l = 0L
|
l = int("".join([str(i) for i in binaryTuple]), 2)
|
||||||
for b in certificate.getComponentByName('tbsCertificate').getComponentByName('subjectPublicKeyInfo').getComponentByName('subjectPublicKey'):
|
return extractRSAKeyFromASN1(hex(l)[2:-1].decode('hex'))
|
||||||
l = (l << 1) | b
|
|
||||||
|
|
||||||
rsaKey = decoder.decode(hex(l)[2:-1].decode('hex'), asn1Spec=RSAPublicKey())[0]
|
def extractRSAKeyFromASN1(subjectPublicKey):
|
||||||
|
rsaKey = decoder.decode(subjectPublicKey, asn1Spec=RSAPublicKey())[0]
|
||||||
return rsaKey.getComponentByName('modulus')._value , rsaKey.getComponentByName('publicExponent')._value
|
return rsaKey.getComponentByName('modulus')._value , rsaKey.getComponentByName('publicExponent')._value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
219
rdpy/ui/qt4.py
219
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.
|
||||||
#
|
#
|
||||||
@@ -23,13 +23,12 @@ Qt specific code
|
|||||||
QRemoteDesktop is a widget use for render in rdpy
|
QRemoteDesktop is a widget use for render in rdpy
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from PyQt4 import QtGui, QtCore
|
from PyQt5 import QtWidgets
|
||||||
from rdpy.protocol.rfb.rfb import RFBClientObserver
|
from rdpy.core.rdp import RDPClientObserver
|
||||||
from rdpy.protocol.rdp.rdp import RDPClientObserver
|
from rdpy.model.error import CallPureVirtualFuntion
|
||||||
from rdpy.core.error import CallPureVirtualFuntion
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import rdpy.core.log as log
|
import rdpy.model.log as log
|
||||||
import rle
|
import rle
|
||||||
|
|
||||||
class QAdaptor(object):
|
class QAdaptor(object):
|
||||||
@@ -60,12 +59,6 @@ class QAdaptor(object):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendWheelEvent", "QAdaptor"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendWheelEvent", "QAdaptor"))
|
||||||
|
|
||||||
def getWidget(self):
|
|
||||||
"""
|
|
||||||
@return: widget use for render
|
|
||||||
"""
|
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getWidget", "QAdaptor"))
|
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
"""
|
"""
|
||||||
@summary: Call when you want to close connection
|
@summary: Call when you want to close connection
|
||||||
@@ -78,114 +71,12 @@ def qtImageFormatFromRFBPixelFormat(pixelFormat):
|
|||||||
@summary: convert RFB pixel format to QtGui.QImage format
|
@summary: convert RFB pixel format to QtGui.QImage format
|
||||||
"""
|
"""
|
||||||
if pixelFormat.BitsPerPixel.value == 32:
|
if pixelFormat.BitsPerPixel.value == 32:
|
||||||
return QtGui.QImage.Format_RGB32
|
return QtWidgets.QImage.Format_RGB32
|
||||||
elif pixelFormat.BitsPerPixel.value == 16:
|
elif pixelFormat.BitsPerPixel.value == 16:
|
||||||
return QtGui.QImage.Format_RGB16
|
return QtWidgets.QImage.Format_RGB16
|
||||||
|
|
||||||
class RFBClientQt(RFBClientObserver, QAdaptor):
|
|
||||||
"""
|
|
||||||
@summary: QAdaptor for specific RFB protocol stack
|
|
||||||
is to an RFB observer
|
|
||||||
"""
|
|
||||||
def __init__(self, controller):
|
|
||||||
"""
|
|
||||||
@param controller: controller for observer
|
|
||||||
@param width: width of widget
|
|
||||||
@param height: height of widget
|
|
||||||
"""
|
|
||||||
RFBClientObserver.__init__(self, controller)
|
|
||||||
self._widget = QRemoteDesktop(self, 1024, 800)
|
|
||||||
|
|
||||||
def getWidget(self):
|
def RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data):
|
||||||
"""
|
|
||||||
@return: widget use for render
|
|
||||||
"""
|
|
||||||
return self._widget
|
|
||||||
|
|
||||||
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
|
|
||||||
"""
|
|
||||||
@summary: Implement RFBClientObserver interface
|
|
||||||
@param width: width of new image
|
|
||||||
@param height: height of new image
|
|
||||||
@param x: x position of new image
|
|
||||||
@param y: y position of new image
|
|
||||||
@param pixelFormat: pixefFormat structure in rfb.message.PixelFormat
|
|
||||||
@param encoding: encoding type rfb.message.Encoding
|
|
||||||
@param data: image data in accordance with pixel format and encoding
|
|
||||||
"""
|
|
||||||
imageFormat = qtImageFormatFromRFBPixelFormat(pixelFormat)
|
|
||||||
if imageFormat is None:
|
|
||||||
log.error("Receive image in bad format")
|
|
||||||
return
|
|
||||||
|
|
||||||
image = QtGui.QImage(data, width, height, imageFormat)
|
|
||||||
self._widget.notifyImage(x, y, image, width, height)
|
|
||||||
|
|
||||||
def onCutText(self, text):
|
|
||||||
"""
|
|
||||||
@summary: event when server send cut text event
|
|
||||||
@param text: text received
|
|
||||||
"""
|
|
||||||
|
|
||||||
def onBell(self):
|
|
||||||
"""
|
|
||||||
@summary: event when server send biiip
|
|
||||||
"""
|
|
||||||
|
|
||||||
def onReady(self):
|
|
||||||
"""
|
|
||||||
@summary: Event when network stack is ready to receive or send event
|
|
||||||
"""
|
|
||||||
(width, height) = self._controller.getScreen()
|
|
||||||
self._widget.resize(width, height)
|
|
||||||
|
|
||||||
def sendMouseEvent(self, e, isPressed):
|
|
||||||
"""
|
|
||||||
@summary: Convert Qt mouse event to RFB mouse event
|
|
||||||
@param e: qMouseEvent
|
|
||||||
@param isPressed: event come from press or release action
|
|
||||||
"""
|
|
||||||
button = e.button()
|
|
||||||
buttonNumber = 0
|
|
||||||
if button == QtCore.Qt.LeftButton:
|
|
||||||
buttonNumber = 1
|
|
||||||
elif button == QtCore.Qt.MidButton:
|
|
||||||
buttonNumber = 2
|
|
||||||
elif button == QtCore.Qt.RightButton:
|
|
||||||
buttonNumber = 3
|
|
||||||
self.mouseEvent(buttonNumber, e.pos().x(), e.pos().y())
|
|
||||||
|
|
||||||
def sendKeyEvent(self, e, isPressed):
|
|
||||||
"""
|
|
||||||
@summary: Convert Qt key press event to RFB press event
|
|
||||||
@param e: qKeyEvent
|
|
||||||
@param isPressed: event come from press or release action
|
|
||||||
"""
|
|
||||||
self.keyEvent(isPressed, e.nativeVirtualKey())
|
|
||||||
|
|
||||||
def sendWheelEvent(self, e):
|
|
||||||
"""
|
|
||||||
@summary: Convert Qt wheel event to RFB Wheel event
|
|
||||||
@param e: QKeyEvent
|
|
||||||
@param isPressed: event come from press or release action
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def closeEvent(self, e):
|
|
||||||
"""
|
|
||||||
@summary: Call when you want to close connection
|
|
||||||
@param: QCloseEvent
|
|
||||||
"""
|
|
||||||
self._controller.close()
|
|
||||||
|
|
||||||
def onClose(self):
|
|
||||||
"""
|
|
||||||
@summary: Call when stack is close
|
|
||||||
"""
|
|
||||||
#do something maybe a message
|
|
||||||
pass
|
|
||||||
|
|
||||||
def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
|
|
||||||
"""
|
"""
|
||||||
@summary: Bitmap transformation to Qt object
|
@summary: Bitmap transformation to Qt object
|
||||||
@param width: width of bitmap
|
@param width: width of bitmap
|
||||||
@@ -201,36 +92,36 @@ def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
|
|||||||
if isCompress:
|
if isCompress:
|
||||||
buf = bytearray(width * height * 2)
|
buf = bytearray(width * height * 2)
|
||||||
rle.bitmap_decompress(buf, width, height, data, 2)
|
rle.bitmap_decompress(buf, width, height, data, 2)
|
||||||
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB555)
|
image = QtWidgets.QImage(buf, width, height, QtWidgets.QImage.Format_RGB555)
|
||||||
else:
|
else:
|
||||||
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB555).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
|
image = QtWidgets.QImage(data, width, height, QtWidgets.QImage.Format_RGB555).transformed(QtWidgets.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
|
||||||
|
|
||||||
elif bitsPerPixel == 16:
|
elif bitsPerPixel == 16:
|
||||||
if isCompress:
|
if isCompress:
|
||||||
buf = bytearray(width * height * 2)
|
buf = bytearray(width * height * 2)
|
||||||
rle.bitmap_decompress(buf, width, height, data, 2)
|
rle.bitmap_decompress(buf, width, height, data, 2)
|
||||||
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB16)
|
image = QtWidgets.QImage(buf, width, height, QtWidgets.QImage.Format_RGB16)
|
||||||
else:
|
else:
|
||||||
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB16).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
|
image = QtWidgets.QImage(data, width, height, QtWidgets.QImage.Format_RGB16).transformed(QtWidgets.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
|
||||||
|
|
||||||
elif bitsPerPixel == 24:
|
elif bitsPerPixel == 24:
|
||||||
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_RGB888)
|
image = QtWidgets.QImage(buf, width, height, QtWidgets.QImage.Format_RGB888)
|
||||||
else:
|
else:
|
||||||
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB888).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
|
image = QtWidgets.QImage(data, width, height, QtWidgets.QImage.Format_RGB888).transformed(QtWidgets.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
|
||||||
|
|
||||||
elif bitsPerPixel == 32:
|
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_RGB32)
|
image = QtWidgets.QImage(buf, width, height, QtWidgets.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 = QtWidgets.QImage(data, width, height, QtWidgets.QImage.Format_RGB32).transformed(QtWidgets.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
|
||||||
else:
|
else:
|
||||||
log.error("Receive image in bad format")
|
log.error("Receive image in bad format")
|
||||||
image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
|
image = QtWidgets.QImage(width, height, QtWidgets.QImage.Format_RGB32)
|
||||||
return image
|
return image
|
||||||
|
|
||||||
class RDPClientQt(RDPClientObserver, QAdaptor):
|
class RDPClientQt(RDPClientObserver, QAdaptor):
|
||||||
@@ -239,12 +130,12 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
|
|||||||
"""
|
"""
|
||||||
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)
|
||||||
|
|
||||||
@@ -299,17 +190,17 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
|
|||||||
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: 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)
|
||||||
@@ -317,41 +208,44 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
|
|||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@summary: Call when stack is ready
|
@summary: Call when stack is ready
|
||||||
|
@see: rdp.RDPClientObserver.onReady
|
||||||
"""
|
"""
|
||||||
#do something maybe a loader
|
#do something maybe a loader
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Windows session is ready
|
||||||
|
@see: rdp.RDPClientObserver.onSessionReady
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: Call when stack is close
|
@summary: Call when stack is close
|
||||||
|
@see: rdp.RDPClientObserver.onClose
|
||||||
"""
|
"""
|
||||||
#do something maybe a message
|
#do something maybe a message
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class QRemoteDesktop(QtGui.QWidget):
|
class QRemoteDesktop(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
@summary: 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
|
||||||
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
|
self._buffer = QtWidgets.QImage(width, height, QtWidgets.QImage.Format_RGB32)
|
||||||
|
|
||||||
def notifyImage(self, x, y, qimage, width, height):
|
def notifyImage(self, x, y, qimage, width, height):
|
||||||
"""
|
"""
|
||||||
@@ -360,27 +254,30 @@ class QRemoteDesktop(QtGui.QWidget):
|
|||||||
@param y: y position of new image
|
@param y: y position of new image
|
||||||
@param qimage: new QImage
|
@param qimage: new QImage
|
||||||
"""
|
"""
|
||||||
#save in refresh list (order is important)
|
#fill buffer image
|
||||||
self._refresh.append((x, y, qimage, width, height))
|
with QtWidgets.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 = QtWidgets.QImage(width, height, QtWidgets.QImage.Format_RGB32)
|
||||||
|
QtWidgets.QWidget.resize(self, width, height)
|
||||||
|
|
||||||
def paintEvent(self, e):
|
def paintEvent(self, e):
|
||||||
"""
|
"""
|
||||||
@summary: Call when Qt renderer engine estimate that is needed
|
@summary: Call when Qt renderer engine estimate that is needed
|
||||||
@param e: QEvent
|
@param e: QEvent
|
||||||
"""
|
"""
|
||||||
#fill buffer image
|
|
||||||
with QtGui.QPainter(self._buffer) as qp:
|
|
||||||
#draw image
|
|
||||||
for (x, y, image, width, height) in self._refresh:
|
|
||||||
qp.drawImage(x, y, image, 0, 0, width, height)
|
|
||||||
#draw in widget
|
#draw in widget
|
||||||
with QtGui.QPainter(self) as qp:
|
with QtWidgets.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):
|
||||||
"""
|
"""
|
||||||
@summary: Call when mouse move
|
@summary: Call when mouse move
|
||||||
|
|||||||
@@ -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
setup.py
26
setup.py
@@ -4,38 +4,40 @@ import setuptools
|
|||||||
from distutils.core import setup, Extension
|
from distutils.core import setup, Extension
|
||||||
|
|
||||||
setup(name='rdpy',
|
setup(name='rdpy',
|
||||||
version='1.2.0',
|
version='2.0.0',
|
||||||
description='Remote Desktop Protocol in Python',
|
description='Remote Desktop Protocol in Python',
|
||||||
long_description="""
|
long_description="""
|
||||||
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol.
|
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server side). RDPY is built over the event driven network engine Twisted.
|
||||||
RDPY is 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.core',
|
'rdpy.model',
|
||||||
'rdpy.security',
|
'rdpy.security',
|
||||||
'rdpy.protocol',
|
'rdpy.core.pdu',
|
||||||
'rdpy.protocol.rdp',
|
'rdpy.core.nla',
|
||||||
'rdpy.protocol.rdp.pdu',
|
'rdpy.core.t125',
|
||||||
'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.py',
|
'bin/rdpy-rdpclient.py',
|
||||||
'bin/rdpy-rdpproxy.py',
|
'bin/rdpy-rdphoneypot.py',
|
||||||
|
'bin/rdpy-rdpmitm.py',
|
||||||
'bin/rdpy-rdpscreenshot.py',
|
'bin/rdpy-rdpscreenshot.py',
|
||||||
|
'bin/rdpy-rssplayer.py',
|
||||||
'bin/rdpy-vncclient.py',
|
'bin/rdpy-vncclient.py',
|
||||||
'bin/rdpy-vncscreenshot.py'
|
'bin/rdpy-vncscreenshot.py'
|
||||||
],
|
],
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'twisted',
|
'PyQt5',
|
||||||
'pyopenssl',
|
'PyQt5-sip',
|
||||||
'service_identity',
|
'service_identity',
|
||||||
'qt4reactor',
|
|
||||||
'rsa',
|
'rsa',
|
||||||
|
'pyasn1'
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class LayerTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
class TestAutomata(rdpy.core.layer.RawLayer):
|
class TestAutomata(rdpy.core.layer.RawLayer):
|
||||||
def expectedCallBack(self, data):
|
def expectedCallBack(self, data):
|
||||||
if data.dataLen() == 4:
|
if data.data_len() == 4:
|
||||||
raise LayerTest.LayerCaseException()
|
raise LayerTest.LayerCaseException()
|
||||||
|
|
||||||
t = TestAutomata()
|
t = TestAutomata()
|
||||||
@@ -69,7 +69,7 @@ class LayerTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
class TestAutomata(rdpy.core.layer.RawLayer):
|
class TestAutomata(rdpy.core.layer.RawLayer):
|
||||||
def expectedCallBack(self, data):
|
def expectedCallBack(self, data):
|
||||||
if data.dataLen() == 4:
|
if data.data_len() == 4:
|
||||||
raise LayerTest.LayerCaseException()
|
raise LayerTest.LayerCaseException()
|
||||||
|
|
||||||
t = TestAutomata()
|
t = TestAutomata()
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
def __write__(self, s):
|
def __write__(self, s):
|
||||||
raise Exception()
|
raise Exception()
|
||||||
s = rdpy.core.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
self.assertRaises(Exception, s.writeType, TestType(conditional = lambda:True))
|
self.assertRaises(Exception, s.write_type, TestType(conditional = lambda:True))
|
||||||
|
|
||||||
@unittest.expectedFailure
|
@unittest.expectedFailure
|
||||||
def test_type_write_conditional_false(self):
|
def test_type_write_conditional_false(self):
|
||||||
@@ -67,7 +67,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
def __write__(self, s):
|
def __write__(self, s):
|
||||||
raise Exception()
|
raise Exception()
|
||||||
s = rdpy.core.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
self.assertRaises(Exception, s.writeType, TestType(conditional = lambda:False))
|
self.assertRaises(Exception, s.write_type, TestType(conditional = lambda:False))
|
||||||
|
|
||||||
def test_type_read_conditional_true(self):
|
def test_type_read_conditional_true(self):
|
||||||
"""
|
"""
|
||||||
@@ -77,7 +77,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
def __read__(self, s):
|
def __read__(self, s):
|
||||||
raise Exception()
|
raise Exception()
|
||||||
s = rdpy.core.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
self.assertRaises(Exception, s.readType, TestType(conditional = lambda:True))
|
self.assertRaises(Exception, s.read_type, TestType(conditional = lambda:True))
|
||||||
|
|
||||||
@unittest.expectedFailure
|
@unittest.expectedFailure
|
||||||
def test_type_read_conditional_false(self):
|
def test_type_read_conditional_false(self):
|
||||||
@@ -88,7 +88,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
def __read__(self, s):
|
def __read__(self, s):
|
||||||
raise Exception()
|
raise Exception()
|
||||||
s = rdpy.core.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
self.assertRaises(Exception, s.readType, TestType(conditional = lambda:False))
|
self.assertRaises(Exception, s.read_type, TestType(conditional = lambda:False))
|
||||||
|
|
||||||
|
|
||||||
def test_sizeof_conditional_true(self):
|
def test_sizeof_conditional_true(self):
|
||||||
@@ -138,7 +138,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
@summary: test write uint8 in stream
|
@summary: test write uint8 in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.core.type.UInt8(1))
|
s.write_type(rdpy.core.type.UInt8(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x01', "invalid stream write")
|
self.assertEqual(''.join(s.buflist), '\x01', "invalid stream write")
|
||||||
|
|
||||||
def test_stream_write_uint16Le_type(self):
|
def test_stream_write_uint16Le_type(self):
|
||||||
@@ -146,7 +146,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
@summary: test write UInt16Le in stream
|
@summary: test write UInt16Le in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.core.type.UInt16Le(1))
|
s.write_type(rdpy.core.type.UInt16Le(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x01\x00', "invalid stream write")
|
self.assertEqual(''.join(s.buflist), '\x01\x00', "invalid stream write")
|
||||||
|
|
||||||
def test_stream_write_uint16Be_type(self):
|
def test_stream_write_uint16Be_type(self):
|
||||||
@@ -154,7 +154,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
@summary: test write UInt16Be in stream
|
@summary: test write UInt16Be in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.core.type.UInt16Be(1))
|
s.write_type(rdpy.core.type.UInt16Be(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x00\x01', "invalid stream write")
|
self.assertEqual(''.join(s.buflist), '\x00\x01', "invalid stream write")
|
||||||
|
|
||||||
def test_stream_write_uint24Le_type(self):
|
def test_stream_write_uint24Le_type(self):
|
||||||
@@ -162,7 +162,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
@summary: test write UInt24Le in stream
|
@summary: test write UInt24Le in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.core.type.UInt24Le(1))
|
s.write_type(rdpy.core.type.UInt24Le(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x01\x00\x00', "invalid stream write")
|
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):
|
||||||
@@ -170,7 +170,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
@summary: test write uint24Be in stream
|
@summary: test write uint24Be in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.core.type.UInt24Be(1))
|
s.write_type(rdpy.core.type.UInt24Be(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x00\x00\x01', "invalid stream write")
|
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):
|
||||||
@@ -178,7 +178,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
@summary: test write UInt32Le in stream
|
@summary: test write UInt32Le in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.core.type.UInt32Le(1))
|
s.write_type(rdpy.core.type.UInt32Le(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x01\x00\x00\x00', "invalid stream write")
|
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):
|
||||||
@@ -186,7 +186,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
@summary: test write UInt32Be in stream
|
@summary: test write UInt32Be in stream
|
||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream()
|
s = rdpy.core.type.Stream()
|
||||||
s.writeType(rdpy.core.type.UInt32Be(1))
|
s.write_type(rdpy.core.type.UInt32Be(1))
|
||||||
self.assertEqual(''.join(s.buflist), '\x00\x00\x00\x01', "invalid stream write")
|
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):
|
||||||
@@ -195,9 +195,9 @@ class TypeTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream('\x01')
|
s = rdpy.core.type.Stream('\x01')
|
||||||
t = rdpy.core.type.UInt8()
|
t = rdpy.core.type.UInt8()
|
||||||
s.readType(t)
|
s.read_type(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.data_len(), 0, "not read all stream")
|
||||||
|
|
||||||
def test_stream_read_uint16Le_type(self):
|
def test_stream_read_uint16Le_type(self):
|
||||||
"""
|
"""
|
||||||
@@ -205,9 +205,9 @@ class TypeTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream('\x01\x00')
|
s = rdpy.core.type.Stream('\x01\x00')
|
||||||
t = rdpy.core.type.UInt16Le()
|
t = rdpy.core.type.UInt16Le()
|
||||||
s.readType(t)
|
s.read_type(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.data_len(), 0, "not read all stream")
|
||||||
|
|
||||||
def test_stream_read_uint16Be_type(self):
|
def test_stream_read_uint16Be_type(self):
|
||||||
"""
|
"""
|
||||||
@@ -215,9 +215,9 @@ class TypeTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream('\x00\x01')
|
s = rdpy.core.type.Stream('\x00\x01')
|
||||||
t = rdpy.core.type.UInt16Be()
|
t = rdpy.core.type.UInt16Be()
|
||||||
s.readType(t)
|
s.read_type(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.data_len(), 0, "not read all stream")
|
||||||
|
|
||||||
def test_stream_read_uint24Le_type(self):
|
def test_stream_read_uint24Le_type(self):
|
||||||
"""
|
"""
|
||||||
@@ -225,9 +225,9 @@ class TypeTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream('\x01\x00\x00')
|
s = rdpy.core.type.Stream('\x01\x00\x00')
|
||||||
t = rdpy.core.type.UInt24Le()
|
t = rdpy.core.type.UInt24Le()
|
||||||
s.readType(t)
|
s.read_type(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.data_len(), 0, "not read all stream")
|
||||||
|
|
||||||
def test_stream_read_uint24Be_type(self):
|
def test_stream_read_uint24Be_type(self):
|
||||||
"""
|
"""
|
||||||
@@ -235,9 +235,9 @@ class TypeTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream('\x00\x00\x01')
|
s = rdpy.core.type.Stream('\x00\x00\x01')
|
||||||
t = rdpy.core.type.UInt24Be()
|
t = rdpy.core.type.UInt24Be()
|
||||||
s.readType(t)
|
s.read_type(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.data_len(), 0, "not read all stream")
|
||||||
|
|
||||||
def test_stream_read_uint32Le_type(self):
|
def test_stream_read_uint32Le_type(self):
|
||||||
"""
|
"""
|
||||||
@@ -245,9 +245,9 @@ class TypeTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream('\x01\x00\x00\x00')
|
s = rdpy.core.type.Stream('\x01\x00\x00\x00')
|
||||||
t = rdpy.core.type.UInt32Le()
|
t = rdpy.core.type.UInt32Le()
|
||||||
s.readType(t)
|
s.read_type(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.data_len(), 0, "not read all stream")
|
||||||
|
|
||||||
def test_stream_read_uint32Be_type(self):
|
def test_stream_read_uint32Be_type(self):
|
||||||
"""
|
"""
|
||||||
@@ -255,9 +255,9 @@ class TypeTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
s = rdpy.core.type.Stream('\x00\x00\x00\x01')
|
s = rdpy.core.type.Stream('\x00\x00\x00\x01')
|
||||||
t = rdpy.core.type.UInt32Be()
|
t = rdpy.core.type.UInt32Be()
|
||||||
s.readType(t)
|
s.read_type(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.data_len(), 0, "not read all stream")
|
||||||
|
|
||||||
def test_stream_read_optional_singletype(self):
|
def test_stream_read_optional_singletype(self):
|
||||||
"""
|
"""
|
||||||
@@ -267,7 +267,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
t = rdpy.core.type.SimpleType("I", 4, False, 0, optional = True)
|
t = rdpy.core.type.SimpleType("I", 4, False, 0, optional = True)
|
||||||
#empty stream
|
#empty stream
|
||||||
s1 = rdpy.core.type.Stream()
|
s1 = rdpy.core.type.Stream()
|
||||||
s1.readType(t)
|
s1.read_type(t)
|
||||||
self.assertEqual(t.value, 0, "invalid stream read optional value")
|
self.assertEqual(t.value, 0, "invalid stream read optional value")
|
||||||
|
|
||||||
def test_stream_read_conditional_singletype_false(self):
|
def test_stream_read_conditional_singletype_false(self):
|
||||||
@@ -277,7 +277,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
#unsigned int case
|
#unsigned int case
|
||||||
t = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:False)
|
t = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:False)
|
||||||
s1 = rdpy.core.type.Stream("\x01\x00\x00\x00")
|
s1 = rdpy.core.type.Stream("\x01\x00\x00\x00")
|
||||||
s1.readType(t)
|
s1.read_type(t)
|
||||||
self.assertEqual(t.value, 0, "invalid stream read conditional value")
|
self.assertEqual(t.value, 0, "invalid stream read conditional value")
|
||||||
|
|
||||||
def test_stream_read_conditional_singletype_true(self):
|
def test_stream_read_conditional_singletype_true(self):
|
||||||
@@ -287,7 +287,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
#unsigned int case
|
#unsigned int case
|
||||||
t = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:True)
|
t = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:True)
|
||||||
s1 = rdpy.core.type.Stream("\x01\x00\x00\x00")
|
s1 = rdpy.core.type.Stream("\x01\x00\x00\x00")
|
||||||
s1.readType(t)
|
s1.read_type(t)
|
||||||
self.assertEqual(t.value, 1, "invalid stream read conditional value")
|
self.assertEqual(t.value, 1, "invalid stream read conditional value")
|
||||||
|
|
||||||
def test_stream_read_rollback_constant_constraint(self):
|
def test_stream_read_rollback_constant_constraint(self):
|
||||||
@@ -302,9 +302,9 @@ class TypeTest(unittest.TestCase):
|
|||||||
|
|
||||||
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00")
|
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||||
try:
|
try:
|
||||||
s.readType(TestComposite())
|
s.read_type(TestComposite())
|
||||||
except Exception:
|
except Exception:
|
||||||
self.assertEqual(s.readLen(), 0, "invalid stream roll back operation")
|
self.assertEqual(s.read_len(), 0, "invalid stream roll back operation")
|
||||||
return
|
return
|
||||||
self.assertTrue(False, "Constant constraint fail")
|
self.assertTrue(False, "Constant constraint fail")
|
||||||
|
|
||||||
@@ -327,9 +327,9 @@ class TypeTest(unittest.TestCase):
|
|||||||
|
|
||||||
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||||
try:
|
try:
|
||||||
s.readType(TestComposite())
|
s.read_type(TestComposite())
|
||||||
except Exception:
|
except Exception:
|
||||||
self.assertEqual(s.readLen(), 0, "invalid stream roll back operation")
|
self.assertEqual(s.read_len(), 0, "invalid stream roll back operation")
|
||||||
return
|
return
|
||||||
self.assertTrue(False, "Constant constraint fail")
|
self.assertTrue(False, "Constant constraint fail")
|
||||||
|
|
||||||
@@ -352,9 +352,9 @@ class TypeTest(unittest.TestCase):
|
|||||||
|
|
||||||
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||||
try:
|
try:
|
||||||
s.readType(TestComposite())
|
s.read_type(TestComposite())
|
||||||
except Exception:
|
except Exception:
|
||||||
self.assertEqual(s.readLen(), 0, "invalid stream roll back operation")
|
self.assertEqual(s.read_len(), 0, "invalid stream roll back operation")
|
||||||
return
|
return
|
||||||
self.assertTrue(False, "Constant constraint fail")
|
self.assertTrue(False, "Constant constraint fail")
|
||||||
|
|
||||||
@@ -369,8 +369,8 @@ class TypeTest(unittest.TestCase):
|
|||||||
rdpy.core.type.CompositeType.__init__(self, readLen = readLen)
|
rdpy.core.type.CompositeType.__init__(self, readLen = readLen)
|
||||||
self.padding = rdpy.core.type.UInt32Le(0)
|
self.padding = rdpy.core.type.UInt32Le(0)
|
||||||
s = rdpy.core.type.Stream("\x00" * 10)
|
s = rdpy.core.type.Stream("\x00" * 10)
|
||||||
s.readType(TestReadLength(rdpy.core.type.UInt8(10)))
|
s.read_type(TestReadLength(rdpy.core.type.UInt8(10)))
|
||||||
self.assertEqual(s.dataLen(), 0, "invalid stream read trash data as padding")
|
self.assertEqual(s.data_len(), 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):
|
||||||
"""
|
"""
|
||||||
@@ -383,7 +383,7 @@ class TypeTest(unittest.TestCase):
|
|||||||
rdpy.core.type.CompositeType.__init__(self, readLen = readLen)
|
rdpy.core.type.CompositeType.__init__(self, readLen = readLen)
|
||||||
self.padding = rdpy.core.type.UInt32Le(0)
|
self.padding = rdpy.core.type.UInt32Le(0)
|
||||||
s = rdpy.core.type.Stream("\x00" * 10)
|
s = rdpy.core.type.Stream("\x00" * 10)
|
||||||
self.assertRaises(InvalidSize, s.readType, TestReadLength(rdpy.core.type.UInt8(2)))
|
self.assertRaises(InvalidSize, s.read_type, TestReadLength(rdpy.core.type.UInt8(2)))
|
||||||
|
|
||||||
def test_stream_read_string(self):
|
def test_stream_read_string(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ 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.core.t125.ber as ber
|
||||||
import rdpy.core.type as type
|
import rdpy.core.type as type
|
||||||
import rdpy.core.error as error
|
|
||||||
|
|
||||||
class BERTest(unittest.TestCase):
|
class BERTest(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
@@ -40,7 +40,7 @@ class BERTest(unittest.TestCase):
|
|||||||
@summary: test readLength function in ber module
|
@summary: test readLength function in ber module
|
||||||
"""
|
"""
|
||||||
s1 = type.Stream()
|
s1 = type.Stream()
|
||||||
s1.writeType(type.UInt8(0x1a))
|
s1.write_type(type.UInt8(0x1a))
|
||||||
s1.pos = 0
|
s1.pos = 0
|
||||||
|
|
||||||
l1 = ber.readLength(s1)
|
l1 = ber.readLength(s1)
|
||||||
@@ -48,7 +48,7 @@ class BERTest(unittest.TestCase):
|
|||||||
self.assertTrue(l1 == 0x1a, "readLength fail in small format")
|
self.assertTrue(l1 == 0x1a, "readLength fail in small format")
|
||||||
|
|
||||||
s2 = type.Stream()
|
s2 = type.Stream()
|
||||||
s2.writeType((type.UInt8(0x81),type.UInt8(0xab)))
|
s2.write_type((type.UInt8(0x81), type.UInt8(0xab)))
|
||||||
s2.pos = 0
|
s2.pos = 0
|
||||||
|
|
||||||
l2 = ber.readLength(s2)
|
l2 = ber.readLength(s2)
|
||||||
@@ -56,7 +56,7 @@ class BERTest(unittest.TestCase):
|
|||||||
self.assertTrue(l2 == 0xab, "readLength fail in big format of size 1")
|
self.assertTrue(l2 == 0xab, "readLength fail in big format of size 1")
|
||||||
|
|
||||||
s3 = type.Stream()
|
s3 = type.Stream()
|
||||||
s3.writeType((type.UInt8(0x82),type.UInt16Be(0xabab)))
|
s3.write_type((type.UInt8(0x82), type.UInt16Be(0xabab)))
|
||||||
s3.pos = 0
|
s3.pos = 0
|
||||||
|
|
||||||
l3 = ber.readLength(s3)
|
l3 = ber.readLength(s3)
|
||||||
|
|||||||
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.core.nla import ntlm, cssp
|
||||||
|
from rdpy.security import rc4
|
||||||
|
|
||||||
|
pubKeyHex = """
|
||||||
|
MIGJAoGBAJ6VtUEDxTPqKWUrZe8wcd1zuzA77Mpyz73g+C
|
||||||
|
H/ppd2oQi10saVgdK6cRBKrCU0N6DD\nV/DqH4yE63vmbF
|
||||||
|
AmH7dBCljTgIc9C0HZvFQ6D3cUefW5pDjrEwg1rr+zF1ri
|
||||||
|
WIk5xCJ/FleQCK+R\nO5XIU9DAjhmK8xC8yMdC+xLeLV6D
|
||||||
|
AgMBAAE=
|
||||||
|
"""
|
||||||
|
peer0_0 = """
|
||||||
|
MC+gAwIBAqEoMCYwJKAiBCBOVExNU1NQAAEAAAA1gghgAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAA==
|
||||||
|
"""
|
||||||
|
peer1_0 = """
|
||||||
|
MIIBCaADAgECoYIBADCB/TCB+qCB9wSB9E5UTE1TU1AAAg
|
||||||
|
AAAA4ADgA4AAAANYKJYnPHQ6nn/Lv8\nAAAAAAAAAACuAK
|
||||||
|
4ARgAAAAYBsR0AAAAPUwBJAFIAQQBEAEUATAACAA4AUwBJ
|
||||||
|
AFIAQQBEAEUATAAB\nABYAVwBBAFYALQBHAEwAVwAtADAA
|
||||||
|
MAA5AAQAGgBTAGkAcgBhAGQAZQBsAC4AbABvAGMAYQBsAA
|
||||||
|
MA\nMgB3AGEAdgAtAGcAbAB3AC0AMAAwADkALgBTAGkAcg
|
||||||
|
BhAGQAZQBsAC4AbABvAGMAYQBsAAUAGgBT\nAGkAcgBhAG
|
||||||
|
QAZQBsAC4AbABvAGMAYQBsAAcACABWkzQyx1XQAQAAAAA=
|
||||||
|
"""
|
||||||
|
peer0_1 = """
|
||||||
|
MIICD6ADAgECoYIBYjCCAV4wggFaoIIBVgSCAVJOVExNU1
|
||||||
|
NQAAMAAAAYABgAUAAAANoA2gBoAAAA\nCAAIAEAAAAAIAA
|
||||||
|
gASAAAAAAAAABQAAAAEAAQAEIBAAA1gghgYwBvAGMAbwB0
|
||||||
|
AG8AdABvABqKwrxk\n2sAom6gUCFFt1rgpCdKZGTNwnlGg
|
||||||
|
bsU5R/OelmrD/LLrx+ABAQAAAAAAAABFCDLHVdABKQnSmR
|
||||||
|
kz\ncJ4AAAAAAgAOAFMASQBSAEEARABFAEwAAQAWAFcAQQ
|
||||||
|
BWAC0ARwBMAFcALQAwADAAOQAEABoAUwBp\nAHIAYQBkAG
|
||||||
|
UAbAAuAGwAbwBjAGEAbAADADIAdwBhAHYALQBnAGwAdwAt
|
||||||
|
ADAAMAA5AC4AUwBpAHIA\nYQBkAGUAbAAuAGwAbwBjAGEA
|
||||||
|
bAAFABoAUwBpAHIAYQBkAGUAbAAuAGwAbwBjAGEAbAAHAA
|
||||||
|
gAVpM0\nMsdV0AEAAAAAv+z19mkBOu9b0Kv+P991MKOCAK
|
||||||
|
AEggCcAQAAAMQOzZaPZ8DdAAAAAM+IvTDiU0pL\njUnU6a
|
||||||
|
NjH+gZWeaIlqpQNYECmpElixwPj8aRRFVfTtkbw66U3gmo
|
||||||
|
3YBkUoVK8tfHESkivuWtV2tP\n3KGuAFv/6GzbFYQYlA7r
|
||||||
|
zZ1Bw072ps8s9cWeoNmAX6oiZmFW3j7LX3xkr7+nJoOoXI
|
||||||
|
jzvorm5kz3\nldCo8Iwh+IZ3SSnj0/h4H1GR
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TestCsspNtlm(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
@summary: test generate ntlmv2 over cssp authentication protocol
|
||||||
|
"""
|
||||||
|
def testCSSPNTLMAuthentication(self):
|
||||||
|
negotiate_data_request = cssp.decode_der_trequest(peer0_0.decode('base64'))
|
||||||
|
challenge_data_request = cssp.decode_der_trequest(peer1_0.decode('base64'))
|
||||||
|
authenticate_data_request = cssp.decode_der_trequest(peer0_1.decode('base64'))
|
||||||
|
|
||||||
|
negotiate_data = cssp.getNegoTokens(negotiate_data_request)[0]
|
||||||
|
challenge_data = cssp.getNegoTokens(challenge_data_request)[0]
|
||||||
|
authenticate_data = cssp.getNegoTokens(authenticate_data_request)[0]
|
||||||
|
|
||||||
|
negotiate = ntlm.NegotiateMessage()
|
||||||
|
negotiate_data.read_type(negotiate)
|
||||||
|
|
||||||
|
challenge = ntlm.ChallengeMessage()
|
||||||
|
challenge_data.read_type(challenge)
|
||||||
|
|
||||||
|
ServerChallenge = challenge.ServerChallenge.value
|
||||||
|
ServerName = challenge.getTargetInfo()
|
||||||
|
|
||||||
|
authenticate = ntlm.AuthenticateMessage()
|
||||||
|
authenticate_data.read_type(authenticate)
|
||||||
|
|
||||||
|
NtChallengeResponseTemp = authenticate.getNtChallengeResponse()
|
||||||
|
NTProofStr = NtChallengeResponseTemp[:16]
|
||||||
|
temp = NtChallengeResponseTemp[16:]
|
||||||
|
Timestamp = temp[8:16]
|
||||||
|
ClientChallenge = temp[16:24]
|
||||||
|
|
||||||
|
EncryptedRandomSessionKey = authenticate.getEncryptedRandomSession()
|
||||||
|
domain = "coco"
|
||||||
|
user = "toto"
|
||||||
|
password = "lolo"
|
||||||
|
|
||||||
|
ResponseKeyNT = ntlm.NTOWFv2(password, user, domain)
|
||||||
|
ResponseKeyLM = ntlm.LMOWFv2(password, user, domain)
|
||||||
|
NtChallengeResponse, LmChallengeResponse, SessionBaseKey = ntlm.ComputeResponsev2(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ClientChallenge, Timestamp, ServerName)
|
||||||
|
KeyExchangeKey = ntlm.KXKEYv2(SessionBaseKey, LmChallengeResponse, ServerChallenge)
|
||||||
|
ExportedSessionKey = ntlm.RC4K(KeyExchangeKey, EncryptedRandomSessionKey)
|
||||||
|
|
||||||
|
domain, user = domain, user
|
||||||
|
if challenge.NegotiateFlags.value & ntlm.Negotiate.NTLMSSP_NEGOTIATE_UNICODE:
|
||||||
|
domain, user = ntlm.UNICODE(domain), ntlm.UNICODE(user)
|
||||||
|
|
||||||
|
ClientSigningKey = ntlm.SIGNKEY(ExportedSessionKey, True)
|
||||||
|
ServerSigningKey = ntlm.SIGNKEY(ExportedSessionKey, False)
|
||||||
|
ClientSealingKey = ntlm.SEALKEY(ExportedSessionKey, True)
|
||||||
|
ServerSealingKey = ntlm.SEALKEY(ExportedSessionKey, False)
|
||||||
|
|
||||||
|
interface = ntlm.NTLMv2SecurityInterface(rc4.RC4Key(ClientSealingKey), rc4.RC4Key(ServerSealingKey), ClientSigningKey, ServerSigningKey)
|
||||||
|
|
||||||
|
EncryptedPubKeySrc = cssp.getPubKeyAuth(authenticate_data_request)
|
||||||
|
EncryptedPubKeyDst = interface.GSS_WrapEx(pubKeyHex.decode('base64'))
|
||||||
|
|
||||||
|
self.assertTrue(EncryptedPubKeySrc == EncryptedPubKeyDst, "Public key must be equals")
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ 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
|
||||||
from rdpy.protocol.rdp import lic, sec
|
from rdpy.core import lic, sec
|
||||||
import rdpy.core.type as type
|
import rdpy.core.type as type
|
||||||
|
|
||||||
#dump of server request
|
#dump of server request
|
||||||
@@ -91,7 +91,7 @@ class TestLic(unittest.TestCase):
|
|||||||
def test_valid_client_licensing_error_message(self):
|
def test_valid_client_licensing_error_message(self):
|
||||||
l = lic.LicenseManager(None)
|
l = lic.LicenseManager(None)
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType(lic.createValidClientLicensingErrorMessage())
|
s.write_type(lic.createValidClientLicensingErrorMessage())
|
||||||
#reinit position
|
#reinit position
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
|
|
||||||
@@ -105,10 +105,21 @@ class TestLic(unittest.TestCase):
|
|||||||
if flag != sec.SecurityFlag.SEC_LICENSE_PKT:
|
if flag != sec.SecurityFlag.SEC_LICENSE_PKT:
|
||||||
return
|
return
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType(message)
|
s.write_type(message)
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
s.readType(lic.LicPacket(lic.ClientNewLicenseRequest()))
|
s.read_type(lic.LicPacket(lic.ClientNewLicenseRequest()))
|
||||||
self._state = True
|
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()
|
t = Transport()
|
||||||
l = lic.LicenseManager(t)
|
l = lic.LicenseManager(t)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ 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.core.t125.per as per
|
||||||
import rdpy.core.type as type
|
import rdpy.core.type as type
|
||||||
import rdpy.core.error as error
|
import rdpy.core.error as error
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ class PERTest(unittest.TestCase):
|
|||||||
@summary: test readLength function in per module
|
@summary: test readLength function in per module
|
||||||
"""
|
"""
|
||||||
s1 = type.Stream()
|
s1 = type.Stream()
|
||||||
s1.writeType(type.UInt8(0x1a))
|
s1.write_type(type.UInt8(0x1a))
|
||||||
s1.pos = 0
|
s1.pos = 0
|
||||||
|
|
||||||
l1 = per.readLength(s1)
|
l1 = per.readLength(s1)
|
||||||
@@ -48,7 +48,7 @@ class PERTest(unittest.TestCase):
|
|||||||
self.assertTrue(l1 == 0x1a, "readLength fail in small format")
|
self.assertTrue(l1 == 0x1a, "readLength fail in small format")
|
||||||
|
|
||||||
s2 = type.Stream()
|
s2 = type.Stream()
|
||||||
s2.writeType(type.UInt16Be(0x1abc | 0x8000))
|
s2.write_type(type.UInt16Be(0x1abc | 0x8000))
|
||||||
s2.pos = 0
|
s2.pos = 0
|
||||||
|
|
||||||
l2 = per.readLength(s2)
|
l2 = per.readLength(s2)
|
||||||
@@ -78,7 +78,7 @@ class PERTest(unittest.TestCase):
|
|||||||
for t in [type.UInt8, type.UInt16Be, type.UInt32Be]:
|
for t in [type.UInt8, type.UInt16Be, type.UInt32Be]:
|
||||||
v = t(3)
|
v = t(3)
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType((per.writeLength(type.sizeof(v)), v))
|
s.write_type((per.writeLength(type.sizeof(v)), v))
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
|
|
||||||
self.assertTrue(per.readInteger(s) == 3, "invalid readLength for type %s" % t)
|
self.assertTrue(per.readInteger(s) == 3, "invalid readLength for type %s" % t)
|
||||||
@@ -86,7 +86,7 @@ class PERTest(unittest.TestCase):
|
|||||||
#error case
|
#error case
|
||||||
for l in [0, 3, 5]:
|
for l in [0, 3, 5]:
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType(per.writeLength(l))
|
s.write_type(per.writeLength(l))
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
|
|
||||||
self.assertRaises(error.InvalidValue, per.readInteger, s)
|
self.assertRaises(error.InvalidValue, per.readInteger, s)
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ 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.tpkt as tpkt
|
import rdpy.core.tpkt as tpkt
|
||||||
import rdpy.core.type as type
|
import rdpy.core.type as type
|
||||||
import rdpy.core.error as error
|
|
||||||
|
|
||||||
class TPKTTest(unittest.TestCase):
|
class TPKTTest(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
@@ -60,13 +60,13 @@ class TPKTTest(unittest.TestCase):
|
|||||||
def connect(self):
|
def connect(self):
|
||||||
pass
|
pass
|
||||||
def recv(self, data):
|
def recv(self, data):
|
||||||
data.readType(type.String("test_tpkt_layer_recv", constant = True))
|
data.read_type(type.String("test_tpkt_layer_recv", constant = True))
|
||||||
raise TPKTTest.TPKT_PASS()
|
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.write_type((type.UInt8(tpkt.Action.FASTPATH_ACTION_X224), type.UInt8(), type.UInt16Be(type.sizeof(message) + 4), message))
|
||||||
|
|
||||||
layer = tpkt.TPKT(Presentation())
|
layer = tpkt.TPKT(Presentation())
|
||||||
layer.connect()
|
layer.connect()
|
||||||
@@ -80,13 +80,13 @@ class TPKTTest(unittest.TestCase):
|
|||||||
def setFastPathSender(self, fastPathSender):
|
def setFastPathSender(self, fastPathSender):
|
||||||
pass
|
pass
|
||||||
def recvFastPath(self, secFlag, fastPathS):
|
def recvFastPath(self, secFlag, fastPathS):
|
||||||
fastPathS.readType(type.String("test_tpkt_layer_recv_fastpath", constant = True))
|
fastPathS.read_type(type.String("test_tpkt_layer_recv_fastpath", constant = True))
|
||||||
raise TPKTTest.TPKT_PASS()
|
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.write_type((type.UInt8(tpkt.Action.FASTPATH_ACTION_FASTPATH), type.UInt8(type.sizeof(message) + 2), message))
|
||||||
|
|
||||||
layer = tpkt.TPKT(None)
|
layer = tpkt.TPKT(None)
|
||||||
layer.initFastPath(FastPathLayer())
|
layer.initFastPath(FastPathLayer())
|
||||||
@@ -101,13 +101,13 @@ class TPKTTest(unittest.TestCase):
|
|||||||
def setFastPathSender(self, fastPathSender):
|
def setFastPathSender(self, fastPathSender):
|
||||||
pass
|
pass
|
||||||
def recvFastPath(self, secflag, fastPathS):
|
def recvFastPath(self, secflag, fastPathS):
|
||||||
fastPathS.readType(type.String("test_tpkt_layer_recv_fastpath_ext_length", constant = True))
|
fastPathS.read_type(type.String("test_tpkt_layer_recv_fastpath_ext_length", constant = True))
|
||||||
raise TPKTTest.TPKT_PASS()
|
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.write_type((type.UInt8(tpkt.Action.FASTPATH_ACTION_FASTPATH), type.UInt16Be((type.sizeof(message) + 3) | 0x8000), message))
|
||||||
|
|
||||||
layer = tpkt.TPKT(None)
|
layer = tpkt.TPKT(None)
|
||||||
layer.initFastPath(FastPathLayer())
|
layer.initFastPath(FastPathLayer())
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ 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.x224 as x224
|
import rdpy.core.x224 as x224
|
||||||
import rdpy.core.type as type
|
import rdpy.core.type as type
|
||||||
import rdpy.core.error as error
|
import rdpy.core.error as error
|
||||||
|
|
||||||
@@ -53,12 +53,12 @@ class X224Test(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.read_type(type.String('test_x224_layer_recvData', constant = True))
|
||||||
raise X224Test.X224_PASS()
|
raise X224Test.X224_PASS()
|
||||||
|
|
||||||
layer = x224.X224Layer(Presentation())
|
layer = x224.X224Layer(Presentation())
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType((x224.X224DataHeader(), type.String('test_x224_layer_recvData')))
|
s.write_type((x224.X224DataHeader(), type.String('test_x224_layer_recvData')))
|
||||||
#reinit position
|
#reinit position
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
|
|
||||||
@@ -71,10 +71,10 @@ class X224Test(unittest.TestCase):
|
|||||||
class Transport(object):
|
class Transport(object):
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType(data)
|
s.write_type(data)
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
s.readType(x224.X224DataHeader())
|
s.read_type(x224.X224DataHeader())
|
||||||
s.readType(type.String('test_x224_layer_send', constant = True))
|
s.read_type(type.String('test_x224_layer_send', constant = True))
|
||||||
raise X224Test.X224_PASS()
|
raise X224Test.X224_PASS()
|
||||||
|
|
||||||
layer = x224.X224Layer(None)
|
layer = x224.X224Layer(None)
|
||||||
@@ -89,10 +89,10 @@ class X224Test(unittest.TestCase):
|
|||||||
class Transport(object):
|
class Transport(object):
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType(data)
|
s.write_type(data)
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
t = x224.ClientConnectionRequestPDU()
|
t = x224.ConnectionRequestPDU()
|
||||||
s.readType(t)
|
s.read_type(t)
|
||||||
|
|
||||||
if t.protocolNeg.code != x224.NegociationType.TYPE_RDP_NEG_REQ:
|
if t.protocolNeg.code != x224.NegociationType.TYPE_RDP_NEG_REQ:
|
||||||
raise X224Test.X224_FAIL()
|
raise X224Test.X224_FAIL()
|
||||||
@@ -107,28 +107,15 @@ class X224Test(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertRaises(X224Test.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_bad_protocol(self):
|
|
||||||
"""
|
|
||||||
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
|
|
||||||
Server ask another protocol than SSL or RDP
|
|
||||||
"""
|
|
||||||
message = x224.ServerConnectionConfirm()
|
|
||||||
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_HYBRID
|
|
||||||
s = type.Stream()
|
|
||||||
s.writeType(message)
|
|
||||||
s.pos = 0
|
|
||||||
layer = x224.Client(None)
|
|
||||||
self.assertRaises(error.InvalidExpectedDataException, layer.recvConnectionConfirm, s)
|
|
||||||
|
|
||||||
def test_x224_client_recvConnectionConfirm_negotiation_failure(self):
|
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.ConnectionConfirmPDU()
|
||||||
message.protocolNeg.code.value = x224.NegociationType.TYPE_RDP_NEG_FAILURE
|
message.protocolNeg.code.value = x224.NegociationType.TYPE_RDP_NEG_FAILURE
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType(message)
|
s.write_type(message)
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
layer = x224.Client(None)
|
layer = x224.Client(None)
|
||||||
self.assertRaises(error.RDPSecurityNegoFail, layer.recvConnectionConfirm, s)
|
self.assertRaises(error.RDPSecurityNegoFail, layer.recvConnectionConfirm, s)
|
||||||
@@ -141,12 +128,10 @@ class X224Test(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):
|
||||||
@@ -156,11 +141,11 @@ class X224Test(unittest.TestCase):
|
|||||||
def recvData(data):
|
def recvData(data):
|
||||||
raise X224Test.X224_PASS()
|
raise X224Test.X224_PASS()
|
||||||
|
|
||||||
message = x224.ServerConnectionConfirm()
|
message = x224.ConnectionConfirmPDU()
|
||||||
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_SSL
|
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_SSL
|
||||||
|
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType(message)
|
s.write_type(message)
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
layer = x224.Client(Presentation())
|
layer = x224.Client(Presentation())
|
||||||
layer._transport = Transport()
|
layer._transport = Transport()
|
||||||
@@ -180,17 +165,17 @@ class X224Test(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.ConnectionConfirmPDU):
|
||||||
raise X224Test.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 X224Test.X224_FAIL()
|
raise X224Test.X224_FAIL()
|
||||||
def close(self):
|
def close(self):
|
||||||
raise X224Test.X224_PASS()
|
raise X224Test.X224_PASS()
|
||||||
|
|
||||||
message = x224.ClientConnectionRequestPDU()
|
message = x224.ConnectionRequestPDU()
|
||||||
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_HYBRID
|
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_HYBRID
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType(message)
|
s.write_type(message)
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
|
|
||||||
layer = x224.Server(None, "key", "cert", True)
|
layer = x224.Server(None, "key", "cert", True)
|
||||||
@@ -214,15 +199,12 @@ class X224Test(unittest.TestCase):
|
|||||||
x224.ServerTLSContext = ServerTLSContext
|
x224.ServerTLSContext = ServerTLSContext
|
||||||
|
|
||||||
class Transport(object):
|
class Transport(object):
|
||||||
def __init__(self):
|
|
||||||
class TLS(object):
|
|
||||||
def startTLS(self, context):
|
def startTLS(self, context):
|
||||||
global tls
|
global tls
|
||||||
tls = True
|
tls = True
|
||||||
self.transport = TLS()
|
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
if not isinstance(data, x224.ServerConnectionConfirm):
|
if not isinstance(data, x224.ConnectionConfirmPDU):
|
||||||
raise X224Test.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 X224Test.X224_FAIL()
|
raise X224Test.X224_FAIL()
|
||||||
@@ -232,10 +214,10 @@ class X224Test(unittest.TestCase):
|
|||||||
global connect_event
|
global connect_event
|
||||||
connect_event = True
|
connect_event = True
|
||||||
|
|
||||||
message = x224.ClientConnectionRequestPDU()
|
message = x224.ConnectionRequestPDU()
|
||||||
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_SSL | x224.Protocols.PROTOCOL_RDP
|
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_SSL | x224.Protocols.PROTOCOL_RDP
|
||||||
s = type.Stream()
|
s = type.Stream()
|
||||||
s.writeType(message)
|
s.write_type(message)
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
|
|
||||||
layer = x224.Server(Presentation(), "key", "cert")
|
layer = x224.Server(Presentation(), "key", "cert")
|
||||||
|
|||||||
Reference in New Issue
Block a user