134 Commits

Author SHA1 Message Date
citronneur
1c4b42544c Update: Finish mcs connect 2020-05-27 22:27:24 +02:00
citronneur
9550734743 Fix: bad string length 2020-04-21 22:13:20 +02:00
citronneur
73b97d6929 Update: Start MCS layer 2020-04-21 22:02:35 +02:00
citronneur
018c59fe42 Update: refactor cssp code 2020-04-20 22:51:32 +02:00
citronneur
9cac72a8d2 Update: TPKT + x224 asyncio complient 2020-04-17 15:52:37 +02:00
unknown
3f56b25f46 Start python3 2020-04-14 21:56:46 +02:00
Sylvain Peyrefitte
cef16a9f64 Merge pull request #108 from cudeso/master
Add logging for rdphoneypot
2020-04-10 21:12:15 +02:00
Koen Van Impe
9aea135fd9 Add logging 2020-04-10 21:05:18 +02:00
Sylvain Peyrefitte
4109b7a6fe Merge pull request #67 from Mutchako/patch-1
Update setup.py
2018-08-23 22:20:14 +02:00
Sylvain Peyrefitte
d3b0ae5e90 Merge pull request #80 from speidy/pointerex
onPointerEvent: handle 4, 5 mouse buttons based on INPUT_EVENT_MOUSEX event
2018-08-23 22:18:20 +02:00
Idan Freiberg
a1f9afa87a onPointerEvent: handle 4, 5 mouse buttons based on INPUT_EVENT_MOUSEX event 2018-08-06 07:52:32 +03:00
Sylvain Peyrefitte
b8ff4136b6 Merge pull request #78 from speidy/mitm
rdpy-rdpmitm: pep8 fixes, argument parsing improvements
2018-08-05 16:23:28 +02:00
Sylvain Peyrefitte
ce04150790 Merge pull request #77 from speidy/pointerex
data: add support for INPUT_EVENT_MOUSEX event
2018-08-05 16:23:03 +02:00
speidy
bcec1aad25 rdpy-rdpmitm: use argparse for argument parsing 2018-08-05 17:07:29 +03:00
speidy
c18a4c4101 rdpy-rdpmitm: apply pep8 fixes 2018-08-05 15:58:07 +03:00
speidy
3e37899ae4 data: add support for INPUT_EVENT_MOUSEX event 2018-08-05 14:28:23 +03:00
Mutchako
cd6e14e7ef Update setup.py
Syntax error on install_requires. Unnecessary comma on last statement.
2018-02-12 09:48:17 -05:00
Sylvain Peyrefitte
629d2160af Merge pull request #33 from ChrisTruncer/bin_
Small change to reference object attribute
2015-06-01 11:40:37 +02:00
Christopher Truncer
e23def3179 Small changeto reference object attribute 2015-05-28 12:08:55 -04:00
speyrefitte
a23ae25a1f fix issue on unhandle upadte 2015-05-21 10:29:32 +02:00
speyrefitte
763ed2e3ee fix major bug on update handle 2015-05-20 17:51:43 +02:00
speyrefitte
11d66a4818 add onSessionReady event -> user session is ready 2015-05-19 17:53:15 +02:00
speyrefitte
9b99365f80 fix bug on lwin key activation 2015-05-19 16:42:22 +02:00
speyrefitte
bd7c708bf3 update version 2015-05-04 11:49:37 +02:00
speyrefitte
1deb2d69ea bug fixing 2015-05-04 11:47:25 +02:00
speyrefitte
0a5a1fd12c add keylogger on rss player and file format 2015-04-29 12:13:24 +02:00
speyrefitte
c97b451ce3 Merge branch 'hotfix' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:32:50 +02:00
speyrefitte
80f989a804 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:32:44 +02:00
speyrefitte
c6e100f9a6 Merge branch 'master' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:31:05 +02:00
Sylvain Peyrefitte
15df00ec20 Merge pull request #24 from citronneur/master
Update readme with pypi version
2015-03-25 14:48:18 +01:00
Sylvain Peyrefitte
342349cf41 Merge pull request #23 from citronneur/master
Just add pypi package
2015-03-25 14:47:14 +01:00
Sylvain Peyrefitte
d6043106e3 Update README.md 2015-03-25 12:15:57 +01:00
speyrefitte
bd7e73a6e7 change log format 2015-03-20 17:54:48 +01:00
Sylvain Peyrefitte
fc1685e652 Merge pull request #21 from ojosdegris/patch-1
Added OS X install example
2015-03-18 10:47:01 +01:00
vittore
4320824aae Fixed example to follow existing style. 2015-03-13 12:11:41 -04:00
vittore
5a438174b9 Added OS X install example 2015-03-13 12:07:22 -04:00
speyrefitte
bb9483e7e1 update setup.py 2015-03-13 10:38:26 +01:00
speyrefitte
bd362263f7 Correct bug from cssp security layer 2015-03-13 10:17:13 +01:00
citronneur
20de5f6f82 Add tests for cssp ntlm authentication protocol 2015-03-09 22:31:08 +01:00
citronneur
95052a323f remove pycrypto dependancies, bug fixing 2015-03-08 21:04:26 +01:00
speyrefitte
0abf18d130 NLA Security Layer is AVAILABLE 2015-03-06 18:19:42 +01:00
Sylvain Peyrefitte
b57b3d7398 Update ntlm.py 2015-03-05 22:53:13 +01:00
citronneur
0695825d98 cssp protocol proxy 2015-03-05 22:28:33 +01:00
speyrefitte
8fb4893b6f almost finish ntlmv2... 2015-03-05 18:26:36 +01:00
citronneur
30a16fbb7a add some usefull methods 2015-03-03 21:57:40 +01:00
speyrefitte
98494d0e73 try to understand key in ntlm 2015-03-03 18:27:45 +01:00
speyrefitte
a7058f1c54 not handle correctly the ntlmv2 auth message 2015-03-02 18:37:18 +01:00
citronneur
1e2f284e97 Parse chanllenge response from server 2015-02-24 22:35:16 +01:00
citronneur
8cd789480f Fix immediatly automata 2015-02-22 14:04:09 +01:00
citronneur
e9a93d117b automata modification for NLA 2015-02-21 14:29:17 +01:00
citronneur
3fe16130d8 NTLM pbs ... 2015-02-21 10:07:19 +01:00
citronneur
31b0920a87 Some changes + NTLM challenge message 2015-02-20 22:13:50 +01:00
speyrefitte
36c05faa11 NTLM Negotiate message embended in cssp request 2015-02-20 10:13:24 +01:00
citronneur
1c3119cffd ASN.1 Tag correcr 2015-02-19 23:05:21 +01:00
speyrefitte
d6bb21565d start spneg 2015-02-19 18:26:50 +01:00
citronneur
d6428430eb Start NTLM support 2015-02-18 22:01:58 +01:00
citronneur
a4f4d71929 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-02-18 21:10:18 +01:00
citronneur
9e211c0199 Merge branch 'master' of https://github.com/citronneur/rdpy into dev 2015-02-18 21:10:11 +01:00
Sylvain Peyrefitte
5bd78cc012 Merge pull request #18 from manuteleco/master
Fix typos and tabs in README
2015-02-17 23:10:36 +01:00
Manuel Rodríguez Guimeráns
e139a2c7eb Replace tabs with spaces in code snippets. 2015-02-17 16:14:06 +01:00
Manuel Rodríguez Guimeráns
349a8a7227 Fix typos in documentation. 2015-02-17 16:12:58 +01:00
speyrefitte
35514a2849 Fix build issue 2015-02-17 13:45:22 +01:00
citronneur
222ee76c91 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-02-16 22:29:42 +01:00
citronneur
30c3611bb9 add credssp grammar 2015-02-16 22:29:12 +01:00
citronneur
6c93ca17b0 release 1.2.2 2015-02-16 11:35:01 +01:00
speyrefitte
a01eb57cef Add support for salted mac generation, fix issue 17 2015-02-10 11:39:26 +01:00
speyrefitte
9e50c2292d fix certuficate signing 2015-02-09 15:43:19 +01:00
speyrefitte
82d7798255 fix issue 16 2015-02-06 18:07:02 +01:00
speyrefitte
e9db7d720f fix issue 16 2015-02-06 15:23:44 +01:00
speyrefitte
1d5b15a310 fix minor bug to work with rdesktop but finally there is a bug in rdesktop 2015-02-06 14:35:41 +01:00
speyrefitte
a8ddaa77ff fix issue 14 on xp sp3 + server side effect for honeypot 2015-02-05 16:06:06 +01:00
citronneur
4c56f55266 fix issue 14 2015-02-04 21:47:32 +01:00
citronneur
1039e014c1 fix issue 14 2015-02-04 21:04:58 +01:00
speyrefitte
84ac320e82 add multiple file for honeypot 2015-01-29 17:56:21 +01:00
speyrefitte
02dfe8f46e fix issue 13 2015-01-23 17:13:14 +01:00
speyrefitte
8b159e668f merge jaredhaight branch 2015-01-19 11:23:46 +01:00
Sylvain Peyrefitte
b22a7d5dce Merge pull request #11 from r04r/patch-1
Fix typos in README.md
2015-01-19 11:06:39 +01:00
Jared Haight
1b1dfa06c8 Fixed some typos 2015-01-16 22:40:31 -05:00
r04r
8e7e6cdcb4 Fix typos in README.md 2015-01-16 18:08:19 +01:00
speyrefitte
ff65c4b701 fix delivrary bugs 2015-01-15 11:05:41 +01:00
speyrefitte
c5b8588e5f update README 2015-01-15 10:50:21 +01:00
speyrefitte
9dcac862ff finish honeypot tools 2015-01-14 15:49:00 +01:00
speyrefitte
e9fd801237 add honey pot binary 2015-01-13 16:21:13 +01:00
speyrefitte
0686b2b677 update license + rsr reader and writer 2015-01-09 18:07:06 +01:00
speyrefitte
9a4f5f254c add first step of rsr support 2015-01-07 18:32:16 +01:00
Sylvain Peyrefitte
522eda39e8 Update README.md 2015-01-06 17:22:32 +01:00
Sylvain Peyrefitte
1b4fd4a686 Update README.md 2015-01-06 17:21:06 +01:00
speyrefitte
33dbbf45d4 fix build error 2015-01-06 16:18:11 +01:00
speyrefitte
a3839e47c4 add new binaries form 2015-01-06 16:00:44 +01:00
speyrefitte
b378a1d15c ready for 1.2.0 2015-01-06 10:24:06 +01:00
speyrefitte
7ff5c5caa3 fix rdp stander security layer server side bug, fix lisense automata, ready for next release 2015-01-05 18:34:50 +01:00
speyrefitte
f4808d0ae2 finish RDP basic security layer for server side but have bug on faspath + basic security layer... 2014-12-24 17:20:09 +01:00
speyrefitte
28f50b1f0e finish RDP basic security layer on server side except signature and fastpath 2014-12-23 18:24:41 +01:00
speyrefitte
f9b30a668a bug on signing key 2014-12-22 18:36:26 +01:00
speyrefitte
d330564d2b bug fix 2014-12-17 18:19:36 +01:00
citronneur
69b3f6befe server side of basic RDP security layer 2014-12-16 22:11:48 +01:00
speyrefitte
a0ae3d97ec fix build issue 2014-12-16 18:03:12 +01:00
speyrefitte
f2481149d9 finish RDP basic security layer client side, start server side 2014-12-15 18:26:27 +01:00
speyrefitte
f9f92b8215 add support for x509 certificate 2014-12-12 18:14:12 +01:00
citronneur
f36f8222df fix update keys for 128 56 and 40 bits keys, Client RDP basic security layer finish 2014-12-11 22:42:59 +01:00
speyrefitte
d4d98471eb support 40bits and 56bits key on client side, bug on update keys 2014-12-11 18:34:51 +01:00
speyrefitte
f3a3ad8ac3 finish 128 bit basic rdp security layer 2014-12-10 18:32:10 +01:00
citronneur
47a9f75fa6 do not decrypt lic packet 2014-12-09 22:23:27 +01:00
speyrefitte
92c642ffed first packet in RDP basic security layer 2014-12-09 18:01:47 +01:00
citronneur
551af0aa7a move info packet to security layer 2014-12-08 23:05:58 +01:00
speyrefitte
ccf0156150 add basic RDP secure layer... 2014-12-08 18:15:28 +01:00
citronneur
e7c6e61a25 RDP Basic security client message 2014-12-07 19:24:09 +01:00
citronneur
de1347840b Build repair 2014-12-06 14:32:35 +01:00
speyrefitte
873d1fac41 Begin og RDP basic security layer 2014-12-05 18:11:19 +01:00
speyrefitte
fc3efa60ee service_identity in travis 2014-12-03 10:18:51 +01:00
speyrefitte
e4c04a7dd1 rrrr tag to quickly 2014-12-03 09:56:41 +01:00
speyrefitte
cdcfdec379 bad setup.py 2014-12-03 09:51:43 +01:00
speyrefitte
894dc44a52 add service_identity in depends 2014-12-03 09:43:16 +01:00
citronneur
47dba5bb6e bug fix on auto detect keybord for windows system 2014-12-03 09:38:14 +01:00
citronneur
c4d071a2e0 Bug fix in screenshot script 2014-12-02 21:32:52 +01:00
speyrefitte
44d99eaca4 Eyewitness integration 2014-12-02 17:00:42 +01:00
speyrefitte
9419bea9b0 fix issue 7 2014-12-02 16:07:33 +01:00
speyrefitte
bd91093617 add 64 bit pre build 2014-12-02 10:12:49 +01:00
speyrefitte
15fadfe3dd Merge branch 'dev' 2014-12-01 18:13:20 +01:00
speyrefitte
fa25a40721 add hostname support + finish license automata 2014-12-01 18:06:08 +01:00
citronneur
99198321a4 add keyboard layout detection or force 2014-11-30 11:22:59 +01:00
citronneur
3c3d7423a5 add fullscreen mode for client 2014-11-29 19:00:14 +01:00
speyrefitte
5af9f0708a bug fix on gui + add license neg (almost) 2014-11-28 17:54:49 +01:00
citronneur
4e7ea06906 code refactoring + gitignore updtae 2014-11-20 22:14:16 +01:00
citronneur
7fc25e35f1 Merge branch 'master' of https://github.com/citronneur/rdpy into dev 2014-11-20 20:29:57 +01:00
speyrefitte
078aaa13f6 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2014-11-17 18:38:22 +01:00
speyrefitte
7e98bc373a license automata update 2014-11-17 18:37:48 +01:00
speyrefitte
0d9b766a03 Merge branch 'dev' of https://github.com/citronneur/rdpy 2014-11-17 11:09:06 +01:00
citronneur
c755148a3c strange bug on proxy 2014-11-16 09:36:20 +01:00
speyrefitte
84a3d7619b Merge branch 'dev' 2014-11-13 14:27:11 +01:00
speyrefitte
3e1b8e57eb add win32 support in setup.py 2014-11-13 14:25:12 +01:00
citronneur
744ff023ed README mistakes 2014-11-11 14:56:07 +01:00
citronneur
bc89e67974 delete pypi 2014-11-11 14:54:53 +01:00
citronneur
55ba109166 first dev commit 2014-11-11 14:52:17 +01:00
67 changed files with 7396 additions and 4918 deletions

7
.gitignore vendored
View File

@@ -2,7 +2,10 @@
.project
.pydevproject
README.md~
rdpy/core/tmp/*
*.so
*.os
.sconsign.dblite
dist/*
build/*
rdpy.egg-info/*
*.pyd
.idea

View File

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

276
README.md
View File

@@ -1,25 +1,56 @@
# RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=master)](https://travis-ci.org/citronneur/rdpy)
# RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=dev)](https://travis-ci.org/citronneur/rdpy) [![PyPI version](https://badge.fury.io/py/rdpy.png)](http://badge.fury.io/py/rdpy)
Remote Desktop Protocol in twisted PYthon.
Remote Desktop Protocol in twisted python.
RDPY is still under development.
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (client and server side). RDPY is built over the event driven network engine Twisted. RDPY support standard RDP security layer, RDP over SSL and NLA authentication (through ntlmv2 authentication protocol).
RDPY is a pure Python implementation ot the Microsoft RDP (Remote Desktop Protocol) protocol. RDPY is built over the event driven network engine Twisted.
RDPY provides the following RDP and VNC binaries :
* RDP Man In The Middle proxy which record session
* RDP Honeypot
* RDP screenshoter
* RDP client
* VNC client
* VNC screenshoter
* RSS Player
## Build
RDPY is fully implemented in python, except the bitmap uncompression algorithm which is implemented in C for performance purposes.
RDPY is fully implemented in python, except the bitmap decompression algorithm which is implemented in C for performance purposes.
### Depends
### Dependencies
* python2.7
* python-qt4
Dependencies are only needed for pyqt4 binaries :
* rdpy-rdpclient
* rdpy-rdpscreenshot
* rdpy-vncclient
* rdpy-vncscreenshot
* rdpy-rssplayer
### Make
#### Linux
Example for Debian based systems :
```
sudo apt-get install python-qt4
```
#### OS X
Example for OS X to install PyQt with homebrew
```
$ brew install qt sip pyqt
```
#### Windows
x86 | x86_64
----|-------
[PyQt4](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x32.exe) | [PyQt4](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x64.exe/download)
[PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download) | [PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download)
### Build
```
$ git clone https://github.com/citronneur/rdpy.git rdpy
$ pip install twisted pyopenssl qt4reactor
$ pip install twisted pyopenssl qt4reactor service_identity rsa pyasn1
$ python rdpy/setup.py install
```
@@ -28,7 +59,7 @@ Or use PIP:
$ pip install rdpy
```
For virtualenv, tou need to link qt4 library to it:
For virtualenv, you will need to link the qt4 library to it:
```
$ ln -s /usr/lib/python2.7/dist-packages/PyQt4/ $VIRTUAL_ENV/lib/python2.7/site-packages/
$ ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-packages/
@@ -36,145 +67,244 @@ $ ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-
## RDPY Binaries
RDPY comes with some very useful binaries; These binaries are linux and windows compatible.
RDPY comes with some very useful binaries. These binaries are linux and windows compatible.
### rdpy-rdpclient
rdpy-rdpclient is a simple RDP Qt4 client .
rdpy-rdpclient is a simple RDP Qt4 client.
```
$ rdpy/bin/rdpy-rdpclient [-u username] [-p password] [-d domain] [...] XXX.XXX.XXX.XXX[:3389]
$ rdpy-rdpclient.py [-u username] [-p password] [-d domain] [-r rss_ouput_file] [...] XXX.XXX.XXX.XXX[:3389]
```
You can use rdpy-rdpclient in a Recorder Session Scenario, used in rdpy-rdphoneypot.
### rdpy-vncclient
rdpy-vncclient is a simple VNC Qt4 client .
```
$ rdpy/bin/rdpy-vncclient [-p password] XXX.XXX.XXX.XXX[:5900]
$ rdpy-vncclient.py [-p password] XXX.XXX.XXX.XXX[:5900]
```
### rdpy-rdpscreenshot
rdpy-rdpscreenshot save login screen in file.
rdpy-rdpscreenshot saves login screen in file.
```
$ rdpy/bin/rdpy-rdpscreenshot [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX.XXX[:3389]
$ rdpy-rdpscreenshot.py [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX.XXX[:3389]
```
### rdpy-vncscreenshot
rdpy-vncscreenshot save first screen update in file.
rdpy-vncscreenshot saves the first screen update in file.
```
$ rdpy/bin/rdpy-vncscreenshot [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900]
$ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900]
```
### rdpy-rdpproxy
### rdpy-rdpmitm
rdpy-rdpproxy is a RDP proxy. It is used to manage and control access to the RDP servers as well as watch live sessions through any RDP client. It can be compared to a HTTP reverse proxy with added spy features.
rdpy-rdpmitm is a RDP proxy allows you to do a Man In The Middle attack on RDP protocol.
Record Session Scenario into rss file which can be replayed by rdpy-rssplayer.
```
$ rdpy/bin/rdpy-rdpproxy -f credentials_file_path -k private_key_file_path -c certificate_file_path [-i admin_ip[:admin_port]] listen_port
$ rdpy-rdpmitm.py -o output_dir [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] [-r (for XP or server 2003 client)] target_host[:target_port]
```
The credentials file is JSON file that must conform with the following format:
Output directory is used to save the rss file with following format (YYYYMMDDHHMMSS_ip_index.rss)
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer If one of both parameters are omitted, the server use standard RDP as security layer.
### rdpy-rdphoneypot
rdpy-rdphoneypot is an RDP honey Pot. Use Recorded Session Scenario to replay scenario through RDP Protocol.
```
{
"domain1":
{
"username1":
[
{"ip":"machine1", "port":3389"},
{"ip":"machine2", "port":3389"}
],
"username2":
[
{"ip":"machine1", "port":3389"}
]
}
}
$ rdpy-rdphoneypot.py [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] rss_file_path_1 ... rss_file_path_N
```
In this exemple domain1\username1 can access to machine1 and machine2 and domain1\username2 can only access to machine1.
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer. If one of both parameters are omitted, the server use standard RDP as security layer.
You can specify more than one files to match more common screen size.
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer but RDPY is limited to SSL. The CredSSP security layer is planned for an upcoming release. The basic RDP security layer is not supported (windows wp sp1&2).
### rdpy-rssplayer
The IP and port admin are used in order to spy active sessions thanks to a RDP client (rdpy-rdpclient, remina, mstsc). Common values are 127.0.0.1:3389 to protect from connections by unauthorized user.
rdpy-rssplayer is use to replay Record Session Scenario (rss) files generates by either rdpy-rdpmitm or rdpy-rdpclient binaries.
```
$ rdpy-rssplayer.py rss_file_path
```
## RDPY Qt Widget
RDPY can also be used as Qt widget throw rdpy.ui.qt4.QRemoteDesktop class. It can be embedded in your own Qt application. qt4reactor must be used in your app for Twisted and Qt to work together. For more details, see sources of rdpy-rdpclient.
RDPY can also be used as Qt widget through rdpy.ui.qt4.QRemoteDesktop class. It can be embedded in your own Qt application. qt4reactor must be used in your app for Twisted and Qt to work together. For more details, see sources of rdpy-rdpclient.
## RDPY library
In a nutshell the RDPY can be used as a protocol library with a twisted engine.
In a nutshell RDPY can be used as a protocol library with a twisted engine.
The RDP client code looks like this:
### Simple RDP Client
```
from rdpy.protocol.rdp import rdp
```python
from rdpy.core import rdp
class MyRDPFactory(rdp.ClientFactory):
def clientConnectionLost(self, connector, reason):
def clientConnectionLost(self, connector, reason):
reactor.stop()
def clientConnectionFailed(self, connector, reason):
reactor.stop()
def buildObserver(self, controller, addr):
class MyObserver(rdp.RDPClientObserver)
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
#here code handle bitmap
pass
class MyObserver(rdp.RDPClientObserver):
def onReady(self):
#send 'r' key
self._controller.sendKeyEventUnicode(ord(unicode("r".toUtf8(), encoding="UTF-8")), True)
#mouse move and click at pixel 200x200
self._controller.sendPointerEvent(200, 200, 1, true)
def onReady(self):
"""
@summary: Call when stack is ready
"""
#send 'r' key
self._controller.sendKeyEventUnicode(ord(unicode("r".toUtf8(), encoding="UTF-8")), True)
#mouse move and click at pixel 200x200
self._controller.sendPointerEvent(200, 200, 1, true)
def onClose(self):
pass
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@summary: Notify bitmap update
@param destLeft: xmin position
@param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding
@param destBottom: ymax position because RDP can send bitmap with padding
@param width: width of bitmap
@param height: height of bitmap
@param bitsPerPixel: number of bit per pixel
@param isCompress: use RLE compression
@param data: bitmap data
"""
return MyObserver(controller)
def onSessionReady(self):
"""
@summary: Windows session is ready
"""
def onClose(self):
"""
@summary: Call when stack is close
"""
return MyObserver(controller)
from twisted.internet import reactor
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory())
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389, MyRDPFactory())
reactor.run()
```
The VNC client code looks like this:
### Simple RDP Server
```python
from rdpy.core import rdp
class MyRDPFactory(rdp.ServerFactory):
def buildObserver(self, controller, addr):
class MyObserver(rdp.RDPServerObserver):
def onReady(self):
"""
@summary: Call when server is ready
to send and receive messages
"""
def onKeyEventScancode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in scan code format
@param code: scan code of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventScancode
"""
def onKeyEventUnicode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in unicode format
@param code: unicode of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventUnicode
"""
def onPointerEvent(self, x, y, button, isPressed):
"""
@summary: Event call on mouse event
@param x: x position
@param y: y position
@param button: 1, 2, 3, 4 or 5 button
@param isPressed: True if mouse button is pressed
@see: rdp.RDPServerObserver.onPointerEvent
"""
def onClose(self):
"""
@summary: Call when human client close connection
@see: rdp.RDPServerObserver.onClose
"""
return MyObserver(controller)
from twisted.internet import reactor
reactor.listenTCP(3389, MyRDPFactory())
reactor.run()
```
from rdpy.protocol.rfb import rdp
class MyRDPFactory(rfb.ClientFactory):
### Simple VNC Client
```python
from rdpy.protocol.rfb import rfb
def clientConnectionLost(self, connector, reason):
class MyRFBFactory(rfb.ClientFactory):
def clientConnectionLost(self, connector, reason):
reactor.stop()
def clientConnectionFailed(self, connector, reason):
reactor.stop()
def buildObserver(self, controller, addr):
class MyObserver(rfb.RFBClientObserver)
class MyObserver(rfb.RFBClientObserver):
def onReady(self):
pass
def onReady(self):
"""
@summary: Event when network stack is ready to receive or send event
"""
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
#here code handle bitmap
pass
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
"""
@summary: Implement RFBClientObserver interface
@param width: width of new image
@param height: height of new image
@param x: x position of new image
@param y: y position of new image
@param pixelFormat: pixefFormat structure in rfb.message.PixelFormat
@param encoding: encoding type rfb.message.Encoding
@param data: image data in accordance with pixel format and encoding
"""
def onClose(self):
pass
def onCutText(self, text):
"""
@summary: event when server send cut text event
@param text: text received
"""
return MyObserver(controller)
def onBell(self):
"""
@summary: event when server send biiip
"""
def onClose(self):
"""
@summary: Call when stack is close
"""
return MyObserver(controller)
from twisted.internet import reactor
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory())
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389, MyRFBFactory())
reactor.run()
```

View File

@@ -1,148 +0,0 @@
#!/usr/bin/python
#
# Copyright (c) 2014 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
example of use rdpy as rdp client
"""
import sys, os, getopt
from PyQt4 import QtGui
from rdpy.ui.qt4 import RDPClientQt
from rdpy.protocol.rdp import rdp
import rdpy.base.log as log
log._LOG_LEVEL = log.Level.INFO
class RDPClientQtFactory(rdp.ClientFactory):
"""
@summary: Factory create a RDP GUI client
"""
def __init__(self, width, height, username, password, domain):
"""
@param width: width of client
@param heigth: heigth of client
@param username: username present to the server
@param password: password present to the server
@param domain: microsoft domain
"""
self._width = width
self._height = height
self._username = username
self._passwod = password
self._domain = domain
self._w = None
def buildObserver(self, controller, addr):
"""
@summary: Build RFB observer
We use a RDPClientQt as RDP observer
@param controller: build factory and needed by observer
@param addr: destination address
@return: RDPClientQt
"""
#create client observer
client = RDPClientQt(controller, self._width, self._height)
#create qt widget
self._w = client.getWidget()
self._w.setWindowTitle('rdpy-rdpclient')
self._w.show()
controller.setUsername(self._username)
controller.setPassword(self._passwod)
controller.setDomain(self._domain)
controller.setPerformanceSession()
return client
def startedConnecting(self, connector):
pass
def clientConnectionLost(self, connector, reason):
"""
@summary: Connection lost event
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
@param reason: str use to advertise reason of lost connection
"""
QtGui.QMessageBox.warning(self._w, "Warning", "Lost connection : %s"%reason)
reactor.stop()
app.exit()
def clientConnectionFailed(self, connector, reason):
"""
@summary: Connection failed event
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
@param reason: str use to advertise reason of lost connection
"""
QtGui.QMessageBox.warning(self._w, "Warning", "Connection failed : %s"%reason)
reactor.stop()
app.exit()
def help():
print "Usage: rdpy-rdpclient [options] ip[:port]"
print "\t-u: user name"
print "\t-p: password"
print "\t-d: domain"
print "\t-w: width of screen default value is 1024"
print "\t-l: height of screen default value is 800"
if __name__ == '__main__':
#default script argument
username = ""
password = ""
domain = ""
width = 1024
height = 800
try:
opts, args = getopt.getopt(sys.argv[1:], "hu:p:d:w:l:")
except getopt.GetoptError:
help()
for opt, arg in opts:
if opt == "-h":
help()
sys.exit()
elif opt == "-u":
username = arg
elif opt == "-p":
password = arg
elif opt == "-d":
domain = arg
elif opt == "-w":
width = int(arg)
elif opt == "-l":
height = int(arg)
if ':' in args[0]:
ip, port = args[0].split(':')
else:
ip, port = args[0], "3389"
#create application
app = QtGui.QApplication(sys.argv)
#add qt4 reactor
import qt4reactor
qt4reactor.install()
from twisted.internet import reactor
reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain))
reactor.runReturn()
app.exec_()

47
bin/rdpy-rdpclient.py Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/python
#
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
example of use rdpy as rdp client
"""
import sys
import asyncio
from rdpy.core import tpkt, x224
from rdpy.core.nla import ntlm
from rdpy.core.t125 import mcs
from rdpy.model.message import UInt8
if __name__ == '__main__':
#sys.exit(app.exec_())
async def tcp_echo_client(message):
reader, writer = await asyncio.open_connection(
'127.0.0.1', 33389)
x224_layer = await x224.connect(tpkt.Tpkt(reader, writer), ntlm.NTLMv2("", "sylvain", "sylvain"))
mcs_layer = mcs.Client(x224_layer)
await mcs_layer.connect()
await asyncio.sleep(10)
print("foooooooooooooooooooo")
asyncio.run(tcp_echo_client('Hello World!'))

184
bin/rdpy-rdphoneypot.py Executable file
View 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
View 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()

View File

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

View File

@@ -1,6 +1,6 @@
#!/usr/bin/python
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -23,32 +23,43 @@ example of use rdpy
take screenshot of login page
"""
import sys, os, getopt
import getopt
import sys
from PyQt4 import QtCore, QtGui
from rdpy.protocol.rdp import rdp
from PyQt4 import QtGui
from rdpy.core import rdp
from rdpy.ui.qt4 import RDPBitmapToQtImage
import rdpy.base.log as log
from twisted.internet import task
import rdpy.core.log as log
from rdpy.core.error import RDPSecurityNegoFail
#set log level
# set log level
log._LOG_LEVEL = log.Level.INFO
class RDPScreenShotFactory(rdp.ClientFactory):
"""
@summary: Factory for screenshot exemple
"""
def __init__(self, width, height, path, timeout):
__INSTANCE__ = 0
__STATE__ = []
def __init__(self, reactor, app, width, height, path, timeout):
"""
@param width: width of screen
@param height: height of screen
@param path: path of output screenshot
@param timeout: close connection after timeout s without any updating
@param reactor: twisted reactor
@param width: {integer} width of screen
@param height: {integer} height of screen
@param path: {str} path of output screenshot
@param timeout: {float} close connection after timeout s without any updating
"""
RDPScreenShotFactory.__INSTANCE__ += 1
self._reactor = reactor
self._app = app
self._width = width
self._height = height
self._path = path
self._timeout = timeout
#NLA server can't be screenshooting
self._security = rdp.SecurityLevel.RDP_LEVEL_SSL
def clientConnectionLost(self, connector, reason):
"""
@@ -56,9 +67,18 @@ class RDPScreenShotFactory(rdp.ClientFactory):
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
@param reason: str use to advertise reason of lost connection
"""
log.info("connection lost : %s"%reason)
reactor.stop()
app.exit()
if reason.type == RDPSecurityNegoFail and self._security != "rdp":
log.info("due to RDPSecurityNegoFail try standard security layer")
self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
connector.connect()
return
log.info("connection lost : %s" % reason)
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
RDPScreenShotFactory.__INSTANCE__ -= 1
if(RDPScreenShotFactory.__INSTANCE__ == 0):
self._reactor.stop()
self._app.exit()
def clientConnectionFailed(self, connector, reason):
"""
@@ -66,10 +86,12 @@ class RDPScreenShotFactory(rdp.ClientFactory):
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
@param reason: str use to advertise reason of lost connection
"""
log.info("connection failes : %s"%reason)
reactor.stop()
app.exit()
log.info("connection failed : %s"%reason)
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
RDPScreenShotFactory.__INSTANCE__ -= 1
if(RDPScreenShotFactory.__INSTANCE__ == 0):
self._reactor.stop()
self._app.exit()
def buildObserver(self, controller, addr):
"""
@@ -81,53 +103,91 @@ class RDPScreenShotFactory(rdp.ClientFactory):
"""
@summary: observer that connect, cache every image received and save at deconnection
"""
def __init__(self, controller, width, height, path, timeout):
def __init__(self, controller, width, height, path, timeout, reactor):
"""
@param controller: RDPClientController
@param width: width of screen
@param height: height of screen
@param path: path of output screenshot
@param timeout: close connection after timeout s without any updating
@param controller: {RDPClientController}
@param width: {integer} width of screen
@param height: {integer} height of screen
@param path: {str} path of output screenshot
@param timeout: {float} close connection after timeout s without any updating
@param reactor: twisted reactor
"""
rdp.RDPClientObserver.__init__(self, controller)
controller.setScreen(width, height);
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
self._path = path
self._hasUpdated = True
self._brandWidthTask = task.LoopingCall(self.checkUpdate)
self._brandWidthTask.start(timeout) # call every second
self._timeout = timeout
self._startTimeout = False
self._reactor = reactor
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@summary: callback use when bitmap is received
"""
self._hasUpdated = True
image = RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data);
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data);
with QtGui.QPainter(self._buffer) as qp:
#draw image
# draw image
qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1)
if not self._startTimeout:
self._startTimeout = False
self._reactor.callLater(self._timeout, self.checkUpdate)
def onReady(self):
"""
@summary: callback use when RDP stack is connected (just before received bitmap)
"""
log.info("connected %s"%addr)
log.info("connected %s" % addr)
def onSessionReady(self):
"""
@summary: Windows session is ready
@see: rdp.RDPClientObserver.onSessionReady
"""
pass
def onClose(self):
"""
@summary: callback use when RDP stack is closed
"""
log.info("save screenshot into %s"%self._path)
log.info("save screenshot into %s" % self._path)
self._buffer.save(self._path)
def checkUpdate(self):
if not self._hasUpdated:
log.info("close connection on timeout without updating orders")
self._controller.close();
return
self._hasUpdated = False
self._controller.close();
controller.setScreen(self._width, self._height);
controller.setSecurityLevel(self._security)
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout, self._reactor)
def main(width, height, path, timeout, hosts):
"""
@summary: main algorithm
@param height: {integer} height of screenshot
@param width: {integer} width of screenshot
@param timeout: {float} in sec
@param hosts: {list(str(ip[:port]))}
@return: {list(tuple(ip, port, Failure instance)} list of connection state
"""
#create application
app = QtGui.QApplication(sys.argv)
#add qt4 reactor
import qt4reactor
qt4reactor.install()
from twisted.internet import reactor
for host in hosts:
if ':' in host:
ip, port = host.split(':')
else:
ip, port = host, "3389"
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(reactor, app, width, height, path + "%s.jpg" % ip, timeout))
reactor.runReturn()
app.exec_()
return RDPScreenShotFactory.__STATE__
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout)
def help():
print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
@@ -137,11 +197,11 @@ def help():
print "\t-t: timeout of connection without any updating order (default is 2s)"
if __name__ == '__main__':
#default script argument
# default script argument
width = 1024
height = 800
path = "/tmp/rdpy-rdpscreenshot.jpg"
timeout = 2.0
path = "/tmp/"
timeout = 5.0
try:
opts, args = getopt.getopt(sys.argv[1:], "hw:l:o:t:")
@@ -160,19 +220,4 @@ if __name__ == '__main__':
elif opt == "-t":
timeout = float(arg)
if ':' in args[0]:
ip, port = args[0].split(':')
else:
ip, port = args[0], "3389"
#create application
app = QtGui.QApplication(sys.argv)
#add qt4 reactor
import qt4reactor
qt4reactor.install()
from twisted.internet import reactor
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(width, height, path, timeout))
reactor.runReturn()
app.exec_()
main(width, height, path, timeout, args)

125
bin/rdpy-rssplayer.py Executable file
View 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_())

View File

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

View File

@@ -1,6 +1,6 @@
#!/usr/bin/python
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -26,7 +26,7 @@ take screenshot of login page
import sys, os, getopt
from PyQt4 import QtCore, QtGui
from rdpy.protocol.rfb import rfb
import rdpy.base.log as log
import rdpy.core.log as log
from rdpy.ui.qt4 import qtImageFormatFromRFBPixelFormat
from twisted.internet import task
@@ -37,11 +37,13 @@ class RFBScreenShotFactory(rfb.ClientFactory):
"""
@summary: Factory for screenshot exemple
"""
__INSTANCE__ = 0
def __init__(self, password, path):
"""
@param password: password for VNC authentication
@param path: path of output screenshot
"""
RFBScreenShotFactory.__INSTANCE__ += 1
self._path = path
self._password = password
@@ -52,8 +54,10 @@ class RFBScreenShotFactory(rfb.ClientFactory):
@param reason: str use to advertise reason of lost connection
"""
log.info("connection lost : %s"%reason)
reactor.stop()
app.exit()
RFBScreenShotFactory.__INSTANCE__ -= 1
if(RFBScreenShotFactory.__INSTANCE__ == 0):
reactor.stop()
app.exit()
def clientConnectionFailed(self, connector, reason):
"""
@@ -62,8 +66,10 @@ class RFBScreenShotFactory(rfb.ClientFactory):
@param reason: str use to advertise reason of lost connection
"""
log.info("connection failed : %s"%reason)
reactor.stop()
app.exit()
RFBScreenShotFactory.__INSTANCE__ -= 1
if(RFBScreenShotFactory.__INSTANCE__ == 0):
reactor.stop()
app.exit()
def buildObserver(self, controller, addr):
@@ -132,7 +138,7 @@ def help():
if __name__ == '__main__':
#default script argument
path = "/tmp/rdpy-vncscreenshot.jpg"
path = "/tmp/"
password = ""
try:
@@ -148,20 +154,23 @@ if __name__ == '__main__':
elif opt == "-p":
password = arg
if ':' in args[0]:
ip, port = args[0].split(':')
else:
ip, port = args[0], "5900"
#create application
app = QtGui.QApplication(sys.argv)
#add qt4 reactor
import qt4reactor
qt4reactor.install()
from twisted.internet import reactor
reactor.connectTCP(ip, int(port), RFBScreenShotFactory(password, path))
for arg in args:
if ':' in arg:
ip, port = arg.split(':')
else:
ip, port = arg, "5900"
reactor.connectTCP(ip, int(port), RFBScreenShotFactory(password, path + "%s.jpg"%ip))
reactor.runReturn()
app.exec_()

View File

@@ -939,9 +939,18 @@ static PyMethodDef rle_methods[] =
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC
initrle(void)
static struct PyModuleDef rle =
{
(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);
}

351
rdpy/core/lic.py Normal file
View File

@@ -0,0 +1,351 @@
#
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
@summary: RDP extended license
@see: http://msdn.microsoft.com/en-us/library/cc241880.aspx
"""
from rdpy.model.type import CompositeType, CallableValue, UInt8, UInt16Le, UInt32Le, Buffer, sizeof, FactoryType, ArrayType, Stream
from rdpy.model.error import InvalidExpectedDataException
import rdpy.model.log as log
from rdpy.core import sec
from rdpy.core.t125 import gcc
from rdpy.security import rc4
from rdpy.security import rsa_wrapper as rsa
class MessageType(object):
"""
@summary: License packet message type
"""
LICENSE_REQUEST = 0x01
PLATFORM_CHALLENGE = 0x02
NEW_LICENSE = 0x03
UPGRADE_LICENSE = 0x04
LICENSE_INFO = 0x12
NEW_LICENSE_REQUEST = 0x13
PLATFORM_CHALLENGE_RESPONSE = 0x15
ERROR_ALERT = 0xFF
class ErrorCode(object):
"""
@summary: License error message code
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
"""
ERR_INVALID_SERVER_CERTIFICATE = 0x00000001
ERR_NO_LICENSE = 0x00000002
ERR_INVALID_SCOPE = 0x00000004
ERR_NO_LICENSE_SERVER = 0x00000006
STATUS_VALID_CLIENT = 0x00000007
ERR_INVALID_CLIENT = 0x00000008
ERR_INVALID_PRODUCTID = 0x0000000B
ERR_INVALID_MESSAGE_LEN = 0x0000000C
ERR_INVALID_MAC = 0x00000003
class StateTransition(object):
"""
@summary: Automata state transition
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
"""
ST_TOTAL_ABORT = 0x00000001
ST_NO_TRANSITION = 0x00000002
ST_RESET_PHASE_TO_START = 0x00000003
ST_RESEND_LAST_MESSAGE = 0x00000004
class BinaryBlobType(object):
"""
@summary: Binary blob data type
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
"""
BB_ANY_BLOB = 0x0000
BB_DATA_BLOB = 0x0001
BB_RANDOM_BLOB = 0x0002
BB_CERTIFICATE_BLOB = 0x0003
BB_ERROR_BLOB = 0x0004
BB_ENCRYPTED_DATA_BLOB = 0x0009
BB_KEY_EXCHG_ALG_BLOB = 0x000D
BB_SCOPE_BLOB = 0x000E
BB_CLIENT_USER_NAME_BLOB = 0x000F
BB_CLIENT_MACHINE_NAME_BLOB = 0x0010
class Preambule(object):
"""
@summary: Preambule version
"""
PREAMBLE_VERSION_2_0 = 0x2
PREAMBLE_VERSION_3_0 = 0x3
EXTENDED_ERROR_MSG_SUPPORTED = 0x80
class LicenseBinaryBlob(CompositeType):
"""
@summary: Blob use by license manager to exchange security data
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
"""
def __init__(self, blobType = BinaryBlobType.BB_ANY_BLOB, optional = False):
CompositeType.__init__(self, optional = optional)
self.wBlobType = UInt16Le(blobType, constant = True if blobType != BinaryBlobType.BB_ANY_BLOB else False)
self.wBlobLen = UInt16Le(lambda:sizeof(self.blobData))
self.blobData = Buffer(readLen = self.wBlobLen)
class LicensingErrorMessage(CompositeType):
"""
@summary: License error message
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
"""
_MESSAGE_TYPE_ = MessageType.ERROR_ALERT
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.dwErrorCode = UInt32Le()
self.dwStateTransition = UInt32Le()
self.blob = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
class ProductInformation(CompositeType):
"""
@summary: License server product information
@see: http://msdn.microsoft.com/en-us/library/cc241915.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.dwVersion = UInt32Le()
self.cbCompanyName = UInt32Le(lambda:sizeof(self.pbCompanyName))
#may contain "Microsoft Corporation" from server microsoft
self.pbCompanyName = Buffer("Microsoft Corporation", readLen = self.cbCompanyName, unicode = True)
self.cbProductId = UInt32Le(lambda:sizeof(self.pbProductId))
#may contain "A02" from microsoft license server
self.pbProductId = Buffer("A02", readLen = self.cbProductId, unicode = True)
class Scope(CompositeType):
"""
@summary: Use in license nego
@see: http://msdn.microsoft.com/en-us/library/cc241917.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.scope = LicenseBinaryBlob(BinaryBlobType.BB_SCOPE_BLOB)
class ScopeList(CompositeType):
"""
@summary: Use in license nego
@see: http://msdn.microsoft.com/en-us/library/cc241916.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.scopeCount = UInt32Le(lambda:sizeof(self.scopeArray))
self.scopeArray = ArrayType(Scope, readLen = self.scopeCount)
class ServerLicenseRequest(CompositeType):
"""
@summary: Send by server to signal license request
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc241914.aspx
"""
_MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.serverRandom = Buffer("\x00" * 32, readLen = CallableValue(32))
self.productInfo = ProductInformation()
self.keyExchangeList = LicenseBinaryBlob(BinaryBlobType.BB_KEY_EXCHG_ALG_BLOB)
self.serverCertificate = LicenseBinaryBlob(BinaryBlobType.BB_CERTIFICATE_BLOB)
self.scopeList = ScopeList()
class ClientNewLicenseRequest(CompositeType):
"""
@summary: Send by client to ask new license for client.
RDPY doesn'support license reuse, need it in futur version
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
"""
_MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
#RSA and must be only RSA
self.preferredKeyExchangeAlg = UInt32Le(0x00000001, constant = True)
#pure microsoft client ;-)
#http://msdn.microsoft.com/en-us/library/1040af38-c733-4fb3-acd1-8db8cc979eda#id10
self.platformId = UInt32Le(0x04000000 | 0x00010000)
self.clientRandom = Buffer("\x00" * 32, readLen = CallableValue(32))
self.encryptedPreMasterSecret = LicenseBinaryBlob(BinaryBlobType.BB_RANDOM_BLOB)
self.ClientUserName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_USER_NAME_BLOB)
self.ClientMachineName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_MACHINE_NAME_BLOB)
class ServerPlatformChallenge(CompositeType):
"""
@summary: challenge send from server to client
@see: http://msdn.microsoft.com/en-us/library/cc241921.aspx
"""
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.connectFlags = UInt32Le()
self.encryptedPlatformChallenge = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
self.MACData = Buffer(readLen = CallableValue(16))
class ClientPLatformChallengeResponse(CompositeType):
"""
@summary: client challenge response
@see: http://msdn.microsoft.com/en-us/library/cc241922.aspx
"""
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE_RESPONSE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.encryptedPlatformChallengeResponse = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
self.encryptedHWID = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
self.MACData = Buffer(readLen = CallableValue(16))
class LicPacket(CompositeType):
"""
@summary: A license packet
"""
def __init__(self, message = None):
CompositeType.__init__(self)
#preambule
self.bMsgtype = UInt8(lambda:self.licensingMessage.__class__._MESSAGE_TYPE_)
self.flag = UInt8(Preambule.PREAMBLE_VERSION_3_0)
self.wMsgSize = UInt16Le(lambda: sizeof(self))
def LicensingMessageFactory():
"""
@summary: factory for message nego
Use in read mode
"""
for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest, ServerPlatformChallenge, ClientPLatformChallengeResponse]:
if self.bMsgtype.value == c._MESSAGE_TYPE_:
return c(readLen = self.wMsgSize - 4)
log.debug("unknown license message : %s"%self.bMsgtype.value)
return Buffer(readLen =self.wMsgSize - 4)
if message is None:
message = FactoryType(LicensingMessageFactory)
elif not "_MESSAGE_TYPE_" in message.__class__.__dict__:
raise InvalidExpectedDataException("Try to send an invalid license message")
self.licensingMessage = message
def createValidClientLicensingErrorMessage():
"""
@summary: Create a licensing error message that accept client
server automata message
"""
message = LicensingErrorMessage()
message.dwErrorCode.value = ErrorCode.STATUS_VALID_CLIENT
message.dwStateTransition.value = StateTransition.ST_NO_TRANSITION
return LicPacket(message)
class LicenseManager(object):
"""
@summary: handle license automata (client side)
@see: http://msdn.microsoft.com/en-us/library/cc241890.aspx
"""
def __init__(self, transport):
"""
@param transport: layer use to send packet
"""
self._transport = transport
self._username = ""
self._hostname = ""
def recv(self, s):
"""
@summary: receive license packet from PDU layer
@return true when license automata is finish
"""
licPacket = LicPacket()
s.read_type(licPacket)
#end of automata
if licPacket.bMsgtype.value == MessageType.ERROR_ALERT and licPacket.licensingMessage.dwErrorCode.value == ErrorCode.STATUS_VALID_CLIENT and licPacket.licensingMessage.dwStateTransition.value == StateTransition.ST_NO_TRANSITION:
return True
elif licPacket.bMsgtype.value == MessageType.LICENSE_REQUEST:
self.sendClientNewLicenseRequest(licPacket.licensingMessage)
return False
elif licPacket.bMsgtype.value == MessageType.PLATFORM_CHALLENGE:
self.sendClientChallengeResponse(licPacket.licensingMessage)
return False
#yes get a new license
elif licPacket.bMsgtype.value == MessageType.NEW_LICENSE:
return True
else:
raise InvalidExpectedDataException("Not a valid license packet")
def sendClientNewLicenseRequest(self, licenseRequest):
"""
@summary: Create new license request in response to server license request
@param licenseRequest: {ServerLicenseRequest}
@see: http://msdn.microsoft.com/en-us/library/cc241989.aspx
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
"""
#get server information
serverRandom = licenseRequest.serverRandom.value
if self._transport.getGCCServerSettings().SC_SECURITY.serverCertificate._is_readed:
serverCertificate = self._transport.getGCCServerSettings().SC_SECURITY.serverCertificate
else:
s = Stream(licenseRequest.serverCertificate.blobData.value)
serverCertificate = gcc.ServerCertificate()
s.read_type(serverCertificate)
#generate crypto values
clientRandom = rsa.random(256)
preMasterSecret = rsa.random(384)
masterSecret = sec.masterSecret(preMasterSecret, clientRandom, serverRandom)
sessionKeyBlob = sec.masterSecret(masterSecret, serverRandom, clientRandom)
self._macSalt = sessionKeyBlob[:16]
self._licenseKey = sec.finalHash(sessionKeyBlob[16:32], clientRandom, serverRandom)
#format message
message = ClientNewLicenseRequest()
message.clientRandom.value = clientRandom
message.encryptedPreMasterSecret.blobData.value = rsa.encrypt(preMasterSecret[::-1], serverCertificate.certData.getPublicKey())[::-1] + "\x00" * 8
message.ClientMachineName.blobData.value = self._hostname + "\x00"
message.ClientUserName.blobData.value = self._username + "\x00"
self._transport.sendFlagged(sec.SecurityFlag.SEC_LICENSE_PKT, LicPacket(message))
def sendClientChallengeResponse(self, platformChallenge):
"""
@summary: generate valid challenge response
@param platformChallenge: {ServerPlatformChallenge}
"""
serverEncryptedChallenge = platformChallenge.encryptedPlatformChallenge.blobData.value
#decrypt server challenge
#it should be TEST word in unicode format
serverChallenge = rc4.crypt(rc4.RC4Key(self._licenseKey), serverEncryptedChallenge)
if serverChallenge != "T\x00E\x00S\x00T\x00\x00\x00":
raise InvalidExpectedDataException("bad license server challenge")
#generate hwid
s = Stream()
s.write_type((UInt32Le(2), Buffer(self._hostname + self._username + "\x00" * 16)))
hwid = s.getvalue()[:20]
message = ClientPLatformChallengeResponse()
message.encryptedPlatformChallengeResponse.blobData.value = serverEncryptedChallenge
message.encryptedHWID.blobData.value = rc4.crypt(rc4.RC4Key(self._licenseKey), hwid)
message.MACData.value = sec.macData(self._macSalt, serverChallenge + hwid)
self._transport.sendFlagged(sec.SecurityFlag.SEC_LICENSE_PKT, LicPacket(message))

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

@@ -0,0 +1,206 @@
#
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
@summary: Credential Security Support Provider
@see: https://msdn.microsoft.com/en-us/library/cc226764.aspx
"""
from pyasn1.type import namedtype, univ, tag
import pyasn1.codec.der.encoder as der_encoder
import pyasn1.codec.der.decoder as der_decoder
from rdpy.core.nla import sspi
from rdpy.model.message import Stream
from rdpy.model import error
from rdpy.security.x509 import X509Certificate
class NegoToken(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('negoToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
)
class NegoData(univ.SequenceOf):
"""
@summary: contain spnego ntlm of kerberos data
@see: https://msdn.microsoft.com/en-us/library/cc226781.aspx
"""
componentType = NegoToken()
class TSRequest(univ.Sequence):
"""
@summary: main structure
@see: https://msdn.microsoft.com/en-us/library/cc226780.aspx
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('version', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.OptionalNamedType('negoTokens', NegoData().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
namedtype.OptionalNamedType('authInfo', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
namedtype.OptionalNamedType('pubKeyAuth', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
namedtype.OptionalNamedType('errorCode', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
)
class TSCredentials(univ.Sequence):
"""
@summary: contain user information
@see: https://msdn.microsoft.com/en-us/library/cc226782.aspx
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('credType', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.NamedType('credentials', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)
class TSPasswordCreds(univ.Sequence):
"""
@summary: contain username and password
@see: https://msdn.microsoft.com/en-us/library/cc226783.aspx
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('domainName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.NamedType('userName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
namedtype.NamedType('password', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
)
class TSCspDataDetail(univ.Sequence):
"""
@summary: smart card credentials
@see: https://msdn.microsoft.com/en-us/library/cc226785.aspx
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('keySpec', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.OptionalNamedType('cardName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
namedtype.OptionalNamedType('readerName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
namedtype.OptionalNamedType('containerName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
namedtype.OptionalNamedType('cspName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
)
class TSSmartCardCreds(univ.Sequence):
"""
@summary: smart card credentials
@see: https://msdn.microsoft.com/en-us/library/cc226784.aspx
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('pin', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.NamedType('cspData', TSCspDataDetail().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
namedtype.OptionalNamedType('userHint', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
namedtype.OptionalNamedType('domainHint', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
)
def encode_der_trequest(nego_types=[], auth_info=None, pub_key_auth=None):
"""
@summary: create TSRequest from list of Type
@param nego_types: {list(Type)}
@param auth_info: {str} authentication info TSCredentials encrypted with authentication protocol
@param pub_key_auth: {str} public key encrypted with authentication protocol
@return: {str} TRequest der encoded
"""
negoData = NegoData().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))
#fill nego data tokens
i = 0
for negoType in nego_types:
s = Stream()
s.write_type(negoType)
negoToken = NegoToken()
negoToken.setComponentByPosition(0, s.getvalue())
negoData.setComponentByPosition(i, negoToken)
i += 1
request = TSRequest()
request.setComponentByName("version", univ.Integer(2).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
if i > 0:
request.setComponentByName("negoTokens", negoData)
if not auth_info is None:
request.setComponentByName("authInfo", univ.OctetString(auth_info).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
if not pub_key_auth is None:
request.setComponentByName("pubKeyAuth", univ.OctetString(pub_key_auth).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
return der_encoder.encode(request)
def decode_der_trequest(s):
"""
@summary: Decode the stream as
@param s: {str}
"""
return der_decoder.decode(s, asn1Spec=TSRequest())[0]
def getNegoTokens(tRequest):
negoData = tRequest.getComponentByName("negoTokens")
return [Stream(negoData.getComponentByPosition(i).getComponentByPosition(0).asOctets()) for i in range(len(negoData))]
def encode_der_tcredentials(domain, username, password):
passwordCred = TSPasswordCreds()
passwordCred.setComponentByName("domainName", domain)
passwordCred.setComponentByName("userName", username)
passwordCred.setComponentByName("password", password)
credentials = TSCredentials()
credentials.setComponentByName("credType", 1)
credentials.setComponentByName("credentials", der_encoder.encode(passwordCred))
return der_encoder.encode(credentials)
async def connect(reader, writer, authentication_protocol: sspi.IAuthenticationProtocol):
"""
CSSP connection sequence
"""
# send negotiate message
writer.write(encode_der_trequest(nego_types=[authentication_protocol.getNegotiateMessage()]))
await writer.drain()
trequest = decode_der_trequest(await reader.read(1500))
message, interface = authentication_protocol.getAuthenticateMessage(getNegoTokens(trequest)[0])
# get binary certificate
# There is no other way to get certificate that have net been validated
peer_certificate = writer.transport._ssl_protocol._sslpipe.ssl_object._sslobj.getpeercert(True)
x509_certificate = der_decoder.decode(peer_certificate, asn1Spec=X509Certificate())[0]
public_key = x509_certificate.getComponentByName("tbsCertificate").getComponentByName("subjectPublicKeyInfo").getComponentByName("subjectPublicKey").asOctets()
# send back public key encrypted with NTLM
writer.write(encode_der_trequest(nego_types=[message], pub_key_auth=interface.GSS_WrapEx(public_key)))
await writer.drain()
# now check the increment
# sever must respond with the same key incremented to one
trequest = decode_der_trequest(await reader.read(1500))
public_key_inc = interface.GSS_UnWrapEx(trequest.getComponentByName("pubKeyAuth").asOctets())
if int.from_bytes(public_key, "little") + 1 != int.from_bytes(public_key_inc, "little"):
raise error.InvalidExpectedDataException("CSSP : Invalid public key increment")
domain, user, password = authentication_protocol.getEncodedCredentials()
writer.write(encode_der_trequest(auth_info=interface.GSS_WrapEx(encode_der_tcredentials(domain, user, password))))
await writer.drain()

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

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

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

@@ -0,0 +1,69 @@
#
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
@summary: security service provider interface (Microsoft)
"""
from rdpy.model.error import CallPureVirtualFuntion
class IAuthenticationProtocol(object):
"""
@summary: generic class for authentication Protocol (ex: ntlmv2, SPNEGO or kerberos)
"""
def getNegotiateMessage(self):
"""
@summary: Client first handshake message for authentication protocol
@return: {object} first handshake message
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getNegotiateMessage", "IAuthenticationProtocol"))
def getAuthenticateMessage(self, s):
"""
@summary: Client last handshake message
@param s: {Stream} challenge message stream
@return: {(object, IGenericSecurityService)} Last handshake message and interface for application
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getAuthenticateMessage", "IAuthenticationProtocol"))
def getEncodedCredentials(self):
"""
@summary: return encoded credentials accorded with authentication protocol nego
@return: (domain, username, password)
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getEncodedCredentials", "IAuthenticationProtocol"))
class IGenericSecurityService(object):
"""
@summary: use by application from authentification protocol
@see: http://www.rfc-editor.org/rfc/rfc2743.txt
"""
def GSS_WrapEx(self, data):
"""
@summary: encrypt data with key exchange in Authentication protocol
@param data: {str}
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "GSS_WrapEx", "IGenericSecurityService"))
def GSS_UnWrapEx(self, data):
"""
@summary: decrypt data with key exchange in Authentication protocol
@param data: {str}
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "GSS_UnWrapEx", "IGenericSecurityService"))

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -16,19 +16,20 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from rdpy.base.error import InvalidExpectedDataException
import rdpy.base.log as log
from rdpy.model.error import InvalidExpectedDataException
import rdpy.model.log as log
"""
Definition of structure use for capabilities nego
Use in PDU layer
"""
from rdpy.network.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
from rdpy.model.type import CompositeType, CallableValue, Buffer, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
class CapsType(object):
"""
Different type of capabilities
@summary: Different type of capabilities
@see: http://msdn.microsoft.com/en-us/library/cc240486.aspx
"""
CAPSTYPE_GENERAL = 0x0001
@@ -62,7 +63,7 @@ class CapsType(object):
class MajorType(object):
"""
Use in general capability
@summary: Use in general capability
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
"""
OSMAJORTYPE_UNSPECIFIED = 0x0000
@@ -76,7 +77,7 @@ class MajorType(object):
class MinorType(object):
"""
Use in general capability
@summary: Use in general capability
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
"""
OSMINORTYPE_UNSPECIFIED = 0x0000
@@ -92,7 +93,7 @@ class MinorType(object):
class GeneralExtraFlag(object):
"""
Use in general capability
@summary: Use in general capability
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
"""
FASTPATH_OUTPUT_SUPPORTED = 0x0001
@@ -107,7 +108,7 @@ class Boolean(object):
class OrderFlag(object):
"""
Use in order capability
@summary: Use in order capability
@see: http://msdn.microsoft.com/en-us/library/cc240556.aspx
"""
NEGOTIATEORDERSUPPORT = 0x0002
@@ -118,7 +119,7 @@ class OrderFlag(object):
class Order(object):
"""
Drawing orders supported
@summary: Drawing orders supported
Use in order capability
@see: http://msdn.microsoft.com/en-us/library/cc240556.aspx
"""
@@ -146,7 +147,7 @@ class Order(object):
class OrderEx(object):
"""
Extension orders
@summary: Extension orders
Use in order capability
"""
ORDERFLAGS_EX_CACHE_BITMAP_REV3_SUPPORT = 0x0002
@@ -154,7 +155,7 @@ class OrderEx(object):
class InputFlags(object):
"""
Input flag use in input capability
@summary: Input flag use in input capability
@see: http://msdn.microsoft.com/en-us/library/cc240563.aspx
"""
INPUT_FLAG_SCANCODES = 0x0001
@@ -168,7 +169,7 @@ class InputFlags(object):
class BrushSupport(object):
"""
Brush support of client
@summary: Brush support of client
@see: http://msdn.microsoft.com/en-us/library/cc240564.aspx
"""
BRUSH_DEFAULT = 0x00000000
@@ -177,7 +178,7 @@ class BrushSupport(object):
class GlyphSupport(object):
"""
Use by glyph order
@summary: Use by glyph order
@see: http://msdn.microsoft.com/en-us/library/cc240565.aspx
"""
GLYPH_SUPPORT_NONE = 0x0000
@@ -187,7 +188,7 @@ class GlyphSupport(object):
class OffscreenSupportLevel(object):
"""
Use to determine offscreen cache level supported
@summary: Use to determine offscreen cache level supported
@see: http://msdn.microsoft.com/en-us/library/cc240550.aspx
"""
FALSE = 0x00000000
@@ -195,7 +196,7 @@ class OffscreenSupportLevel(object):
class VirtualChannelCompressionFlag(object):
"""
Use to determine virtual channel compression
@summary: Use to determine virtual channel compression
@see: http://msdn.microsoft.com/en-us/library/cc240551.aspx
"""
VCCAPS_NO_COMPR = 0x00000000
@@ -204,7 +205,7 @@ class VirtualChannelCompressionFlag(object):
class SoundFlag(object):
"""
Use in sound capability to inform it
@summary: Use in sound capability to inform it
@see: http://msdn.microsoft.com/en-us/library/cc240552.aspx
"""
NONE = 0x0000
@@ -212,7 +213,7 @@ class SoundFlag(object):
class CacheEntry(CompositeType):
"""
Use in capability cache exchange
@summary: Use in capability cache exchange
@see: http://msdn.microsoft.com/en-us/library/cc240566.aspx
"""
def __init__(self):
@@ -223,7 +224,7 @@ class CacheEntry(CompositeType):
class Capability(CompositeType):
"""
A capability
@summary: A capability
@see: http://msdn.microsoft.com/en-us/library/cc240486.aspx
"""
def __init__(self, capability = None):
@@ -235,12 +236,12 @@ class Capability(CompositeType):
"""
Closure for capability factory
"""
for c in [GeneralCapability, BitmapCapability, OrderCapability, BitmapCacheCapability, PointerCapability, InputCapability, BrushCapability, GlyphCapability, OffscreenBitmapCacheCapability, VirtualChannelCapability, SoundCapability, ControlCapability, WindowActivationCapability, FontCapability, ColorCacheCapability, ShareCapability]:
if self.capabilitySetType.value == c._TYPE_:
for c in [GeneralCapability, BitmapCapability, OrderCapability, BitmapCacheCapability, PointerCapability, InputCapability, BrushCapability, GlyphCapability, OffscreenBitmapCacheCapability, VirtualChannelCapability, SoundCapability, ControlCapability, WindowActivationCapability, FontCapability, ColorCacheCapability, ShareCapability, MultiFragmentUpdate]:
if self.capabilitySetType.value == c._TYPE_ and (self.lengthCapability.value - 4) > 0:
return c(readLen = self.lengthCapability - 4)
log.debug("unknown Capability type : %s"%hex(self.capabilitySetType.value))
#read entire packet
return String(readLen = self.lengthCapability - 4)
return Buffer(readLen =self.lengthCapability - 4)
if capability is None:
capability = FactoryType(CapabilityFactory)
@@ -251,7 +252,7 @@ class Capability(CompositeType):
class GeneralCapability(CompositeType):
"""
General capability (protocol version and compression mode)
@summary: General capability (protocol version and compression mode)
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
@@ -274,7 +275,7 @@ class GeneralCapability(CompositeType):
class BitmapCapability(CompositeType):
"""
Bitmap format Capability
@summary: Bitmap format Capability
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240554.aspx
@@ -299,7 +300,7 @@ class BitmapCapability(CompositeType):
class OrderCapability(CompositeType):
"""
Order capability list all drawing order supported
@summary: Order capability list all drawing order supported
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240556.aspx
@@ -308,7 +309,7 @@ class OrderCapability(CompositeType):
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.terminalDescriptor = String("\x00" * 16, readLen = UInt8(16))
self.terminalDescriptor = Buffer("\x00" * 16, readLen = CallableValue(16))
self.pad4octetsA = UInt32Le(0)
self.desktopSaveXGranularity = UInt16Le(1)
self.desktopSaveYGranularity = UInt16Le(20)
@@ -316,7 +317,7 @@ class OrderCapability(CompositeType):
self.maximumOrderLevel = UInt16Le(1)
self.numberFonts = UInt16Le()
self.orderFlags = UInt16Le(OrderFlag.NEGOTIATEORDERSUPPORT)
self.orderSupport = ArrayType(UInt8, init = [UInt8(0) for _ in range (0, 32)], readLen = UInt8(32))
self.orderSupport = ArrayType(UInt8, init = [UInt8(0) for _ in range (0, 32)], readLen = CallableValue(32))
self.textFlags = UInt16Le()
self.orderSupportExFlags = UInt16Le()
self.pad4octetsB = UInt32Le()
@@ -328,7 +329,7 @@ class OrderCapability(CompositeType):
class BitmapCacheCapability(CompositeType):
"""
Order use to cache bitmap very useful
@summary: Order use to cache bitmap very useful
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240559.aspx
"""
@@ -351,7 +352,7 @@ class BitmapCacheCapability(CompositeType):
class PointerCapability(CompositeType):
"""
Use to indicate pointer handle of client
@summary: Use to indicate pointer handle of client
Paint by server or per client
client -> server
server -> client
@@ -359,15 +360,16 @@ class PointerCapability(CompositeType):
"""
_TYPE_ = CapsType.CAPSTYPE_POINTER
def __init__(self, readLen = None):
def __init__(self, isServer = False, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.colorPointerFlag = UInt16Le()
self.colorPointerCacheSize = UInt16Le()
self.pointerCacheSize = UInt16Le()
self.colorPointerCacheSize = UInt16Le(20)
#old version of rdp doesn't support ...
self.pointerCacheSize = UInt16Le(conditional = lambda:isServer)
class InputCapability(CompositeType):
"""
Use to indicate input capabilities
@summary: Use to indicate input capabilities
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240563.aspx
@@ -387,11 +389,11 @@ class InputCapability(CompositeType):
#same value as gcc.ClientCoreSettings.keyboardFnKeys
self.keyboardFunctionKey = UInt32Le()
#same value as gcc.ClientCoreSettingrrs.imeFileName
self.imeFileName = String("\x00" * 64, readLen = UInt8(64))
self.imeFileName = Buffer("\x00" * 64, readLen = CallableValue(64))
class BrushCapability(CompositeType):
"""
Use to indicate brush capability
@summary: Use to indicate brush capability
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240564.aspx
"""
@@ -403,7 +405,7 @@ class BrushCapability(CompositeType):
class GlyphCapability(CompositeType):
"""
Use in font order
@summary: Use in font order
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240565.aspx
"""
@@ -411,7 +413,7 @@ class GlyphCapability(CompositeType):
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.glyphCache = ArrayType(CacheEntry, init = [CacheEntry() for _ in range(0,10)], readLen = UInt8(10))
self.glyphCache = ArrayType(CacheEntry, init = [CacheEntry() for _ in range(0,10)], readLen = CallableValue(10))
self.fragCache = UInt32Le()
#all fonts are sent with bitmap format (very expensive)
self.glyphSupportLevel = UInt16Le(GlyphSupport.GLYPH_SUPPORT_NONE)
@@ -419,7 +421,7 @@ class GlyphCapability(CompositeType):
class OffscreenBitmapCacheCapability(CompositeType):
"""
use to cached bitmap in offscreen area
@summary: use to cached bitmap in offscreen area
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240550.aspx
"""
@@ -433,7 +435,7 @@ class OffscreenBitmapCacheCapability(CompositeType):
class VirtualChannelCapability(CompositeType):
"""
use to determine virtual channel compression
@summary: use to determine virtual channel compression
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240551.aspx
@@ -447,7 +449,7 @@ class VirtualChannelCapability(CompositeType):
class SoundCapability(CompositeType):
"""
Use to exchange sound capability
@summary: Use to exchange sound capability
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240552.aspx
"""
@@ -460,7 +462,7 @@ class SoundCapability(CompositeType):
class ControlCapability(CompositeType):
"""
client -> server but server ignore contents! Thanks krosoft for brandwidth
@summary: client -> server but server ignore contents! Thanks krosoft for brandwidth
@see: http://msdn.microsoft.com/en-us/library/cc240568.aspx
"""
_TYPE_ = CapsType.CAPSTYPE_CONTROL
@@ -474,7 +476,7 @@ class ControlCapability(CompositeType):
class WindowActivationCapability(CompositeType):
"""
client -> server but server ignore contents! Thanks krosoft for brandwidth
@summary: client -> server but server ignore contents! Thanks krosoft for brandwidth
@see: http://msdn.microsoft.com/en-us/library/cc240569.aspx
"""
_TYPE_ = CapsType.CAPSTYPE_ACTIVATION
@@ -488,7 +490,7 @@ class WindowActivationCapability(CompositeType):
class FontCapability(CompositeType):
"""
Use to indicate font support
@summary: Use to indicate font support
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240571.aspx
@@ -515,7 +517,7 @@ class ColorCacheCapability(CompositeType):
class ShareCapability(CompositeType):
"""
Use to advertise channel id of server
@summary: Use to advertise channel id of server
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240570.aspx
@@ -526,3 +528,16 @@ class ShareCapability(CompositeType):
CompositeType.__init__(self, readLen = readLen)
self.nodeId = UInt16Le()
self.pad2octets = UInt16Le()
class MultiFragmentUpdate(CompositeType):
"""
@summary: Use to advertise fast path max buffer to use
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240649.aspx
"""
_TYPE_ = CapsType.CAPSETTYPE_MULTIFRAGMENTUPDATE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.MaxRequestSize = UInt32Le(0)

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -22,80 +22,14 @@ Implement the main graphic layer
In this layer are managed all mains bitmap update orders end user inputs
"""
from rdpy.network.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
from rdpy.base.error import InvalidExpectedDataException
import rdpy.base.log as log
import caps, order
class SecurityFlag(object):
"""
Microsoft security flags
@see: http://msdn.microsoft.com/en-us/library/cc240579.aspx
"""
SEC_EXCHANGE_PKT = 0x0001
SEC_TRANSPORT_REQ = 0x0002
RDP_SEC_TRANSPORT_RSP = 0x0004
SEC_ENCRYPT = 0x0008
SEC_RESET_SEQNO = 0x0010
SEC_IGNORE_SEQNO = 0x0020
SEC_INFO_PKT = 0x0040
SEC_LICENSE_PKT = 0x0080
SEC_LICENSE_ENCRYPT_CS = 0x0200
SEC_LICENSE_ENCRYPT_SC = 0x0200
SEC_REDIRECTION_PKT = 0x0400
SEC_SECURE_CHECKSUM = 0x0800
SEC_AUTODETECT_REQ = 0x1000
SEC_AUTODETECT_RSP = 0x2000
SEC_HEARTBEAT = 0x4000
SEC_FLAGSHI_VALID = 0x8000
class InfoFlag(object):
"""
Client capabilities informations
"""
INFO_MOUSE = 0x00000001
INFO_DISABLECTRLALTDEL = 0x00000002
INFO_AUTOLOGON = 0x00000008
INFO_UNICODE = 0x00000010
INFO_MAXIMIZESHELL = 0x00000020
INFO_LOGONNOTIFY = 0x00000040
INFO_COMPRESSION = 0x00000080
INFO_ENABLEWINDOWSKEY = 0x00000100
INFO_REMOTECONSOLEAUDIO = 0x00002000
INFO_FORCE_ENCRYPTED_CS_PDU = 0x00004000
INFO_RAIL = 0x00008000
INFO_LOGONERRORS = 0x00010000
INFO_MOUSE_HAS_WHEEL = 0x00020000
INFO_PASSWORD_IS_SC_PIN = 0x00040000
INFO_NOAUDIOPLAYBACK = 0x00080000
INFO_USING_SAVED_CREDS = 0x00100000
INFO_AUDIOCAPTURE = 0x00200000
INFO_VIDEO_DISABLE = 0x00400000
INFO_CompressionTypeMask = 0x00001E00
class PerfFlag(object):
"""
Network performances flag
"""
PERF_DISABLE_WALLPAPER = 0x00000001
PERF_DISABLE_FULLWINDOWDRAG = 0x00000002
PERF_DISABLE_MENUANIMATIONS = 0x00000004
PERF_DISABLE_THEMING = 0x00000008
PERF_DISABLE_CURSOR_SHADOW = 0x00000020
PERF_DISABLE_CURSORSETTINGS = 0x00000040
PERF_ENABLE_FONT_SMOOTHING = 0x00000080
PERF_ENABLE_DESKTOP_COMPOSITION = 0x00000100
class AfInet(object):
"""
IPv4 or IPv6 adress style
"""
AF_INET = 0x00002
AF_INET6 = 0x0017
from rdpy.model.type import CompositeType, CallableValue, Buffer, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
from rdpy.model.error import InvalidExpectedDataException
import rdpy.model.log as log
from rdpy.core.pdu import caps, order
class PDUType(object):
"""
Data PDU type primary index
@summary: Data PDU type primary index
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
"""
PDUTYPE_DEMANDACTIVEPDU = 0x11
@@ -106,7 +40,7 @@ class PDUType(object):
class PDUType2(object):
"""
Data PDU type secondary index
@summary: Data PDU type secondary index
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
PDUTYPE2_UPDATE = 0x02
@@ -136,7 +70,7 @@ class PDUType2(object):
class StreamId(object):
"""
Stream priority
@summary: Stream priority
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
STREAM_UNDEFINED = 0x00
@@ -146,7 +80,7 @@ class StreamId(object):
class CompressionOrder(object):
"""
PDU compression order
@summary: PDU compression order
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
CompressionTypeMask = 0x0F
@@ -156,7 +90,7 @@ class CompressionOrder(object):
class CompressionType(object):
"""
PDU compression type
@summary: PDU compression type
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
PACKET_COMPR_TYPE_8K = 0x0
@@ -166,7 +100,7 @@ class CompressionType(object):
class Action(object):
"""
Action flag use in Control PDU packet
@summary: Action flag use in Control PDU packet
@see: http://msdn.microsoft.com/en-us/library/cc240492.aspx
"""
CTRLACTION_REQUEST_CONTROL = 0x0001
@@ -176,7 +110,7 @@ class Action(object):
class PersistentKeyListFlag(object):
"""
Use to determine the number of persistent key packet
@summary: Use to determine the number of persistent key packet
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
"""
PERSIST_FIRST_PDU = 0x01
@@ -184,7 +118,7 @@ class PersistentKeyListFlag(object):
class BitmapFlag(object):
"""
Use in bitmap update PDU
@summary: Use in bitmap update PDU
@see: http://msdn.microsoft.com/en-us/library/cc240612.aspx
"""
BITMAP_COMPRESSION = 0x0001
@@ -192,7 +126,7 @@ class BitmapFlag(object):
class UpdateType(object):
"""
Use in update PDU to determine which type of update
@summary: Use in update PDU to determine which type of update
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
"""
UPDATETYPE_ORDERS = 0x0000
@@ -202,7 +136,7 @@ class UpdateType(object):
class InputMessageType(object):
"""
Use in slow-path input PDU
@summary: Use in slow-path input PDU
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
"""
INPUT_EVENT_SYNC = 0x0000
@@ -214,7 +148,7 @@ class InputMessageType(object):
class PointerFlag(object):
"""
Use in Pointer event
@summary: Use in Pointer event
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
"""
PTRFLAGS_HWHEEL = 0x0400
@@ -227,9 +161,18 @@ class PointerFlag(object):
PTRFLAGS_BUTTON2 = 0x2000
PTRFLAGS_BUTTON3 = 0x4000
class PointerExFlag(object):
"""
@summary: Use in Pointer event
@see: https://msdn.microsoft.com/en-us/library/cc240587.aspx
"""
PTRXFLAGS_DOWN = 0x8000
PTRXFLAGS_BUTTON1 = 0x0001
PTRXFLAGS_BUTTON2 = 0x0002
class KeyboardFlag(object):
"""
Use in scan code key event
@summary: Use in scan code key event
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
"""
KBDFLAGS_EXTENDED = 0x0100
@@ -238,7 +181,7 @@ class KeyboardFlag(object):
class FastPathUpdateType(object):
"""
Use in Fast Path update packet
@summary: Use in Fast Path update packet
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
"""
FASTPATH_UPDATETYPE_ORDERS = 0x0
@@ -255,22 +198,32 @@ class FastPathUpdateType(object):
class FastPathOutputCompression(object):
"""
Flag for compression
@summary: Flag for compression
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
"""
FASTPATH_OUTPUT_COMPRESSION_USED = 0x2
class Display(object):
"""
Use in supress output PDU
@summary: Use in supress output PDU
@see: http://msdn.microsoft.com/en-us/library/cc240648.aspx
"""
SUPPRESS_DISPLAY_UPDATES = 0x00
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):
"""
Error code use in Error info PDU
@summary: Error code use in Error info PDU
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
"""
ERRINFO_RPC_INITIATED_DISCONNECT = 0x00000001
@@ -479,68 +432,26 @@ class ErrorInfo(object):
ERRINFO_VCDATATOOLONG : "The size of a received Virtual Channel PDU (section 2.2.6.1) exceeds the chunking size specified in the Virtual Channel Capability Set (section 2.2.7.1.10).",
}
class RDPInfo(CompositeType):
"""
Client informations
Contains credentials (very important packet)
@see: http://msdn.microsoft.com/en-us/library/cc240475.aspx
"""
def __init__(self, extendedInfoConditional):
CompositeType.__init__(self)
#code page
self.codePage = UInt32Le()
#support flag
self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS)
self.cbDomain = UInt16Le(lambda:sizeof(self.domain) - 2)
self.cbUserName = UInt16Le(lambda:sizeof(self.userName) - 2)
self.cbPassword = UInt16Le(lambda:sizeof(self.password) - 2)
self.cbAlternateShell = UInt16Le(lambda:sizeof(self.alternateShell) - 2)
self.cbWorkingDir = UInt16Le(lambda:sizeof(self.workingDir) - 2)
#microsoft domain
self.domain = String(readLen = UInt16Le(lambda:self.cbDomain.value + 2), unicode = True)
self.userName = String(readLen = UInt16Le(lambda:self.cbUserName.value + 2), unicode = True)
self.password = String(readLen = UInt16Le(lambda:self.cbPassword.value + 2), unicode = True)
#shell execute at start of session
self.alternateShell = String(readLen = UInt16Le(lambda:self.cbAlternateShell.value + 2), unicode = True)
#working directory for session
self.workingDir = String(readLen = UInt16Le(lambda:self.cbWorkingDir.value + 2), unicode = True)
self.extendedInfo = RDPExtendedInfo(conditional = extendedInfoConditional)
class RDPExtendedInfo(CompositeType):
"""
Add more client informations
"""
def __init__(self, conditional):
CompositeType.__init__(self, conditional = conditional)
self.clientAddressFamily = UInt16Le(AfInet.AF_INET)
self.cbClientAddress = UInt16Le(lambda:sizeof(self.clientAddress))
self.clientAddress = String(readLen = self.cbClientAddress, unicode = True)
self.cbClientDir = UInt16Le(lambda:sizeof(self.clientDir))
self.clientDir = String(readLen = self.cbClientDir, unicode = True)
#TODO make tiomezone
self.clientTimeZone = String("\x00" * 172)
self.clientSessionId = UInt32Le()
self.performanceFlags = UInt32Le()
class ShareControlHeader(CompositeType):
"""
PDU share control header
@summary: PDU share control header
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
"""
def __init__(self, totalLength, pduType, userId):
"""
Set pduType as constant
@summary: Set pduType as constant
@param totalLength: total length of PDU packet
"""
CompositeType.__init__(self)
#share control header
self.totalLength = UInt16Le(totalLength)
self.pduType = UInt16Le(pduType)
self.PDUSource = UInt16Le(userId)
#for xp sp3 and deactiveallpdu PDUSource may not be present
self.PDUSource = UInt16Le(userId, optional = True)
class ShareDataHeader(CompositeType):
"""
PDU share data header
@summary: PDU share data header
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
def __init__(self, size, pduType2 = 0, shareId = 0):
@@ -555,7 +466,7 @@ class ShareDataHeader(CompositeType):
class PDU(CompositeType):
"""
Main PDU message
@summary: Main PDU message
"""
def __init__(self, userId = 0, pduMessage = None):
CompositeType.__init__(self)
@@ -563,14 +474,14 @@ class PDU(CompositeType):
def PDUMessageFactory():
"""
build message in accordance of type self.shareControlHeader.pduType.value
@summary: build message in accordance of type self.shareControlHeader.pduType.value
"""
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
return c()
return c(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
log.debug("unknown PDU type : %s"%hex(self.shareControlHeader.pduType.value))
#read entire packet
return String()
return Buffer(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
if pduMessage is None:
pduMessage = FactoryType(PDUMessageFactory)
@@ -582,17 +493,17 @@ class PDU(CompositeType):
class DemandActivePDU(CompositeType):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240485.aspx
Main use for capabilities exchange server -> client
@summary: Main use for capabilities exchange server -> client
"""
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.shareId = UInt32Le()
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets)))
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
self.sourceDescriptor = Buffer("rdpy", readLen = self.lengthSourceDescriptor)
self.numberCapabilities = UInt16Le(lambda:len(self.capabilitySets._array))
self.pad2Octets = UInt16Le()
self.capabilitySets = ArrayType(caps.Capability, readLen = self.numberCapabilities)
@@ -601,56 +512,58 @@ class DemandActivePDU(CompositeType):
class ConfirmActivePDU(CompositeType):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240488.aspx
Main use for capabilities confirm client -> sever
@summary: Main use for capabilities confirm client -> sever
"""
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.shareId = UInt32Le()
self.originatorId = UInt16Le(0x03EA, constant = True)
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets)))
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
self.sourceDescriptor = Buffer("rdpy", readLen = self.lengthSourceDescriptor)
self.numberCapabilities = UInt16Le(lambda:len(self.capabilitySets._array))
self.pad2Octets = UInt16Le()
self.capabilitySets = ArrayType(caps.Capability, readLen = self.numberCapabilities)
class DeactiveAllPDU(CompositeType):
"""
Use to signal already connected session
@summary: Use to signal already connected session
@see: http://msdn.microsoft.com/en-us/library/cc240536.aspx
"""
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DEACTIVATEALLPDU
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
#in old version this packet is empty i don't know
#and not specified
CompositeType.__init__(self, optional = True, readLen = readLen)
self.shareId = UInt32Le()
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
self.sourceDescriptor = Buffer("rdpy", readLen = self.lengthSourceDescriptor)
class DataPDU(CompositeType):
"""
Generic PDU packet use after connection sequence
@summary: Generic PDU packet use after connection sequence
"""
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
def __init__(self, pduData = None, shareId = 0):
CompositeType.__init__(self)
def __init__(self, pduData = None, shareId = 0, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), lambda:self.pduData.__class__._PDUTYPE2_, shareId)
def PDUDataFactory():
"""
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_:
return c()
return c(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value))
return String()
return Buffer(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
if pduData is None:
pduData = FactoryType(PDUDataFactory)
@@ -691,7 +604,7 @@ class ControlDataPDU(CompositeType):
class ErrorInfoDataPDU(CompositeType):
"""
Use to inform error in PDU layer
@summary: Use to inform error in PDU layer
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU
@@ -707,7 +620,7 @@ class ErrorInfoDataPDU(CompositeType):
class FontListDataPDU(CompositeType):
"""
Use to indicate list of font. Deprecated packet
@summary: Use to indicate list of font. Deprecated packet
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
"""
@@ -725,7 +638,7 @@ class FontListDataPDU(CompositeType):
class FontMapDataPDU(CompositeType):
"""
Use to indicate map of font. Deprecated packet (maybe the same as FontListDataPDU)
@summary: Use to indicate map of font. Deprecated packet (maybe the same as FontListDataPDU)
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
"""
@@ -743,7 +656,7 @@ class FontMapDataPDU(CompositeType):
class PersistentListEntry(CompositeType):
"""
Use to record persistent key in PersistentListPDU
@summary: Use to record persistent key in PersistentListPDU
@see: http://msdn.microsoft.com/en-us/library/cc240496.aspx
"""
def __init__(self):
@@ -753,14 +666,14 @@ class PersistentListEntry(CompositeType):
class PersistentListPDU(CompositeType):
"""
Use to indicate that bitmap cache was already
@summary: Use to indicate that bitmap cache was already
Fill with some keys from previous session
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST
def __init__(self, userId = 0, shareId = 0):
CompositeType.__init__(self)
def __init__(self, userId = 0, shareId = 0, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.numEntriesCache0 = UInt16Le()
self.numEntriesCache1 = UInt16Le()
self.numEntriesCache2 = UInt16Le()
@@ -774,38 +687,38 @@ class PersistentListPDU(CompositeType):
self.bitMask = UInt8()
self.pad2 = UInt8()
self.pad3 = UInt16Le()
self.entries = ArrayType(PersistentListEntry, readLen = UInt16Le(lambda:(self.numEntriesCache0 + self.numEntriesCache1 + self.numEntriesCache2 + self.numEntriesCache3 + self.numEntriesCache4)))
self.entries = ArrayType(PersistentListEntry, readLen = CallableValue(lambda:(self.numEntriesCache0 + self.numEntriesCache1 + self.numEntriesCache2 + self.numEntriesCache3 + self.numEntriesCache4)))
class ClientInputEventPDU(CompositeType):
"""
PDU use to send client inputs in slow path mode
@summary: PDU use to send client inputs in slow path mode
@see: http://msdn.microsoft.com/en-us/library/cc746160.aspx
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.numEvents = UInt16Le(lambda:len(self.slowPathInputEvents._array))
self.pad2Octets = UInt16Le()
self.slowPathInputEvents = ArrayType(SlowPathInputEvent, readLen = self.numEvents)
class ShutdownRequestPDU(CompositeType):
"""
PDU use to signal that the session will be closed
@summary: PDU use to signal that the session will be closed
client -> server
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
class ShutdownDeniedPDU(CompositeType):
"""
PDU use to signal which the session will be closed is connected
@summary: PDU use to signal which the session will be closed is connected
server -> client
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
class InclusiveRectangle(CompositeType):
"""
@@ -844,7 +757,7 @@ class RefreshRectPDU(CompositeType):
class UpdateDataPDU(CompositeType):
"""
Update data PDU use by server to inform update image or palet
@summary: Update data PDU use by server to inform update image or palet
for example
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
"""
@@ -861,13 +774,13 @@ class UpdateDataPDU(CompositeType):
def UpdateDataFactory():
"""
Create object in accordance self.updateType value
@summary: Create object in accordance self.updateType value
"""
for c in [BitmapUpdateDataPDU]:
if self.updateType.value == c._UPDATE_TYPE_:
return c()
return c(readLen = CallableValue(readLen.value - 2))
log.debug("unknown PDU update data type : %s"%hex(self.updateType.value))
return String()
return Buffer(readLen = CallableValue(readLen.value - 2))
if updateData is None:
updateData = FactoryType(UpdateDataFactory, conditional = lambda:(self.updateType.value != UpdateType.UPDATETYPE_SYNCHRONIZE))
@@ -876,9 +789,21 @@ class UpdateDataPDU(CompositeType):
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):
"""
Fast path update PDU packet
@summary: Fast path update PDU packet
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
"""
def __init__(self, updateData = None):
@@ -889,13 +814,13 @@ class FastPathUpdatePDU(CompositeType):
def UpdateDataFactory():
"""
Create correct object in accordance to self.updateHeader field
@summary: Create correct object in accordance to self.updateHeader field
"""
for c in [FastPathBitmapUpdateDataPDU]:
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
return c()
return c(readLen = self.size)
log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf))
return String()
return Buffer(readLen = self.size)
if updateData is None:
updateData = FactoryType(UpdateDataFactory)
@@ -906,7 +831,7 @@ class FastPathUpdatePDU(CompositeType):
class BitmapUpdateDataPDU(CompositeType):
"""
PDU use to send raw bitmap compressed or not
@summary: PDU use to send raw bitmap compressed or not
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
"""
_UPDATE_TYPE_ = UpdateType.UPDATETYPE_BITMAP
@@ -921,20 +846,20 @@ class BitmapUpdateDataPDU(CompositeType):
class OrderUpdateDataPDU(CompositeType):
"""
PDU type use to communicate Accelerated order (GDI)
@summary: PDU type use to communicate Accelerated order (GDI)
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
@todo: not implemented yet but need it
"""
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.pad2OctetsA = UInt16Le()
self.numberOrders = UInt16Le(lambda:len(self.orderData._array))
self.pad2OctetsB = UInt16Le()
self.orderData = ArrayType(order.DrawingOrder, readLen = self.numberOrders)
self.orderData = ArrayType(order.PrimaryDrawingOrder, readLen = self.numberOrders)
class BitmapCompressedDataHeader(CompositeType):
"""
Compressed header of bitmap
@summary: Compressed header of bitmap
@see: http://msdn.microsoft.com/en-us/library/cc240644.aspx
"""
def __init__(self, bodySize = 0, scanWidth = 0, uncompressedSize = 0, conditional = lambda:True):
@@ -953,7 +878,7 @@ class BitmapCompressedDataHeader(CompositeType):
class BitmapData(CompositeType):
"""
Bitmap data here the screen capture
@summary: Bitmap data here the screen capture
"""
def __init__(self, destLeft = 0, destTop = 0, destRight = 0, destBottom = 0, width = 0, height = 0, bitsPerPixel = 0, bitmapDataStream = ""):
"""
@@ -977,24 +902,24 @@ class BitmapData(CompositeType):
self.flags = UInt16Le()
self.bitmapLength = UInt16Le(lambda:(sizeof(self.bitmapComprHdr) + sizeof(self.bitmapDataStream)))
self.bitmapComprHdr = BitmapCompressedDataHeader(bodySize = lambda:sizeof(self.bitmapDataStream), scanWidth = lambda:self.width.value, uncompressedSize = lambda:(self.width.value * self.height.value * self.bitsPerPixel.value), conditional = lambda:((self.flags.value & BitmapFlag.BITMAP_COMPRESSION) and not (self.flags.value & BitmapFlag.NO_BITMAP_COMPRESSION_HDR)))
self.bitmapDataStream = String(bitmapDataStream, readLen = UInt16Le(lambda:(self.bitmapLength.value if (not self.flags.value & BitmapFlag.BITMAP_COMPRESSION or self.flags.value & BitmapFlag.NO_BITMAP_COMPRESSION_HDR) else self.bitmapComprHdr.cbCompMainBodySize.value)))
self.bitmapDataStream = Buffer(bitmapDataStream, readLen = CallableValue(lambda:(self.bitmapLength.value if (not self.flags.value & BitmapFlag.BITMAP_COMPRESSION or self.flags.value & BitmapFlag.NO_BITMAP_COMPRESSION_HDR) else self.bitmapComprHdr.cbCompMainBodySize.value)))
class FastPathBitmapUpdateDataPDU(CompositeType):
"""
Fast path version of bitmap update PDU
@summary: Fast path version of bitmap update PDU
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
"""
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
def __init__(self):
CompositeType.__init__(self)
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.header = UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True)
self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array))
self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles)
class SlowPathInputEvent(CompositeType):
"""
PDU use in slow-path sending client inputs
@summary: PDU use in slow-path sending client inputs
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
"""
def __init__(self, messageData = None):
@@ -1003,11 +928,10 @@ class SlowPathInputEvent(CompositeType):
self.messageType = UInt16Le(lambda:self.slowPathInputData.__class__._INPUT_MESSAGE_TYPE_)
def SlowPathInputDataFactory():
for c in [PointerEvent, ScancodeKeyEvent, UnicodeKeyEvent]:
for c in [PointerEvent, PointerExEvent, ScancodeKeyEvent, UnicodeKeyEvent, SynchronizeEvent]:
if self.messageType.value == c._INPUT_MESSAGE_TYPE_:
return c()
log.debug("unknown slow path input : %s"%hex(self.messageType.value))
return String()
raise InvalidExpectedDataException("unknown slow path input : %s"%hex(self.messageType.value))
if messageData is None:
messageData = FactoryType(SlowPathInputDataFactory)
@@ -1016,9 +940,21 @@ class SlowPathInputEvent(CompositeType):
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):
"""
Event use to communicate mouse position
@summary: Event use to communicate mouse position
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_MOUSE
@@ -1029,9 +965,22 @@ class PointerEvent(CompositeType):
self.xPos = UInt16Le()
self.yPos = UInt16Le()
class PointerExEvent(CompositeType):
"""
@summary: Event use to communicate mouse position
@see: http://msdn.microsoft.com/en-us/library/cc240587.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_MOUSEX
def __init__(self):
CompositeType.__init__(self)
self.pointerFlags = UInt16Le()
self.xPos = UInt16Le()
self.yPos = UInt16Le()
class ScancodeKeyEvent(CompositeType):
"""
Event use to communicate keyboard informations
@summary: Event use to communicate keyboard informations
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SCANCODE
@@ -1044,7 +993,7 @@ class ScancodeKeyEvent(CompositeType):
class UnicodeKeyEvent(CompositeType):
"""
Event use to communicate keyboard informations
@summary: Event use to communicate keyboard informations
@see: http://msdn.microsoft.com/en-us/library/cc240585.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_UNICODE

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -23,27 +23,34 @@ Implement the main graphic layer
In this layer are managed all mains bitmap update orders end user inputs
"""
from rdpy.network.layer import LayerAutomata
from rdpy.base.error import InvalidExpectedDataException, CallPureVirtualFuntion
from rdpy.network.type import UInt16Le
import rdpy.base.log as log
import rdpy.protocol.rdp.gcc as gcc
import rdpy.protocol.rdp.tpkt as tpkt
import lic, data, caps
from rdpy.model.layer import LayerAutomata
from rdpy.model.error import CallPureVirtualFuntion
from rdpy.model.type import ArrayType
import rdpy.model.log as log
from rdpy.core import tpkt
from rdpy.core.pdu import data, caps
class PDUClientListener(object):
"""
Interface for PDU client automata listener
@summary: Interface for PDU client automata listener
"""
def onReady(self):
"""
Event call when PDU layer is ready to send events
@summary: Event call when PDU layer is ready to send events
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener"))
def 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):
"""
call when a bitmap data is received from update PDU
@summary: call when a bitmap data is received from update PDU
@param rectangles: [pdu.BitmapData] struct
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onUpdate", "PDUClientListener"))
@@ -56,37 +63,34 @@ class PDUClientListener(object):
class PDUServerListener(object):
"""
Interface for PDU server automata listener
@summary: Interface for PDU server automata listener
"""
def onReady(self):
"""
Event call when PDU layer is ready to send update
@summary: Event call when PDU layer is ready to send update
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUServerListener"))
def onSlowPathInput(self, slowPathInputEvents):
"""
Event call when slow path input are available
@summary: Event call when slow path input are available
@param slowPathInputEvents: [data.SlowPathInputEvent]
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSlowPathInput", "PDUServerListener"))
class PDULayer(LayerAutomata):
class PDULayer(LayerAutomata, tpkt.IFastPathListener):
"""
Global channel for MCS that handle session
@summary: Global channel for MCS that handle session
identification user, licensing management, and capabilities exchange
"""
def __init__(self):
LayerAutomata.__init__(self, None)
#logon info send from client to server
self._info = data.RDPInfo(extendedInfoConditional = lambda:(self._transport.getGCCServerSettings().getBlock(gcc.MessageType.SC_CORE).rdpVersion.value == gcc.Version.RDP_VERSION_5_PLUS))
#server capabilities
self._serverCapabilities = {
caps.CapsType.CAPSTYPE_GENERAL : caps.Capability(caps.GeneralCapability()),
caps.CapsType.CAPSTYPE_BITMAP : caps.Capability(caps.BitmapCapability()),
caps.CapsType.CAPSTYPE_ORDER : caps.Capability(caps.OrderCapability()),
caps.CapsType.CAPSTYPE_POINTER : caps.Capability(caps.PointerCapability()),
caps.CapsType.CAPSTYPE_POINTER : caps.Capability(caps.PointerCapability(isServer = True)),
caps.CapsType.CAPSTYPE_INPUT : caps.Capability(caps.InputCapability()),
caps.CapsType.CAPSTYPE_VIRTUALCHANNEL : caps.Capability(caps.VirtualChannelCapability()),
caps.CapsType.CAPSTYPE_FONT : caps.Capability(caps.FontCapability()),
@@ -105,28 +109,38 @@ class PDULayer(LayerAutomata):
caps.CapsType.CAPSTYPE_GLYPHCACHE : caps.Capability(caps.GlyphCapability()),
caps.CapsType.CAPSTYPE_OFFSCREENCACHE : caps.Capability(caps.OffscreenBitmapCacheCapability()),
caps.CapsType.CAPSTYPE_VIRTUALCHANNEL : caps.Capability(caps.VirtualChannelCapability()),
caps.CapsType.CAPSTYPE_SOUND : caps.Capability(caps.SoundCapability())
caps.CapsType.CAPSTYPE_SOUND : caps.Capability(caps.SoundCapability()),
caps.CapsType.CAPSETTYPE_MULTIFRAGMENTUPDATE : caps.Capability(caps.MultiFragmentUpdate())
}
#share id between client and server
self._shareId = 0x103EA
#enable or not fast path
self._fastPathSender = None
def setFastPathSender(self, fastPathSender):
"""
@param fastPathSender: {tpkt.FastPathSender}
@note: implement tpkt.IFastPathListener
"""
self._fastPathSender = fastPathSender
def sendPDU(self, pduMessage):
"""
Send a PDU data to transport layer
@summary: Send a PDU data to transport layer
@param pduMessage: PDU message
"""
self._transport.send(data.PDU(self._transport.getUserId(), pduMessage))
def sendDataPDU(self, pduData):
"""
Send an PDUData to transport layer
@summary: Send an PDUData to transport layer
@param pduData: PDU data message
"""
self.sendPDU(data.DataPDU(pduData, self._shareId))
class Client(PDULayer, tpkt.IFastPathListener):
class Client(PDULayer):
"""
Client automata of PDU layer
@summary: Client automata of PDU layer
"""
def __init__(self, listener):
"""
@@ -134,64 +148,26 @@ class Client(PDULayer, tpkt.IFastPathListener):
"""
PDULayer.__init__(self)
self._listener = listener
#enable or not fast path
self._fastPathSender = None
def connect(self):
"""
Connect message in client automata
Send INfo packet (credentials)
Wait License info
@summary: Connect message in client automata
"""
self.sendInfoPkt()
#next state is license info PDU
self.setNextState(self.recvLicenceInfo)
self._gccCore = self._transport.getGCCClientSettings().CS_CORE
self.setNextState(self.recvDemandActivePDU)
#check if client support fast path message
self._clientFastPathSupported = False
def close(self):
"""
Send PDU close packet and call close method on transport method
@summary: Send PDU close packet and call close method on transport method
"""
self._transport.close()
#self.sendDataPDU(data.ShutdownRequestPDU())
def setFastPathSender(self, fastPathSender):
"""
@param fastPathSender: tpkt.FastPathSender
@note: implement tpkt.IFastPathListener
"""
self._fastPathSender = fastPathSender
def recvLicenceInfo(self, s):
"""
Read license info packet and check if is a valid client info
Wait Demand Active PDU
@param s: Stream
"""
#packet preambule
securityFlag = UInt16Le()
securityFlagHi = UInt16Le()
s.readType((securityFlag, securityFlagHi))
if not (securityFlag.value & data.SecurityFlag.SEC_LICENSE_PKT):
raise InvalidExpectedDataException("Waiting license packet")
validClientPdu = lic.LicPacket()
s.readType(validClientPdu)
if validClientPdu.bMsgtype.value == lic.MessageType.ERROR_ALERT and validClientPdu.licensingMessage.dwErrorCode.value == lic.ErrorCode.STATUS_VALID_CLIENT and validClientPdu.licensingMessage.dwStateTransition.value == lic.StateTransition.ST_NO_TRANSITION:
self.setNextState(self.recvDemandActivePDU)
#not tested because i can't buy RDP license server
elif validClientPdu.bMsgtype.value == lic.MessageType.LICENSE_REQUEST:
newLicenseReq = lic.createNewLicenseRequest(validClientPdu.licensingMessage)
self._transport.send((UInt16Le(data.SecurityFlag.SEC_LICENSE_PKT), UInt16Le(), newLicenseReq))
else:
raise InvalidExpectedDataException("Not a valid license packet")
def recvDemandActivePDU(self, s):
"""
Receive demand active PDU which contains
@summary: Receive demand active PDU which contains
Server capabilities. In this version of RDPY only
Restricted group of capabilities are used.
Send Confirm Active PDU
@@ -200,7 +176,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
@param s: Stream
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DEMANDACTIVEPDU:
#not a blocking error because in deactive reactive sequence
@@ -213,6 +189,9 @@ class Client(PDULayer, tpkt.IFastPathListener):
for cap in pdu.pduMessage.capabilitySets._array:
self._serverCapabilities[cap.capabilitySetType] = cap
#secure checksum cap here maybe protocol (another) design error
self._transport._enableSecureCheckSum = bool(self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags & caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM)
self.sendConfirmActivePDU()
#send synchronize
self.sendClientFinalizeSynchronizePDU()
@@ -220,12 +199,12 @@ class Client(PDULayer, tpkt.IFastPathListener):
def recvServerSynchronizePDU(self, s):
"""
Receive from server
@summary: Receive from server
Wait Control Cooperate PDU
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_SYNCHRONIZE:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -236,12 +215,12 @@ class Client(PDULayer, tpkt.IFastPathListener):
def recvServerControlCooperatePDU(self, s):
"""
Receive control cooperate PDU from server
@summary: Receive control cooperate PDU from server
Wait Control Granted PDU
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_COOPERATE:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -252,12 +231,12 @@ class Client(PDULayer, tpkt.IFastPathListener):
def recvServerControlGrantedPDU(self, s):
"""
Receive last control PDU the granted control PDU
@summary: Receive last control PDU the granted control PDU
Wait Font map PDU
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_GRANTED_CONTROL:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -268,12 +247,12 @@ class Client(PDULayer, tpkt.IFastPathListener):
def recvServerFontMapPDU(self, s):
"""
Last useless connection packet from server to client
@summary: Last useless connection packet from server to client
Wait any PDU
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_FONTMAP:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -286,80 +265,82 @@ class Client(PDULayer, tpkt.IFastPathListener):
def recvPDU(self, s):
"""
Main receive function after connection sequence
@summary: Main receive function after connection sequence
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
self.readDataPDU(pdu.pduMessage)
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
#use in deactivation-reactivation sequence
#next state is either a capabilities re exchange or disconnection
#http://msdn.microsoft.com/en-us/library/cc240454.aspx
self.setNextState(self.recvDemandActivePDU)
pdus = ArrayType(data.PDU)
s.read_type(pdus)
for pdu in pdus:
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
self.readDataPDU(pdu.pduMessage)
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
#use in deactivation-reactivation sequence
#next state is either a capabilities re exchange or disconnection
#http://msdn.microsoft.com/en-us/library/cc240454.aspx
self.setNextState(self.recvDemandActivePDU)
def recvFastPath(self, fastPathS):
def recvFastPath(self, secFlag, fastPathS):
"""
Implement IFastPathListener interface
@summary: Implement IFastPathListener interface
Fast path is needed by RDP 8.0
@param fastPathS: Stream that contain fast path data
@param fastPathS: {Stream} that contain fast path data
@param secFlag: {SecFlags}
"""
fastPathPDU = data.FastPathUpdatePDU()
fastPathS.readType(fastPathPDU)
if fastPathPDU.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
self._listener.onUpdate(fastPathPDU.updateData.rectangles._array)
updates = ArrayType(data.FastPathUpdatePDU)
fastPathS.read_type(updates)
for update in updates:
if update.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
self._listener.onUpdate(update.updateData.rectangles._array)
def readDataPDU(self, dataPDU):
"""
read a data PDU object
@summary: read a data PDU object
@param dataPDU: DataPDU object
"""
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
#ignore 0 error code because is not an error code
if dataPDU.pduData.errorInfo.value == 0:
return
errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value)
if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo):
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
log.error("INFO PDU : %s"%errorMessage)
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_DENIED:
#may be an event to ask to user
self._transport.close()
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SAVE_SESSION_INFO:
#handle session event
self._listener.onSessionReady()
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_UPDATE:
self.readUpdateDataPDU(dataPDU.pduData)
def readUpdateDataPDU(self, updateDataPDU):
"""
Read an update data PDU data
@summary: Read an update data PDU data
dispatch update data
@param: UpdateDataPDU object
@param: {UpdateDataPDU} object
"""
if updateDataPDU.updateType.value == data.UpdateType.UPDATETYPE_BITMAP:
self._listener.onUpdate(updateDataPDU.updateData.rectangles._array)
def sendInfoPkt(self):
"""
Send a logon info packet
client automata data
"""
self._transport.send((UInt16Le(data.SecurityFlag.SEC_INFO_PKT), UInt16Le(), self._info))
def sendConfirmActivePDU(self):
"""
Send all client capabilities
@summary: Send all client capabilities
"""
#init general capability
generalCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability
generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS
generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM
if not self._fastPathSender is None:
generalCapability.extraFlags.value |= caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED
#init bitmap capability
bitmapCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_BITMAP].capability
bitmapCapability.preferredBitsPerPixel = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).highColorDepth
bitmapCapability.desktopWidth = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).desktopWidth
bitmapCapability.desktopHeight = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).desktopHeight
bitmapCapability.preferredBitsPerPixel = self._gccCore.highColorDepth
bitmapCapability.desktopWidth = self._gccCore.desktopWidth
bitmapCapability.desktopHeight = self._gccCore.desktopHeight
#init order capability
orderCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_ORDER].capability
@@ -368,11 +349,11 @@ class Client(PDULayer, tpkt.IFastPathListener):
#init input capability
inputCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability
inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX | caps.InputFlags.INPUT_FLAG_UNICODE
inputCapability.keyboardLayout = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).kbdLayout
inputCapability.keyboardType = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).keyboardType
inputCapability.keyboardSubType = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).keyboardSubType
inputCapability.keyboardrFunctionKey = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).keyboardFnKeys
inputCapability.imeFileName = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).imeFileName
inputCapability.keyboardLayout = self._gccCore.kbdLayout
inputCapability.keyboardType = self._gccCore.keyboardType
inputCapability.keyboardSubType = self._gccCore.keyboardSubType
inputCapability.keyboardrFunctionKey = self._gccCore.keyboardFnKeys
inputCapability.imeFileName = self._gccCore.imeFileName
#make active PDU packet
confirmActivePDU = data.ConfirmActivePDU()
@@ -382,7 +363,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
def sendClientFinalizeSynchronizePDU(self):
"""
send a synchronize PDU from client to server
@summary: send a synchronize PDU from client to server
"""
synchronizePDU = data.SynchronizeDataPDU(self._transport.getChannelId())
self.sendDataPDU(synchronizePDU)
@@ -403,16 +384,16 @@ class Client(PDULayer, tpkt.IFastPathListener):
def sendInputEvents(self, pointerEvents):
"""
send client input events
@summary: send client input events
@param pointerEvents: list of pointer events
"""
pdu = data.ClientInputEventPDU()
pdu.slowPathInputEvents._array = [data.SlowPathInputEvent(x) for x in pointerEvents]
self.sendDataPDU(pdu)
class Server(PDULayer, tpkt.IFastPathListener):
class Server(PDULayer):
"""
Server Automata of PDU layer
@summary: Server Automata of PDU layer
"""
def __init__(self, listener):
"""
@@ -425,49 +406,20 @@ class Server(PDULayer, tpkt.IFastPathListener):
def connect(self):
"""
Connect message for server automata
Wait Info Packet
@summary: Connect message for server automata
"""
self.setNextState(self.recvInfoPkt)
def setFastPathSender(self, fastPathSender):
"""
@param fastPathSender: tpkt.FastPathSender
@note: implement tpkt.IFastPathListener
"""
self._fastPathSender = fastPathSender
def recvInfoPkt(self, s):
"""
Receive info packet from client
Client credentials
Send License valid error message
Send Demand Active PDU
Wait Confirm Active PDU
@param s: Stream
"""
securityFlag = UInt16Le()
securityFlagHi = UInt16Le()
s.readType((securityFlag, securityFlagHi))
if not (securityFlag.value & data.SecurityFlag.SEC_INFO_PKT):
raise InvalidExpectedDataException("Waiting info packet")
s.readType(self._info)
#next state send error license
self.sendLicensingErrorMessage()
self.sendDemandActivePDU()
self.setNextState(self.recvConfirmActivePDU)
def recvConfirmActivePDU(self, s):
"""
Receive confirm active PDU from client
@summary: Receive confirm active PDU from client
Capabilities exchange
Wait Client Synchronize PDU
@param s: Stream
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_CONFIRMACTIVEPDU:
#not a blocking error because in deactive reactive sequence
@@ -479,18 +431,21 @@ class Server(PDULayer, tpkt.IFastPathListener):
self._clientCapabilities[cap.capabilitySetType] = cap
#find use full flag
self._clientFastPathSupported = self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED
self._clientFastPathSupported = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED)
#secure checksum cap here maybe protocol (another) design error
self._transport._enableSecureCheckSum = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags & caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM)
self.setNextState(self.recvClientSynchronizePDU)
def recvClientSynchronizePDU(self, s):
"""
Receive from client
@summary: Receive from client
Wait Control Cooperate PDU
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_SYNCHRONIZE:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -500,12 +455,12 @@ class Server(PDULayer, tpkt.IFastPathListener):
def recvClientControlCooperatePDU(self, s):
"""
Receive control cooperate PDU from client
@summary: Receive control cooperate PDU from client
Wait Control Request PDU
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_COOPERATE:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -515,12 +470,12 @@ class Server(PDULayer, tpkt.IFastPathListener):
def recvClientControlRequestPDU(self, s):
"""
Receive last control PDU the request control PDU from client
@summary: Receive last control PDU the request control PDU from client
Wait Font List PDU
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_CONTROL or pdu.pduMessage.pduData.action.value != data.Action.CTRLACTION_REQUEST_CONTROL:
#not a blocking error because in deactive reactive sequence
#input can be send too but ignored
@@ -530,13 +485,13 @@ class Server(PDULayer, tpkt.IFastPathListener):
def recvClientFontListPDU(self, s):
"""
Last synchronize packet from client to server
@summary: Last synchronize packet from client to server
Send Server Finalize PDUs
Wait any PDU
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value != data.PDUType.PDUTYPE_DATAPDU or pdu.pduMessage.shareDataHeader.pduType2.value != data.PDUType2.PDUTYPE2_FONTLIST:
#not a blocking error because in deactive reactive sequence
#input can be send but ignored
@@ -551,55 +506,49 @@ class Server(PDULayer, tpkt.IFastPathListener):
def recvPDU(self, s):
"""
Main receive function after connection sequence
@summary: Main receive function after connection sequence
@param s: Stream from transport layer
"""
pdu = data.PDU()
s.readType(pdu)
s.read_type(pdu)
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
self.readDataPDU(pdu.pduMessage)
def readDataPDU(self, dataPDU):
"""
read a data PDU object
@summary: read a data PDU object
@param dataPDU: DataPDU object
"""
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value)
if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo):
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
log.error("INFO PDU : %s"%errorMessage)
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_INPUT:
self._listener.onSlowPathInput(dataPDU.pduData.slowPathInputEvents._array)
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_REQUEST:
log.debug("Receive Shutdown Request")
self._transport.close()
def recvFastPath(self, fastPathS):
"""
Implement IFastPathListener interface
@summary: Implement IFastPathListener interface
Fast path is needed by RDP 8.0
@param fastPathS: Stream that contain fast path data
"""
pass
def sendLicensingErrorMessage(self):
"""
Send a licensing error data
"""
self._transport.send((UInt16Le(data.SecurityFlag.SEC_LICENSE_PKT), UInt16Le(), lic.createValidClientLicensingErrorMessage()))
def sendDemandActivePDU(self):
"""
Send server capabilities
server automata PDU
@summary: Send server capabilities server automata PDU
"""
#init general capability
generalCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability
generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS
generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED | caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM
inputCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability
inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX
@@ -611,7 +560,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
def sendServerFinalizeSynchronizePDU(self):
"""
Send last synchronize packet from server to client
@summary: Send last synchronize packet from server to client
"""
synchronizePDU = data.SynchronizeDataPDU(self._transport.getChannelId())
self.sendDataPDU(synchronizePDU)
@@ -632,7 +581,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
def sendPDU(self, pduMessage):
"""
Send a PDU data to transport layer
@summary: Send a PDU data to transport layer
@param pduMessage: PDU message
"""
PDULayer.sendPDU(self, pduMessage)
@@ -643,7 +592,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
def sendBitmapUpdatePDU(self, bitmapDatas):
"""
Send bitmap update data
@summary: Send bitmap update data
@param bitmapDatas: List of data.BitmapData
"""
#check bitmap header for client that want it (very old client)
@@ -656,7 +605,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
#fast path case
fastPathUpdateDataPDU = data.FastPathBitmapUpdateDataPDU()
fastPathUpdateDataPDU.rectangles._array = bitmapDatas
self._fastPathSender.sendFastPath(data.FastPathUpdatePDU(fastPathUpdateDataPDU))
self._fastPathSender.sendFastPath(0, data.FastPathUpdatePDU(fastPathUpdateDataPDU))
else:
#slow path case
updateDataPDU = data.BitmapUpdateDataPDU()

View File

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

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -21,15 +21,30 @@
Use to manage RDP stack in twisted
"""
from rdpy.network import layer
from rdpy.base.error import CallPureVirtualFuntion, InvalidValue
import pdu.layer
import pdu.data
import pdu.caps
import rdpy.base.log as log
import tpkt, x224, mcs, gcc
from rdpy.model import layer
from rdpy.model.error import CallPureVirtualFuntion, InvalidValue
from rdpy.core.pdu.layer import PDUClientListener, PDUServerListener
from rdpy.core.pdu import data
from rdpy.core.pdu import caps
from rdpy.core.pdu import layer as pdu
import rdpy.model.log as log
import rdpy.core.tpkt as tpkt
import rdpy.core.x224 as x224
import rdpy.core.sec as sec
from rdpy.core.t125 import mcs, gcc
from rdpy.core.nla import cssp, ntlm
class RDPClientController(pdu.layer.PDUClientListener):
class SecurityLevel(object):
"""
@summary: RDP security level
"""
RDP_LEVEL_RDP = 0
RDP_LEVEL_SSL = 1
RDP_LEVEL_NLA = 2
class RDPClientController(PDUClientListener):
"""
Manage RDP stack as client
"""
@@ -37,13 +52,18 @@ class RDPClientController(pdu.layer.PDUClientListener):
#list of observer
self._clientObserver = []
#PDU layer
self._pduLayer = pdu.layer.Client(self)
self._pduLayer = pdu.Client(self)
#secure layer
self._secLayer = sec.Client(self._pduLayer)
#multi channel service
self._mcsLayer = mcs.Client(self._pduLayer)
self._mcsLayer = mcs.Client(self._secLayer)
#transport pdu layer
self._x224Layer = x224.Client(self._mcsLayer)
#transport packet (protocol layer)
self._tpktLayer = tpkt.TPKT(self._x224Layer, self._pduLayer)
self._tpktLayer = tpkt.TPKT(self._x224Layer)
#fastpath stack
self._pduLayer.initFastPath(self._secLayer)
self._secLayer.initFastPath(self._tpktLayer)
#is pdu layer is ready to send
self._isReady = False
@@ -52,7 +72,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
@return: return Protocol layer for twisted
In case of RDP TPKT is the Raw layer
"""
return self._tpktLayer
return cssp.CSSP(self._tpktLayer, ntlm.NTLMv2(self._secLayer._info.domain.value, self._secLayer._info.userName.value, self._secLayer._info.password.value))
def getColorDepth(self):
"""
@@ -68,59 +88,96 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setPerformanceSession(self):
"""
Set particular flag in RDP stack to avoid wall-paper, theme, menu animation etc...
@summary: Set particular flag in RDP stack to avoid wall-paper, theme, menu animation etc...
"""
self._pduLayer._info.extendedInfo.performanceFlags.value = pdu.data.PerfFlag.PERF_DISABLE_WALLPAPER | pdu.data.PerfFlag.PERF_DISABLE_MENUANIMATIONS | pdu.data.PerfFlag.PERF_DISABLE_CURSOR_SHADOW | pdu.data.PerfFlag.PERF_DISABLE_THEMING | pdu.data.PerfFlag.PERF_DISABLE_FULLWINDOWDRAG
self._secLayer._info.extendedInfo.performanceFlags.value = sec.PerfFlag.PERF_DISABLE_WALLPAPER | sec.PerfFlag.PERF_DISABLE_MENUANIMATIONS | sec.PerfFlag.PERF_DISABLE_CURSOR_SHADOW | sec.PerfFlag.PERF_DISABLE_THEMING | sec.PerfFlag.PERF_DISABLE_FULLWINDOWDRAG
def setScreen(self, width, height):
"""
Set screen dim of session
@summary: Set screen dim of session
@param width: width in pixel of screen
@param height: height in pixel of screen
"""
#set screen definition in MCS layer
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).desktopHeight.value = height
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).desktopWidth.value = width
self._mcsLayer._clientSettings.get_block(gcc.MessageType.CS_CORE).desktopHeight.value = height
self._mcsLayer._clientSettings.get_block(gcc.MessageType.CS_CORE).desktopWidth.value = width
def setUsername(self, username):
"""
Set the username for session
@param username: username of session
@summary: Set the username for session
@param username: {string} username of session
"""
#username in PDU info packet
self._pduLayer._info.userName.value = username
self._secLayer._info.userName.value = username
self._secLayer._licenceManager._username = username
def setPassword(self, password):
"""
Set password for session
@param password: password of session
@summary: Set password for session
@param password: {string} password of session
"""
self.setAutologon()
self._pduLayer._info.password.value = password
self._secLayer._info.password.value = password
def setDomain(self, domain):
"""
Set the windows domain of session
@param domain: domain of session
@summary: Set the windows domain of session
@param domain: {string} domain of session
"""
self._pduLayer._info.domain.value = domain
self._secLayer._info.domain.value = domain
def setAutologon(self):
"""
@summary: enable autologon
"""
self._pduLayer._info.flag |= pdu.data.InfoFlag.INFO_AUTOLOGON
self._secLayer._info.flag |= sec.InfoFlag.INFO_AUTOLOGON
def setAlternateShell(self, appName):
"""
@summary: set application name of app which start at the begining of session
@param appName: {string} application name
"""
self._secLayer._info.alternateShell.value = appName
def setKeyboardLayout(self, layout):
"""
@summary: keyboard layout
@param layout: us | fr
"""
if layout == "fr":
self._mcsLayer._clientSettings.CS_CORE.kbdLayout.value = gcc.KeyboardLayout.FRENCH
elif layout == "us":
self._mcsLayer._clientSettings.CS_CORE.kbdLayout.value = gcc.KeyboardLayout.US
def setHostname(self, hostname):
"""
@summary: set hostname of machine
"""
self._mcsLayer._clientSettings.CS_CORE.clientName.value = hostname[:15] + "\x00" * (15 - len(hostname))
self._secLayer._licenceManager._hostname = hostname
def setSecurityLevel(self, level):
"""
@summary: Request basic security
@param level: {SecurityLevel}
"""
if level == SecurityLevel.RDP_LEVEL_RDP:
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP
elif level == SecurityLevel.RDP_LEVEL_SSL:
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_SSL
elif level == SecurityLevel.RDP_LEVEL_NLA:
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_SSL | x224.Protocols.PROTOCOL_HYBRID
def addClientObserver(self, observer):
"""
Add observer to RDP protocol
@summary: Add observer to RDP protocol
@param observer: new observer to add
"""
self._clientObserver.append(observer)
def removeClientObserver(self, observer):
"""
Remove observer to RDP protocol stack
@summary: Remove observer to RDP protocol stack
@param observer: observer to remove
"""
for i in range(0, len(self._clientObserver)):
@@ -130,7 +187,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def onUpdate(self, rectangles):
"""
Call when a bitmap data is received from update PDU
@summary: Call when a bitmap data is received from update PDU
@param rectangles: [pdu.BitmapData] struct
"""
for observer in self._clientObserver:
@@ -140,16 +197,25 @@ class RDPClientController(pdu.layer.PDUClientListener):
def onReady(self):
"""
Call when PDU layer is connected
@summary: Call when PDU layer is connected
"""
self._isReady = True
#signal all listener
for observer in self._clientObserver:
observer.onReady()
def onSessionReady(self):
"""
@summary: Call when Windows session is ready (connected)
"""
self._isReady = True
#signal all listener
for observer in self._clientObserver:
observer.onSessionReady()
def onClose(self):
"""
Event call when RDP stack is closed
@summary: Event call when RDP stack is closed
"""
self._isReady = False
for observer in self._clientObserver:
@@ -157,7 +223,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def sendPointerEvent(self, x, y, button, isPressed):
"""
send pointer events
@summary: send pointer events
@param x: x position of pointer
@param y: y position of pointer
@param button: 1 or 2 or 3
@@ -167,18 +233,63 @@ class RDPClientController(pdu.layer.PDUClientListener):
return
try:
event = pdu.data.PointerEvent()
if isPressed:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN
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
if button == 1:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON1
elif button == 2:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON2
elif button == 3:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON3
else:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_MOVE
event = pdu.data.PointerEvent()
if isPressed:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN
if button == 1:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON1
elif button == 2:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON2
elif button == 3:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON3
else:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_MOVE
# position
event.xPos.value = x
event.yPos.value = y
# send proper event
self._pduLayer.sendInputEvents([event])
except InvalidValue:
log.info("try send pointer event with incorrect position")
def sendWheelEvent(self, x, y, step, isNegative = False, isHorizontal = False):
"""
@summary: Send a mouse wheel event
@param x: x position of pointer
@param y: y position of pointer
@param step: number of step rolled
@param isHorizontal: horizontal wheel (default is vertical)
@param isNegative: is upper (default down)
"""
if not self._isReady:
return
try:
event = pdu.data.PointerEvent()
if isHorizontal:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_HWHEEL
else:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL
if isNegative:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL_NEGATIVE
event.pointerFlags.value |= (step & pdu.data.PointerFlag.WheelRotationMask)
#position
event.xPos.value = x
@@ -188,13 +299,14 @@ class RDPClientController(pdu.layer.PDUClientListener):
self._pduLayer.sendInputEvents([event])
except InvalidValue:
log.info("try send pointer 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):
"""
Send a scan code to RDP stack
@summary: Send a scan code to RDP stack
@param code: scan code
@param isPressed: True if key is pressed and false if it's released
@param extended: {boolean} extended scancode like ctr or win button
"""
if not self._isReady:
return
@@ -202,11 +314,12 @@ class RDPClientController(pdu.layer.PDUClientListener):
try:
event = pdu.data.ScancodeKeyEvent()
event.keyCode.value = code
if isPressed:
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_DOWN
else:
if not isPressed:
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE
if extended:
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED
#send event
self._pduLayer.sendInputEvents([event])
@@ -215,7 +328,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def sendKeyEventUnicode(self, code, isPressed):
"""
Send a scan code to RDP stack
@summary: Send a scan code to RDP stack
@param code: unicode
@param isPressed: True if key is pressed and false if it's released
"""
@@ -236,7 +349,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def sendRefreshOrder(self, left, top, right, bottom):
"""
Force server to resend a particular zone
@summary: Force server to resend a particular zone
@param left: left coordinate
@param top: top coordinate
@param right: right coordinate
@@ -253,15 +366,16 @@ class RDPClientController(pdu.layer.PDUClientListener):
def close(self):
"""
Close protocol stack
@summary: Close protocol stack
"""
self._pduLayer.close()
class RDPServerController(pdu.layer.PDUServerListener):
class RDPServerController(PDUServerListener):
"""
Controller use in server side mode
@summary: Controller use in server side mode
"""
def __init__(self, privateKeyFileName, certificateFileName, colorDepth):
def __init__(self, colorDepth, privateKeyFileName = None, certificateFileName = None):
"""
@param privateKeyFileName: file contain server private key
@param certficiateFileName: file that contain public key
@@ -272,18 +386,24 @@ class RDPServerController(pdu.layer.PDUServerListener):
self._serverObserver = []
#build RDP protocol stack
self._pduLayer = pdu.layer.Server(self)
#secure layer
self._secLayer = sec.Server(self._pduLayer)
#multi channel service
self._mcsLayer = mcs.Server(self._pduLayer)
self._mcsLayer = mcs.Server(self._secLayer)
#transport pdu layer
self._x224Layer = x224.Server(self._mcsLayer, privateKeyFileName, certificateFileName)
self._x224Layer = x224.Server(self._mcsLayer, privateKeyFileName, certificateFileName, False)
#transport packet (protocol layer)
self._tpktLayer = tpkt.TPKT(self._x224Layer, self._pduLayer)
self._tpktLayer = tpkt.TPKT(self._x224Layer)
#fastpath stack
self._pduLayer.initFastPath(self._secLayer)
self._secLayer.initFastPath(self._tpktLayer)
#set color depth of session
self.setColorDepth(colorDepth)
def close(self):
"""
Close protocol stack
@summary: Close protocol stack
"""
self._pduLayer.close()
@@ -294,30 +414,36 @@ class RDPServerController(pdu.layer.PDUServerListener):
"""
return self._tpktLayer
def getHostname(self):
"""
@return: name of client (information done by RDP)
"""
return self._mcsLayer._clientSettings.CS_CORE.clientName.value.strip('\x00')
def getUsername(self):
"""
Must be call after on ready event else always empty string
@summary: Must be call after on ready event else always empty string
@return: username send by client may be an empty string
"""
return self._pduLayer._info.userName.value
return self._secLayer._info.userName.value
def getPassword(self):
"""
Must be call after on ready event else always empty string
@summary: Must be call after on ready event else always empty string
@return: password send by client may be an empty string
"""
return self._pduLayer._info.password.value
return self._secLayer._info.password.value
def getDomain(self):
"""
Must be call after on ready event else always empty string
@summary: Must be call after on ready event else always empty string
@return: domain send by client may be an empty string
"""
return self._pduLayer._info.domain.value
return self._secLayer._info.domain.value
def getCredentials(self):
"""
Must be call after on ready event else always empty string
@summary: Must be call after on ready event else always empty string
@return: tuple(domain, username, password)
"""
return (self.getDomain(), self.getUsername(), self.getPassword())
@@ -337,16 +463,17 @@ class RDPServerController(pdu.layer.PDUServerListener):
def addServerObserver(self, observer):
"""
Add observer to RDP protocol
@summary: Add observer to RDP protocol
@param observer: new observer to add
"""
self._serverObserver.append(observer)
def setColorDepth(self, colorDepth):
"""
Set color depth of session
if PDU stack is already connected send a deactive-reactive sequence
@param colorDepth: depth of session (15, 16, 24)
@summary: Set color depth of session
if PDU stack is already connected send a deactive-reactive sequence
and an onReady message is re send when client is ready
@param colorDepth: {integer} depth of session (15, 16, 24)
"""
self._colorDepth = colorDepth
self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_BITMAP].capability.preferredBitsPerPixel.value = colorDepth
@@ -357,13 +484,13 @@ class RDPServerController(pdu.layer.PDUServerListener):
def setKeyEventUnicodeSupport(self):
"""
Enable key event in unicode format
@summary: Enable key event in unicode format
"""
self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_INPUT].capability.inputFlags.value |= pdu.caps.InputFlags.INPUT_FLAG_UNICODE
def onReady(self):
"""
RDP stack is now ready
@summary: RDP stack is now ready
"""
self._isReady = True
for observer in self._serverObserver:
@@ -371,7 +498,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
def onClose(self):
"""
Event call when RDP stack is closed
@summary: Event call when RDP stack is closed
"""
self._isReady = False
for observer in self._serverObserver:
@@ -379,18 +506,18 @@ class RDPServerController(pdu.layer.PDUServerListener):
def onSlowPathInput(self, slowPathInputEvents):
"""
Event call when slow path input are available
@summary: Event call when slow path input are available
@param slowPathInputEvents: [data.SlowPathInputEvent]
"""
for observer in self._serverObserver:
for event in slowPathInputEvents:
#scan code
if event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_SCANCODE:
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE), bool(event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED))
#unicode
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_UNICODE:
observer.onKeyEventUnicode(event.slowPathInputData.unicode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
#mouse event
#mouse events
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSE:
isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_DOWN
button = 0
@@ -401,10 +528,19 @@ class RDPServerController(pdu.layer.PDUServerListener):
elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_BUTTON3:
button = 3
observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed)
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSEX:
isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_DOWN
button = 0
if event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_BUTTON1:
button = 4
elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_BUTTON2:
button = 5
observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed)
def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
send bitmap update
@summary: send bitmap update
@param destLeft: xmin position
@param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding
@@ -426,12 +562,15 @@ class RDPServerController(pdu.layer.PDUServerListener):
class ClientFactory(layer.RawLayerClientFactory):
"""
@summary: Factory of Client RDP protocol
@param reason: twisted reason
"""
def connectionLost(self, tpktLayer):
def connectionLost(self, csspLayer, reason):
#retrieve controller
tpktLayer = csspLayer._layer
x224Layer = tpktLayer._presentation
mcsLayer = x224Layer._presentation
pduLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
pduLayer = secLayer._presentation
controller = pduLayer._listener
controller.onClose()
@@ -452,48 +591,53 @@ class ClientFactory(layer.RawLayerClientFactory):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildObserver", "ClientFactory"))
class ServerFactory(layer.RawLayerServerFactory):
"""
Factory of Server RDP protocol
"""
def __init__(self, privateKeyFileName, certificateFileName, colorDepth):
"""
@param privateKeyFileName: file contain server private key
@param certficiateFileName: file that contain public key
@param colorDepth: color depth of session
"""
self._privateKeyFileName = privateKeyFileName
self._certificateFileName = certificateFileName
self._colorDepth = colorDepth
def connectionLost(self, tpktLayer):
#retrieve controller
x224Layer = tpktLayer._presentation
mcsLayer = x224Layer._presentation
pduLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
controller = pduLayer._listener
controller.onClose()
def buildRawLayer(self, addr):
"""
Function call from twisted and build rdp protocol stack
@param addr: destination address
"""
controller = RDPServerController(self._privateKeyFileName, self._certificateFileName, self._colorDepth)
self.buildObserver(controller, addr)
return controller.getProtocol()
def buildObserver(self, controller, addr):
"""
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 use to inform all RDP event handle by RDPY
@summary: Class use to inform all RDP event handle by RDPY
"""
def __init__(self, controller):
"""
@@ -504,19 +648,25 @@ class RDPClientObserver(object):
def onReady(self):
"""
Stack is ready and connected
@summary: Stack is ready and connected
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver"))
def onSessionReady(self):
"""
@summary: Windows session is ready
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "RDPClientObserver"))
def onClose(self):
"""
Stack is closes
@summary: Stack is closes
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
Notify bitmap update
@summary: Notify bitmap update
@param destLeft: xmin position
@param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding
@@ -531,7 +681,7 @@ class RDPClientObserver(object):
class RDPServerObserver(object):
"""
Class use to inform all RDP event handle by RDPY
@summary: Class use to inform all RDP event handle by RDPY
"""
def __init__(self, controller):
"""
@@ -542,28 +692,29 @@ class RDPServerObserver(object):
def onReady(self):
"""
Stack is ready and connected
@summary: Stack is ready and connected
May be called after an setColorDepth too
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPServerObserver"))
def onClose(self):
"""
Stack is closes
@summary: Stack is closes
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
def onKeyEventScancode(self, code, isPressed):
def onKeyEventScancode(self, code, isPressed, isExtended):
"""
Event call when a keyboard event is catch in scan code format
@param code: scan code of key
@param isPressed: True if key is down
@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
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventScanCode", "RDPServerObserver"))
def onKeyEventUnicode(self, code, isPressed):
"""
Event call when a keyboard event is catch in unicode format
@summary: Event call when a keyboard event is catch in unicode format
@param code: unicode of key
@param isPressed: True if key is down
"""
@@ -571,10 +722,10 @@ class RDPServerObserver(object):
def onPointerEvent(self, x, y, button, isPressed):
"""
Event call on mouse event
@summary: Event call on mouse event
@param x: x position
@param y: y position
@param button: 1, 2 or 3 button
@param button: 1, 2, 3, 4 or 5 button
@param isPressed: True if mouse button is pressed
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onPointerEvent", "RDPServerObserver"))

733
rdpy/core/sec.py Normal file
View File

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

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -22,22 +22,25 @@ Basic Encoding Rules use in RDP.
ASN.1 standard
"""
from rdpy.network.type import UInt8, UInt16Be, UInt32Be, String
from rdpy.base.error import InvalidExpectedDataException, InvalidSize
from rdpy.model.message import UInt8, UInt16Be, UInt32Be, Buffer
from rdpy.model.error import InvalidExpectedDataException, InvalidSize
class BerPc(object):
class BerPc:
BER_PC_MASK = 0x20
BER_PRIMITIVE = 0x00
BER_CONSTRUCT = 0x20
class Class(object):
class Class:
BER_CLASS_MASK = 0xC0
BER_CLASS_UNIV = 0x00
BER_CLASS_APPL = 0x40
BER_CLASS_CTXT = 0x80
BER_CLASS_PRIV = 0xC0
class Tag(object):
class Tag:
BER_TAG_MASK = 0x1F
BER_TAG_BOOLEAN = 0x01
BER_TAG_INTEGER = 0x02
@@ -48,9 +51,10 @@ class Tag(object):
BER_TAG_SEQUENCE = 0x10
BER_TAG_SEQUENCE_OF = 0x10
def berPC(pc):
"""
Return BER_CONSTRUCT if true
@summary: Return BER_CONSTRUCT if true
BER_PRIMITIVE if false
@param pc: boolean
@return: BerPc value
@@ -60,16 +64,17 @@ def berPC(pc):
else:
return BerPc.BER_PRIMITIVE
def readLength(s):
"""
Read length of BER structure
@summary: Read length of BER structure
length be on 1 2 or 3 bytes
@param s: stream
@return: int or Python long
"""
size = None
length = UInt8()
s.readType(length)
s.read_type(length)
byte = length.value
if byte & 0x80:
byte &= ~0x80
@@ -79,14 +84,15 @@ def readLength(s):
size = UInt16Be()
else:
raise InvalidExpectedDataException("BER length may be 1 or 2")
s.readType(size)
s.read_type(size)
else:
size = length
return size.value
def writeLength(size):
"""
Return structure length as expected in BER specification
@summary: Return structure length as expected in BER specification
@param size: int or python long
@return: UInt8 or (UInt8(0x82), UInt16Be)
"""
@@ -95,39 +101,42 @@ def writeLength(size):
else:
return UInt8(size)
def readUniversalTag(s, tag, pc):
"""
Read tag of BER packet
@summary: Read tag of BER packet
@param tag: Tag class attributes
@param pc: boolean
@return: true if tag is correctly read
"""
byte = UInt8()
s.readType(byte)
s.read_type(byte)
return byte.value == ((Class.BER_CLASS_UNIV | berPC(pc)) | (Tag.BER_TAG_MASK & tag))
def writeUniversalTag(tag, pc):
"""
Return universal tag byte
@summary: Return universal tag byte
@param tag: tag class attributes
@param pc: boolean
@return: UInt8
"""
return UInt8((Class.BER_CLASS_UNIV | berPC(pc)) | (Tag.BER_TAG_MASK & tag))
def readApplicationTag(s, tag):
"""
Read application tag
@summary: Read application tag
@param s: stream
@param tag: tag class attributes
@return: length of application packet
"""
byte = UInt8()
s.readType(byte)
s.read_type(byte)
if tag.value > 30:
if byte.value != ((Class.BER_CLASS_APPL | BerPc.BER_CONSTRUCT) | Tag.BER_TAG_MASK):
raise InvalidExpectedDataException()
s.readType(byte)
s.read_type(byte)
if byte.value != tag.value:
raise InvalidExpectedDataException("bad tag")
else:
@@ -136,9 +145,10 @@ def readApplicationTag(s, tag):
return readLength(s)
def writeApplicationTag(tag, size):
"""
Return structure that represent BER application tag
@summary: Return structure that represent BER application tag
@param tag: int python that match an uint8(0xff)
@param size: size to rest of packet
"""
@@ -149,7 +159,7 @@ def writeApplicationTag(tag, size):
def readBoolean(s):
"""
Return boolean
@summary: Return boolean
@param s: stream
@return: boolean
"""
@@ -159,12 +169,12 @@ def readBoolean(s):
if size != 1:
raise InvalidExpectedDataException("bad boolean size")
b = UInt8()
s.readType(b)
s.read_type(b)
return bool(b.value)
def writeBoolean(b):
"""
Return structure that represent boolean in BER specification
@summary: Return structure that represent boolean in BER specification
@param b: boolean
@return: BER boolean block
"""
@@ -175,7 +185,7 @@ def writeBoolean(b):
def readInteger(s):
"""
Read integer structure from stream
@summary: Read integer structure from stream
@param s: stream
@return: int or long python
"""
@@ -186,28 +196,28 @@ def readInteger(s):
if size == 1:
integer = UInt8()
s.readType(integer)
s.read_type(integer)
return integer.value
elif size == 2:
integer = UInt16Be()
s.readType(integer)
s.read_type(integer)
return integer.value
elif size == 3:
integer1 = UInt8()
integer2 = UInt16Be()
s.readType(integer1)
s.readType(integer2)
s.read_type(integer1)
s.read_type(integer2)
return integer2.value + (integer1.value << 16)
elif size == 4:
integer = UInt32Be()
s.readType(integer)
s.read_type(integer)
return integer.value
else:
raise InvalidExpectedDataException("Wrong integer size")
def writeInteger(value):
"""
Write integer value
@summary: Write integer value
@param param: INT or Python long
@return: BER integer block
"""
@@ -220,7 +230,7 @@ def writeInteger(value):
def readOctetString(s):
"""
Read BER string structure
@summary: Read BER string structure
@param s: stream
@return: string python
"""
@@ -231,15 +241,15 @@ def readOctetString(s):
def writeOctetstring(value):
"""
Write string in BER representation
@summary: Write string in BER representation
@param value: string
@return: BER octet string block
"""
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):
"""
Read enumerated structure
@summary: Read enumerated structure
@param s: Stream
@return: int or long
"""
@@ -248,12 +258,12 @@ def readEnumerated(s):
if readLength(s) != 1:
raise InvalidSize("enumerate size is wrong")
enumer = UInt8()
s.readType(enumer)
s.read_type(enumer)
return enumer.value
def writeEnumerated(enumerated):
"""
Write enumerated structure
@summary: Write enumerated structure
@param s: Stream
@return: BER enumerated block
"""

598
rdpy/core/t125/gcc.py Normal file
View File

@@ -0,0 +1,598 @@
#
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Implement GCC structure use in RDP protocol
http://msdn.microsoft.com/en-us/library/cc240508.aspx
"""
from hashlib import md5
from rdpy.model.message import UInt8, UInt16Le, UInt32Le, CompositeType, Buffer, Stream, sizeof, FactoryType, ArrayType
from rdpy.core.t125 import per, mcs
from rdpy.model.error import InvalidExpectedDataException
from rdpy.model import log
from rdpy.security import x509
import rdpy.security.rsa_wrapper as rsa
t124_02_98_oid = ( 0, 0, 20, 124, 0, 1 )
h221_cs_key = b"Duca";
h221_sc_key = b"McDn";
class MessageType(object):
"""
@summary: Server to Client block
GCC conference messages
@see: http://msdn.microsoft.com/en-us/library/cc240509.aspx
"""
#server -> client
SC_CORE = 0x0C01
SC_SECURITY = 0x0C02
SC_NET = 0x0C03
#client -> server
CS_CORE = 0xC001
CS_SECURITY = 0xC002
CS_NET = 0xC003
CS_CLUSTER = 0xC004
CS_MONITOR = 0xC005
class ColorDepth(object):
"""
@summary: Depth color
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
RNS_UD_COLOR_8BPP = 0xCA01
RNS_UD_COLOR_16BPP_555 = 0xCA02
RNS_UD_COLOR_16BPP_565 = 0xCA03
RNS_UD_COLOR_24BPP = 0xCA04
class HighColor(object):
"""
@summary: High color of client
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
HIGH_COLOR_4BPP = 0x0004
HIGH_COLOR_8BPP = 0x0008
HIGH_COLOR_15BPP = 0x000f
HIGH_COLOR_16BPP = 0x0010
HIGH_COLOR_24BPP = 0x0018
class Support(object):
"""
@summary: Supported depth flag
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
RNS_UD_24BPP_SUPPORT = 0x0001
RNS_UD_16BPP_SUPPORT = 0x0002
RNS_UD_15BPP_SUPPORT = 0x0004
RNS_UD_32BPP_SUPPORT = 0x0008
class CapabilityFlags(object):
"""
@summary: For more details on each flags click above
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
RNS_UD_CS_SUPPORT_ERRINFO_PDU = 0x0001
RNS_UD_CS_WANT_32BPP_SESSION = 0x0002
RNS_UD_CS_SUPPORT_STATUSINFO_PDU = 0x0004
RNS_UD_CS_STRONG_ASYMMETRIC_KEYS = 0x0008
RNS_UD_CS_UNUSED = 0x0010
RNS_UD_CS_VALID_CONNECTION_TYPE = 0x0020
RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU = 0x0040
RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT = 0x0080
RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL = 0x0100
RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE = 0x0200
RNS_UD_CS_SUPPORT_HEARTBEAT_PDU = 0x0400
class ConnectionType(object):
"""
@summary: This information is correct if
RNS_UD_CS_VALID_CONNECTION_TYPE flag is set on capabilityFlag
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
CONNECTION_TYPE_MODEM = 0x01
CONNECTION_TYPE_BROADBAND_LOW = 0x02
CONNECTION_TYPE_SATELLITE = 0x03
CONNECTION_TYPE_BROADBAND_HIGH = 0x04
CONNECTION_TYPE_WAN = 0x05
CONNECTION_TYPE_LAN = 0x06
CONNECTION_TYPE_AUTODETECT = 0x07
class Version(object):
"""
@summary: Supported version of RDP
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
RDP_VERSION_4 = 0x00080001
RDP_VERSION_5_PLUS = 0x00080004
class Sequence(object):
RNS_UD_SAS_DEL = 0xAA03
class EncryptionMethod(object):
"""
@summary: Encryption methods supported
@see: http://msdn.microsoft.com/en-us/library/cc240511.aspx
"""
ENCRYPTION_FLAG_40BIT = 0x00000001
ENCRYPTION_FLAG_128BIT = 0x00000002
ENCRYPTION_FLAG_56BIT = 0x00000008
FIPS_ENCRYPTION_FLAG = 0x00000010
class EncryptionLevel(object):
"""
@summary: level of 'security'
@see: http://msdn.microsoft.com/en-us/library/cc240518.aspx
"""
ENCRYPTION_LEVEL_NONE = 0x00000000
ENCRYPTION_LEVEL_LOW = 0x00000001
ENCRYPTION_LEVEL_CLIENT_COMPATIBLE = 0x00000002
ENCRYPTION_LEVEL_HIGH = 0x00000003
ENCRYPTION_LEVEL_FIPS = 0x00000004
class ChannelOptions(object):
"""
@summary: Channel options
@see: http://msdn.microsoft.com/en-us/library/cc240513.aspx
"""
CHANNEL_OPTION_INITIALIZED = 0x80000000
CHANNEL_OPTION_ENCRYPT_RDP = 0x40000000
CHANNEL_OPTION_ENCRYPT_SC = 0x20000000
CHANNEL_OPTION_ENCRYPT_CS = 0x10000000
CHANNEL_OPTION_PRI_HIGH = 0x08000000
CHANNEL_OPTION_PRI_MED = 0x04000000
CHANNEL_OPTION_PRI_LOW = 0x02000000
CHANNEL_OPTION_COMPRESS_RDP = 0x00800000
CHANNEL_OPTION_COMPRESS = 0x00400000
CHANNEL_OPTION_SHOW_PROTOCOL = 0x00200000
REMOTE_CONTROL_PERSISTENT = 0x00100000
class KeyboardType(object):
"""
@summary: Keyboard type
@see: IBM_101_102_KEYS is the most common keyboard type
"""
IBM_PC_XT_83_KEY = 0x00000001
OLIVETTI = 0x00000002
IBM_PC_AT_84_KEY = 0x00000003
IBM_101_102_KEYS = 0x00000004
NOKIA_1050 = 0x00000005
NOKIA_9140 = 0x00000006
JAPANESE = 0x00000007
class KeyboardLayout(object):
"""
@summary: Keyboard layout definition
@see: http://technet.microsoft.com/en-us/library/cc766503%28WS.10%29.aspx
"""
ARABIC = 0x00000401
BULGARIAN = 0x00000402
CHINESE_US_KEYBOARD = 0x00000404
CZECH = 0x00000405
DANISH = 0x00000406
GERMAN = 0x00000407
GREEK = 0x00000408
US = 0x00000409
SPANISH = 0x0000040a
FINNISH = 0x0000040b
FRENCH = 0x0000040c
HEBREW = 0x0000040d
HUNGARIAN = 0x0000040e
ICELANDIC = 0x0000040f
ITALIAN = 0x00000410
JAPANESE = 0x00000411
KOREAN = 0x00000412
DUTCH = 0x00000413
NORWEGIAN = 0x00000414
class CertificateType(object):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240521.aspx
"""
CERT_CHAIN_VERSION_1 = 0x00000001
CERT_CHAIN_VERSION_2 = 0x00000002
class DataBlock(CompositeType):
"""
@summary: Block settings
"""
def __init__(self, data_block=None):
super().__init__()
self.type = UInt16Le(lambda:data_block._TYPE_)
self.length = UInt16Le(lambda: sizeof(self))
def factory():
"""
"""
gcc_type = [
ClientCoreData, ClientSecurityData, ClientNetworkData,
ServerCoreData, ServerNetworkData, ServerSecurityData
]
for c in gcc_type:
if self.type.value == c._TYPE_:
return c(read_len=lambda: (self.length.value - 4))
log.debug("unknown GCC block type : %s"%hex(self.type.value))
# read entire packet
return Buffer(read_len=lambda: (self.length.value - 4))
if data_block is None:
data_block = FactoryType(factory)
elif "_TYPE_" not in data_block.__class__.__dict__:
raise InvalidExpectedDataException("Try to send an invalid GCC blocks")
self.dataBlock = data_block
class ClientCoreData(CompositeType):
"""
"""
_TYPE_ = MessageType.CS_CORE
def __init__(self, read_len=None):
super().__init__(read_len=read_len)
self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS)
self.desktopWidth = UInt16Le(1280)
self.desktopHeight = UInt16Le(800)
self.colorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP)
self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL)
self.kbdLayout = UInt32Le(KeyboardLayout.US)
self.clientBuild = UInt32Le(3790)
self.clientName = Buffer(("rdpy" + "\x00" * 12).encode("utf-16le"), read_len=lambda: 32)
self.keyboardType = UInt32Le(KeyboardType.IBM_101_102_KEYS)
self.keyboardSubType = UInt32Le(0)
self.keyboardFnKeys = UInt32Le(12)
self.imeFileName = Buffer(b"\x00" * 64, read_len=lambda: 64, optional=True)
self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP, optional=True)
self.clientProductId = UInt16Le(1, optional=True)
self.serialNumber = UInt32Le(0, optional=True)
self.highColorDepth = UInt16Le(HighColor.HIGH_COLOR_24BPP, optional=True)
self.supportedColorDepths = UInt16Le(Support.RNS_UD_15BPP_SUPPORT | Support.RNS_UD_16BPP_SUPPORT | Support.RNS_UD_24BPP_SUPPORT | Support.RNS_UD_32BPP_SUPPORT, optional=True)
self.earlyCapabilityFlags = UInt16Le(CapabilityFlags.RNS_UD_CS_SUPPORT_ERRINFO_PDU, optional=True)
self.clientDigProductId = Buffer(b"\x00" * 64, read_len=lambda: 64, optional=True)
self.connectionType = UInt8(optional=True)
self.pad1octet = UInt8(optional=True)
self.serverSelectedProtocol = UInt32Le(optional=True)
class ServerCoreData(CompositeType):
"""
"""
_TYPE_ = MessageType.SC_CORE
def __init__(self, read_len=None):
super().__init__(read_len=read_len)
self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS)
self.clientRequestedProtocol = UInt32Le(optional=True)
self.earlyCapabilityFlags = UInt32Le(optional=True)
class ClientSecurityData(CompositeType):
"""
"""
_TYPE_ = MessageType.CS_SECURITY
def __init__(self, read_len=None):
super().__init__(read_len=read_len)
self.encryptionMethods = UInt32Le(EncryptionMethod.ENCRYPTION_FLAG_40BIT | EncryptionMethod.ENCRYPTION_FLAG_56BIT | EncryptionMethod.ENCRYPTION_FLAG_128BIT)
self.extEncryptionMethods = UInt32Le()
class ServerSecurityData(CompositeType):
"""
"""
_TYPE_ = MessageType.SC_SECURITY
def __init__(self, read_len=None):
super().__init__(read_len=read_len)
self.encryptionMethod = UInt32Le()
self.encryptionLevel = UInt32Le()
self.serverRandomLen = UInt32Le(0x00000020, constant=True, conditional=lambda: not(self.encryptionMethod.value == 0 and self.encryptionLevel.value == 0))
self.serverCertLen = UInt32Le(lambda: sizeof(self.serverCertificate), conditional=lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel.value == 0))
self.serverRandom = Buffer(read_len=lambda: self.serverRandomLen.value, conditional=lambda: not(self.encryptionMethod.value == 0 and self.encryptionLevel.value == 0))
self.serverCertificate = ServerCertificate(read_len=lambda: self.serverCertLen.value, conditional=lambda: not(self.encryptionMethod.value == 0 and self.encryptionLevel.value == 0))
class ServerCertificate(CompositeType):
"""
@summary: Server certificate structure
@see: http://msdn.microsoft.com/en-us/library/cc240521.aspx
"""
def __init__(self, certData = None, read_len = None, conditional = lambda:True):
CompositeType.__init__(self, read_len=read_len, conditional = conditional)
self.dwVersion = UInt32Le(lambda:(self.certData.__class__._TYPE_))
def CertificateFactory():
"""
Closure for capability factory
"""
for c in [ProprietaryServerCertificate, X509CertificateChain]:
if self.dwVersion.value & 0x7fffffff == c._TYPE_:
return c()
raise InvalidExpectedDataException("unknown certificate type : %s "%hex(self.dwVersion.value))
if certData is None:
certData = FactoryType(CertificateFactory)
elif not "_TYPE_" in certData.__class__.__dict__:
raise InvalidExpectedDataException("Try to send an invalid Certificate")
self.certData = certData
class ProprietaryServerCertificate(CompositeType):
"""
@summary: microsoft proprietary certificate
@see: http://msdn.microsoft.com/en-us/library/cc240519.aspx
"""
_TYPE_ = CertificateType.CERT_CHAIN_VERSION_1
#http://msdn.microsoft.com/en-us/library/cc240776.aspx
_TERMINAL_SERVICES_MODULUS_ = "\x3d\x3a\x5e\xbd\x72\x43\x3e\xc9\x4d\xbb\xc1\x1e\x4a\xba\x5f\xcb\x3e\x88\x20\x87\xef\xf5\xc1\xe2\xd7\xb7\x6b\x9a\xf2\x52\x45\x95\xce\x63\x65\x6b\x58\x3a\xfe\xef\x7c\xe7\xbf\xfe\x3d\xf6\x5c\x7d\x6c\x5e\x06\x09\x1a\xf5\x61\xbb\x20\x93\x09\x5f\x05\x6d\xea\x87"
_TERMINAL_SERVICES_PRIVATE_EXPONENT_ = "\x87\xa7\x19\x32\xda\x11\x87\x55\x58\x00\x16\x16\x25\x65\x68\xf8\x24\x3e\xe6\xfa\xe9\x67\x49\x94\xcf\x92\xcc\x33\x99\xe8\x08\x60\x17\x9a\x12\x9f\x24\xdd\xb1\x24\x99\xc7\x3a\xb8\x0a\x7b\x0d\xdd\x35\x07\x79\x17\x0b\x51\x9b\xb3\xc7\x10\x01\x13\xe7\x3f\xf3\x5f"
_TERMINAL_SERVICES_PUBLIC_EXPONENT_ = "\x5b\x7b\x88\xc0"
def __init__(self):
CompositeType.__init__(self)
self.dwSigAlgId = UInt32Le(0x00000001, constant = True)
self.dwKeyAlgId = UInt32Le(0x00000001, constant = True)
self.wPublicKeyBlobType = UInt16Le(0x0006, constant = True)
self.wPublicKeyBlobLen = UInt16Le(lambda:sizeof(self.PublicKeyBlob))
self.PublicKeyBlob = RSAPublicKey(readLen = self.wPublicKeyBlobLen)
self.wSignatureBlobType = UInt16Le(0x0008, constant = True)
self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) + sizeof(self.padding)))
self.SignatureBlob = Buffer(readLen = CallableValue(lambda:(self.wSignatureBlobLen.value - sizeof(self.padding))))
self.padding = Buffer(b"\x00" * 8, readLen = CallableValue(8))
def getPublicKey(self):
"""
@return: {Tuple} (modulus, publicExponent)
"""
log.debug("read RSA public key from proprietary certificate")
#reverse because bignum in little endian
return rsa.PublicKey(self.PublicKeyBlob.pubExp.value, self.PublicKeyBlob.modulus.value[::-1])
def computeSignatureHash(self):
"""
@summary: compute hash
"""
s = Stream()
s.write_type(UInt32Le(self.__class__._TYPE_))
s.write_type(self.dwSigAlgId)
s.write_type(self.dwKeyAlgId)
s.write_type(self.wPublicKeyBlobType)
s.write_type(self.wPublicKeyBlobLen)
s.write_type(self.PublicKeyBlob)
md5Digest = md5.new()
md5Digest.update(s.getvalue())
return md5Digest.digest() + "\x00" + "\xff" * 45 + "\x01"
def sign(self):
"""
@summary: sign proprietary certificate
@see: http://msdn.microsoft.com/en-us/library/cc240778.aspx
"""
self.SignatureBlob.value = rsa.sign(self.computeSignatureHash()[::-1], rsa.PrivateKey(d = ProprietaryServerCertificate._TERMINAL_SERVICES_PRIVATE_EXPONENT_[::-1], n = ProprietaryServerCertificate._TERMINAL_SERVICES_MODULUS_[::-1]))[::-1]
def verify(self):
"""
@summary: verify certificate signature
"""
return rsa.verify(self.SignatureBlob.value[::-1], rsa.PublicKey(e = ProprietaryServerCertificate._TERMINAL_SERVICES_PUBLIC_EXPONENT_[::-1], n = ProprietaryServerCertificate._TERMINAL_SERVICES_MODULUS_[::-1]))[::-1] == self.computeSignatureHash()
class CertBlob(CompositeType):
"""
@summary: certificate blob, contain x509 data
@see: http://msdn.microsoft.com/en-us/library/cc241911.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.cbCert = UInt32Le(lambda:sizeof(self.abCert))
self.abCert = Buffer(readLen = self.cbCert)
class X509CertificateChain(CompositeType):
"""
@summary: X509 certificate chain
@see: http://msdn.microsoft.com/en-us/library/cc241910.aspx
"""
_TYPE_ = CertificateType.CERT_CHAIN_VERSION_2
def __init__(self):
CompositeType.__init__(self)
self.NumCertBlobs = UInt32Le()
self.CertBlobArray = ArrayType(CertBlob, readLen = self.NumCertBlobs)
self.padding = Buffer(readLen = CallableValue(lambda:(8 + 4 * self.NumCertBlobs.value)))
def getPublicKey(self):
"""
@return: {Tuple} (modulus, publicExponent)
"""
log.debug("read RSA public key from x509 certificate")
#last certifcate contain public key
n, e = x509.extractRSAKey(x509.load(self.CertBlobArray[-1].abCert.value))
return rsa.PublicKey(e, n)
def verify(self):
"""
@todo: verify x509 signature
"""
return True
class RSAPublicKey(CompositeType):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240520.aspx
"""
def __init__(self, readLen):
CompositeType.__init__(self, readLen = readLen)
#magic is RSA1(0x31415352)
self.magic = UInt32Le(0x31415352, constant = True)
self.keylen = UInt32Le(lambda:(sizeof(self.modulus) + sizeof(self.padding)))
self.bitlen = UInt32Le(lambda:((self.keylen.value - 8) * 8))
self.datalen = UInt32Le(lambda:((self.bitlen.value / 8) - 1))
self.pubExp = UInt32Le()
self.modulus = Buffer(readLen = CallableValue(lambda:(self.keylen.value - 8)))
self.padding = Buffer(b"\x00" * 8, readLen = CallableValue(8))
class ChannelDef(CompositeType):
"""
"""
def __init__(self, name=b""):
super().__init__()
# name of channel
self.name = Buffer(name[0:8] + b"\x00" * (8 - len(name)), read_len=lambda: 8)
# unknown
self.options = UInt32Le()
class ClientNetworkData(CompositeType):
"""
"""
_TYPE_ = MessageType.CS_NET
def __init__(self, read_len=None):
CompositeType.__init__(self, read_len=read_len)
self.channelCount = UInt32Le(lambda: len(self.channelDefArray))
self.channelDefArray = ArrayType(ChannelDef, read_len=lambda: self.channelCount.value)
class ServerNetworkData(CompositeType):
"""
"""
_TYPE_ = MessageType.SC_NET
def __init__(self, read_len=None):
super().__init__(read_len=read_len)
self.MCSChannelId = UInt16Le(mcs.Channel.MCS_GLOBAL_CHANNEL)
self.channelCount = UInt16Le(lambda: len(self.channelIdArray))
self.channelIdArray = ArrayType(UInt16Le, read_len=lambda: self.channelCount.value)
self.pad = UInt16Le(conditional=lambda: ((self.channelCount.value % 2) == 1))
class Settings(CompositeType):
"""
"""
def __init__(self, init=None, read_len=None):
super().__init__(read_len=read_len)
self.settings = ArrayType(DataBlock, [DataBlock(i) for i in init or []])
def get_block(self, message_type):
"""
"""
for i in self.settings._array:
if i.type.value == message_type:
return i.dataBlock
return None
def client_settings():
"""
"""
return Settings([ClientCoreData(), ClientNetworkData(), ClientSecurityData()])
def serverSettings():
"""
@summary: Build settings for server
@return Settings
"""
return Settings([ServerCoreData(), ServerSecurityData(), ServerNetworkData()])
def readConferenceCreateRequest(s):
"""
@summary: Read a response from client
GCC create request
@param s: Stream
@param client settings (Settings)
"""
per.readChoice(s)
per.readObjectIdentifier(s, t124_02_98_oid)
per.readLength(s)
per.readChoice(s)
per.readSelection(s)
per.readNumericString(s, 1)
per.readPadding(s, 1)
if per.readNumberOfSet(s) != 1:
raise InvalidExpectedDataException("Invalid number of set in readConferenceCreateRequest")
if per.readChoice(s) != 0xc0:
raise InvalidExpectedDataException("Invalid choice in readConferenceCreateRequest")
per.readOctetStream(s, h221_cs_key, 4)
length = per.readLength(s)
clientSettings = Settings(readLen = CallableValue(length))
s.read_type(clientSettings)
return clientSettings
def readConferenceCreateResponse(s):
"""
@summary: Read response from server
and return server settings read from this response
@param s: Stream
@return: ServerSettings
"""
per.readChoice(s)
per.readObjectIdentifier(s, t124_02_98_oid)
per.readLength(s)
per.readChoice(s)
per.readInteger16(s, 1001)
per.readInteger(s)
per.readEnumerates(s)
per.readNumberOfSet(s)
per.readChoice(s)
if not per.readOctetStream(s, h221_sc_key, 4):
raise InvalidExpectedDataException("cannot read h221_sc_key")
length = per.readLength(s)
server_settings = Settings(read_len=lambda: length)
s.read_type(server_settings)
return server_settings
def writeConferenceCreateRequest(userData):
"""
@summary: Write conference create request structure
@param userData: Settings for client
@return: GCC packet
"""
userDataStream = Stream()
userDataStream.write_type(userData)
return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
per.writeLength(len(userDataStream.getvalue()) + 14), per.writeChoice(0),
per.writeSelection(0x08), per.writeNumericString(b"1", 1), per.writePadding(1),
per.writeNumberOfSet(1), per.writeChoice(0xc0),
per.writeOctetStream(h221_cs_key, 4), per.writeOctetStream(userDataStream.getvalue()))
def writeConferenceCreateResponse(serverData):
"""
@summary: Write a conference create response packet
@param serverData: Settings for server
@return: gcc packet
"""
serverDataStream = Stream()
serverDataStream.write_type(serverData)
return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
per.writeLength(len(serverDataStream.getvalue()) + 14), per.writeChoice(0x14),
per.writeInteger16(0x79F3, 1001), per.writeInteger(1), per.writeEnumerates(0),
per.writeNumberOfSet(1), per.writeChoice(0xc0),
per.writeOctetStream(h221_sc_key, 4), per.writeOctetStream(serverDataStream.getvalue()))

767
rdpy/core/t125/mcs.py Normal file
View File

@@ -0,0 +1,767 @@
#
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Implement Multi-Channel Service
Each channel have a particular role.
The main channel is the graphical channel.
It exist channel for file system order, audio channel, clipboard etc...
"""
from typing import Tuple
from rdpy.core import x224
from rdpy.model.layer import LayerAutomata, IStreamSender, Layer
from rdpy.model.message import sizeof, Stream, UInt8, UInt16Le, Buffer
from rdpy.model.error import InvalidExpectedDataException, InvalidValue, InvalidSize, CallPureVirtualFuntion
from rdpy.core.t125.ber import writeLength
import rdpy.model.log as log
from rdpy.core.t125 import ber, gcc, per
import rdpy.security.rsa_wrapper as rsa
class Message:
"""
"""
MCS_TYPE_CONNECT_INITIAL = 0x65
MCS_TYPE_CONNECT_RESPONSE = 0x66
class DomainMCSPDU:
"""
"""
ERECT_DOMAIN_REQUEST = 1
DISCONNECT_PROVIDER_ULTIMATUM = 8
ATTACH_USER_REQUEST = 10
ATTACH_USER_CONFIRM = 11
CHANNEL_JOIN_REQUEST = 14
CHANNEL_JOIN_CONFIRM = 15
SEND_DATA_REQUEST = 25
SEND_DATA_INDICATION = 26
class Channel:
"""
"""
MCS_GLOBAL_CHANNEL = 1003
MCS_USERCHANNEL_BASE = 1001
class IGCCConfig(object):
"""
@summary: Channel information
"""
def getUserId(self):
"""
@return: {integer} mcs user id
@see: mcs.IGCCConfig
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s" % (self.__class__, "getUserId", "IGCCConfig"))
def getChannelId(self):
"""
@return: {integer} return channel id of proxy
@see: mcs.IGCCConfig
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s" % (self.__class__, "getChannelId", "IGCCConfig"))
def getGCCClientSettings(self):
"""
@return: {gcc.Settings} mcs layer gcc client settings
@see: mcs.IGCCConfig
"""
raise CallPureVirtualFuntion(
"%s:%s defined by interface %s" % (self.__class__, "getGCCClientSettings", "IGCCConfig"))
def getGCCServerSettings(self):
"""
@return: {gcc.Settings} mcs layer gcc server settings
@see: mcs.IGCCConfig
"""
raise CallPureVirtualFuntion(
"%s:%s defined by interface %s" % (self.__class__, "getGCCServerSettings", "IGCCConfig"))
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):
"""
@summary: Multiple Channel Service layer
the main layer of RDP protocol
is why he can do everything and more!
"""
class MCSProxySender(Layer, IStreamSender, IGCCConfig):
"""
@summary: Proxy use to set as transport layer for upper channel
use to abstract channel id for presentation layer
"""
def __init__(self, presentation, mcs, channelId):
"""
@param presentation: {Layer} presentation layer
@param mcs: {MCSLayer} MCS layer use as proxy
@param channelId: {integer} channel id for presentation layer
"""
Layer.__init__(self, presentation)
self._mcs = mcs
self._channelId = channelId
def send(self, data):
"""
@summary: A send proxy function, use channel id and specific
send function of MCS layer
@param data: {type.Type | Tuple}
"""
self._mcs.send(self._channelId, data)
def close(self):
"""
@summary: Close wrapped layer
"""
self._mcs.close()
def getUserId(self):
"""
@return: {integer} mcs user id
@see: mcs.IGCCConfig
"""
return self._mcs._userId
def getChannelId(self):
"""
@return: {integer} return channel id of proxy
@see: mcs.IGCCConfig
"""
return self._channelId
def getGCCClientSettings(self):
"""
@return: {gcc.Settings} mcs layer gcc client settings
@see: mcs.IGCCConfig
"""
return self._mcs._clientSettings
def getGCCServerSettings(self):
"""
@return: {gcc.Settings} mcs layer gcc server settings
@see: mcs.IGCCConfig
"""
return self._mcs._serverSettings
def __init__(self, presentation, receiveOpcode, sendOpcode, virtualChannels=[]):
"""
@param presentation: {Layer} presentation layer
@param virtualChannels: {Array(Layer]} list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
@param receiveOpcode: {integer} opcode check when receive data
@param sendOpcode: {integer} opcode use when send data
"""
LayerAutomata.__init__(self, presentation)
self._clientSettings = gcc.clientSettings()
self._serverSettings = gcc.serverSettings()
# default user Id
self._userId = 1 + Channel.MCS_USERCHANNEL_BASE
# list of channel use in this layer and connection state
self._channels = {Channel.MCS_GLOBAL_CHANNEL: presentation}
# virtual channels
self._virtualChannels = virtualChannels
# send opcode
self._sendOpcode = sendOpcode
# receive opcode
self._receiveOpcode = receiveOpcode
def close(self):
"""
@summary: Send disconnect provider ultimatum
"""
self._transport.send((UInt8(self.writeMCSPDUHeader(DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM, 1)),
per.writeEnumerates(0x80), Buffer(b"\x00" * 6)))
self._transport.close()
def allChannelConnected(self):
"""
@summary: All channels are connected to MCS layer
Send connect to upper channel
And prepare MCS layer to receive data
"""
# connection is done
self.setNextState(self.recvData)
# try connection on all requested channel
for (channelId, layer) in self._channels.iteritems():
# use proxy for each channel
MCSLayer.MCSProxySender(layer, self, channelId).connect()
def send(self, channelId, data):
"""
@summary: Specific send function for channelId
@param channelId: {integer} Channel use to send
@param data: {type.type | tuple} message to send
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(self._sendOpcode)),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channelId),
UInt8(0x70),
per.writeLength(sizeof(data)), data))
def recvData(self, data):
"""
@summary: Main receive method
@param data: {Stream}
"""
opcode = UInt8()
data.read_type(opcode)
if self.readMCSPDUHeader(opcode.value, DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM):
log.info("MCS DISCONNECT_PROVIDER_ULTIMATUM")
self._transport.close()
return
# client case
elif not self.readMCSPDUHeader(opcode.value, self._receiveOpcode):
raise InvalidExpectedDataException("Invalid expected MCS opcode receive data")
# server user id
per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
channelId = per.readInteger16(data)
per.readEnumerates(data)
per.readLength(data)
# channel id doesn't match a requested layer
if not self._channels.has_key(channelId):
log.error("receive data for an unconnected layer")
return
self._channels[channelId].recv(data)
def writeDomainParams(self, maxChannels, maxUsers, maxTokens, maxPduSize):
"""
@summary: Write a special domain parameter structure
use in connection sequence
@param maxChannels: {integer} number of MCS channel use
@param maxUsers: {integer} number of MCS user used (1)
@param maxTokens: {integer} unknown
@param maxPduSize: {integer} unknown
@return: {Tuple(type)} domain parameter structure
"""
domainParam = (ber.writeInteger(maxChannels), ber.writeInteger(maxUsers), ber.writeInteger(maxTokens),
ber.writeInteger(1), ber.writeInteger(0), ber.writeInteger(1),
ber.writeInteger(maxPduSize), ber.writeInteger(2))
return (ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True), writeLength(sizeof(domainParam)), domainParam)
def writeMCSPDUHeader(self, mcsPdu, options=0):
"""
@summary: Write MCS PDU header
@param mcsPdu: {integer} PDU code
@param options: {integer} option contains in header
@return: {integer}
"""
return (mcsPdu << 2) | options
def readMCSPDUHeader(self, opcode, mcsPdu):
"""
@summary: Read mcsPdu header and return options parameter
@param opcode: {integer} opcode
@param mcsPdu: {integer} mcsPdu will be checked
@return: {boolean} true if opcode is correct
"""
return (opcode >> 2) == mcsPdu
def readDomainParams(self, s):
"""
@summary: Read domain parameters structure
@param s: {Stream}
@return: {Tuple} (max_channels, max_users, max_tokens, max_pdu_size)
"""
if not ber.readUniversalTag(s, ber.Tag.BER_TAG_SEQUENCE, True):
raise InvalidValue("bad BER tags")
ber.readLength(s) # length
max_channels = ber.readInteger(s)
max_users = ber.readInteger(s)
max_tokens = ber.readInteger(s)
ber.readInteger(s)
ber.readInteger(s)
ber.readInteger(s)
max_pdu_size = ber.readInteger(s)
ber.readInteger(s)
return (max_channels, max_users, max_tokens, max_pdu_size)
class ClientOld(MCSLayer):
"""
@summary: Client automata of multiple channel service layer
"""
def __init__(self, presentation, virtualChannels=[]):
"""
@param presentation: {Layer} presentation layer
@param virtualChannels: {Array(Layer)} list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
"""
MCSLayer.__init__(self, presentation, DomainMCSPDU.SEND_DATA_INDICATION, DomainMCSPDU.SEND_DATA_REQUEST,
virtualChannels)
# use to know state of static channel
self._isGlobalChannelRequested = False
self._isUserChannelRequested = False
# nb channel requested
self._nbChannelRequested = 0
def connect(self):
"""
@summary: Connect message in client automata case
Send ConnectInitial
Wait ConnectResponse
"""
self._clientSettings.CS_CORE.serverSelectedProtocol.value = self._transport._selectedProtocol
# ask for virtual channel
self._clientSettings.CS_NET.channelDefArray._array = [x for (x, _) in self._virtualChannels]
# send connect initial
self.sendConnectInitial()
# next wait response
self.setNextState(self.recvConnectResponse)
def connectNextChannel(self):
"""
@summary: Send sendChannelJoinRequest message on next disconnect channel
Send channel request or connect upper layer if all channels are connected
Wait channel confirm
"""
self.setNextState(self.recvChannelJoinConfirm)
# global channel
if not self._isGlobalChannelRequested:
self.sendChannelJoinRequest(Channel.MCS_GLOBAL_CHANNEL)
self._isGlobalChannelRequested = True
return
# user channel
if not self._isUserChannelRequested:
self.sendChannelJoinRequest(self._userId)
self._isUserChannelRequested = True
return
# static virtual channel
if self._nbChannelRequested < self._serverSettings.get_block(gcc.MessageType.SC_NET).channelCount.value:
channelId = self._serverSettings.get_block(gcc.MessageType.SC_NET).channelIdArray[self._nbChannelRequested]
self._nbChannelRequested += 1
self.sendChannelJoinRequest(channelId)
return
self.allChannelConnected()
def recvConnectResponse(self, data):
"""
@summary: Receive MCS connect response from server
Send Erect domain Request
Send Attach User Request
Wait Attach User Confirm
@param data: {Stream}
"""
ber.readApplicationTag(data, UInt8(Message.MCS_TYPE_CONNECT_RESPONSE))
ber.readEnumerated(data)
ber.readInteger(data)
self.readDomainParams(data)
if not ber.readUniversalTag(data, ber.Tag.BER_TAG_OCTET_STRING, False):
raise InvalidExpectedDataException("invalid expected BER tag")
gccRequestLength = ber.readLength(data)
if data.data_len() != gccRequestLength:
raise InvalidSize("bad size of GCC request")
self._serverSettings = gcc.readConferenceCreateResponse(data)
# send domain request
self.sendErectDomainRequest()
# send attach user request
self.sendAttachUserRequest()
# now wait user confirm from server
self.setNextState(self.recvAttachUserConfirm)
def recvAttachUserConfirm(self, data):
"""
@summary: Receive an attach user confirm
Send Connect Channel
@param data: {Stream}
"""
opcode = UInt8()
data.read_type(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ATTACH_USER_CONFIRM):
raise InvalidExpectedDataException("Invalid MCS PDU : ATTACH_USER_CONFIRM expected")
if per.readEnumerates(data) != 0:
raise InvalidExpectedDataException("Server reject user")
self._userId = per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
self.connectNextChannel()
def recvChannelJoinConfirm(self, data):
"""
@summary: Receive a channel join confirm from server
client automata function
@param data: {Stream}
"""
opcode = UInt8()
data.read_type(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.CHANNEL_JOIN_CONFIRM):
raise InvalidExpectedDataException("Invalid MCS PDU : CHANNEL_JOIN_CONFIRM expected")
confirm = per.readEnumerates(data)
userId = per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
if self._userId != userId:
raise InvalidExpectedDataException("Invalid MCS User Id")
channelId = per.readInteger16(data)
# must confirm global channel and user channel
if (confirm != 0) and (channelId == Channel.MCS_GLOBAL_CHANNEL or channelId == self._userId):
raise InvalidExpectedDataException("Server must confirm static channel")
if confirm == 0:
serverNet = self._serverSettings.get_block(gcc.MessageType.SC_NET)
for i in range(0, serverNet.channelCount.value):
if channelId == serverNet.channelIdArray[i].value:
self._channels[channelId] = self._virtualChannels[i][1]
self.connectNextChannel()
def sendConnectInitial(self):
"""
@summary: Send connect initial packet
client automata function
"""
ccReq = gcc.writeConferenceCreateRequest(self._clientSettings)
ccReqStream = Stream()
ccReqStream.write_type(ccReq)
tmp = (ber.writeOctetstring("\x01"), ber.writeOctetstring("\x01"), ber.writeBoolean(True),
self.writeDomainParams(34, 2, 0, 0xffff),
self.writeDomainParams(1, 1, 1, 0x420),
self.writeDomainParams(0xffff, 0xfc17, 0xffff, 0xffff),
ber.writeOctetstring(ccReqStream.getvalue()))
self._transport.send((ber.writeApplicationTag(Message.MCS_TYPE_CONNECT_INITIAL, sizeof(tmp)), tmp))
def sendErectDomainRequest(self):
"""
@summary: Send a formated erect domain request for RDP connection
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ERECT_DOMAIN_REQUEST)),
per.writeInteger(0),
per.writeInteger(0)))
def sendAttachUserRequest(self):
"""
@summary: Send a formated attach user request for RDP connection
"""
self._transport.send(self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_REQUEST)))
def sendChannelJoinRequest(self, channelId):
"""
@summary: Send a formated Channel join request from client to server
client automata function
@param channelId: {integer} id of channel requested
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.CHANNEL_JOIN_REQUEST)),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channelId)))
class Server(MCSLayer):
"""
@summary: Server automata of multiple channel service layer
"""
def __init__(self, presentation, virtualChannels=[]):
"""
@param presentation: {Layer} presentation layer
@param virtualChannels: {List(Layer)} list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
"""
MCSLayer.__init__(self, presentation, DomainMCSPDU.SEND_DATA_REQUEST, DomainMCSPDU.SEND_DATA_INDICATION,
virtualChannels)
# nb channel requested
self._nbChannelConfirmed = 0
def connect(self):
"""
@summary: Connect message for server automata
Wait Connect Initial
"""
# basic rdp security layer
if self._transport._selectedProtocol == 0:
self._serverSettings.SC_SECURITY.encryptionMethod.value = gcc.EncryptionMethod.ENCRYPTION_FLAG_128BIT
self._serverSettings.SC_SECURITY.encryptionLevel.value = gcc.EncryptionLevel.ENCRYPTION_LEVEL_HIGH
self._serverSettings.SC_SECURITY.serverRandom.value = rsa.random(256)
self._serverSettings.SC_SECURITY.serverCertificate = self._presentation.getCertificate()
self._serverSettings.SC_CORE.clientRequestedProtocol.value = self._transport._requestedProtocol
self.setNextState(self.recvConnectInitial)
def recvConnectInitial(self, data):
"""
@summary: Receive MCS connect initial from client
Send Connect Response
Wait Erect Domain Request
@param data: {Stream}
"""
ber.readApplicationTag(data, UInt8(Message.MCS_TYPE_CONNECT_INITIAL))
ber.readOctetString(data)
ber.readOctetString(data)
if not ber.readBoolean(data):
raise InvalidExpectedDataException("invalid expected BER boolean tag")
self.readDomainParams(data)
self.readDomainParams(data)
self.readDomainParams(data)
self._clientSettings = gcc.readConferenceCreateRequest(Stream(ber.readOctetString(data)))
if not self._clientSettings.CS_NET is None:
i = 1
for channelDef in self._clientSettings.CS_NET.channelDefArray._array:
self._serverSettings.SC_NET.channelIdArray._array.append(UInt16Le(i + Channel.MCS_GLOBAL_CHANNEL))
# if channel can be handle by serve add it
for serverChannelDef, layer in self._virtualChannels:
if channelDef.name == serverChannelDef.name:
self._channels[i + Channel.MCS_GLOBAL_CHANNEL] = layer
i += 1
self.sendConnectResponse()
self.setNextState(self.recvErectDomainRequest)
def recvErectDomainRequest(self, data):
"""
@summary: Receive erect domain request
Wait Attach User Request
@param data: {Stream}
"""
opcode = UInt8()
data.read_type(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ERECT_DOMAIN_REQUEST):
raise InvalidExpectedDataException("Invalid MCS PDU : ERECT_DOMAIN_REQUEST expected")
per.readInteger(data)
per.readInteger(data)
self.setNextState(self.recvAttachUserRequest)
def recvAttachUserRequest(self, data):
"""
@summary: Receive Attach user request
Send Attach User Confirm
Wait Channel Join Request
@param data: {Stream}
"""
opcode = UInt8()
data.read_type(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ATTACH_USER_REQUEST):
raise InvalidExpectedDataException("Invalid MCS PDU : ATTACH_USER_REQUEST expected")
self.sendAttachUserConfirm()
self.setNextState(self.recvChannelJoinRequest)
def recvChannelJoinRequest(self, data):
"""
@summary: Receive for each client channel a request
Send Channel Join Confirm or Connect upper layer when all channel are joined
@param data: {Stream}
"""
opcode = UInt8()
data.read_type(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.CHANNEL_JOIN_REQUEST):
raise InvalidExpectedDataException("Invalid MCS PDU : CHANNEL_JOIN_REQUEST expected")
userId = per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
if self._userId != userId:
raise InvalidExpectedDataException("Invalid MCS User Id")
channelId = per.readInteger16(data)
# actually algo support virtual channel but RDPY have no virtual channel
confirm = 0 if channelId in self._channels.keys() or channelId == self._userId else 1
self.sendChannelJoinConfirm(channelId, confirm)
self._nbChannelConfirmed += 1
if self._nbChannelConfirmed == self._serverSettings.get_block(gcc.MessageType.SC_NET).channelCount.value + 2:
self.allChannelConnected()
def sendConnectResponse(self):
"""
@summary: Send connect response
"""
ccReq = gcc.writeConferenceCreateResponse(self._serverSettings)
ccReqStream = Stream()
ccReqStream.write_type(ccReq)
tmp = (ber.writeEnumerated(0), ber.writeInteger(0), self.writeDomainParams(22, 3, 0, 0xfff8),
ber.writeOctetstring(ccReqStream.getvalue()))
self._transport.send((ber.writeApplicationTag(Message.MCS_TYPE_CONNECT_RESPONSE, sizeof(tmp)), tmp))
def sendAttachUserConfirm(self):
"""
@summary: Send attach user confirm
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_CONFIRM), 2),
per.writeEnumerates(0),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE)))
def sendChannelJoinConfirm(self, channelId, confirm):
"""
@summary: Send a confirm channel (or not) to client
@param channelId: {integer} id of channel
@param confirm: {boolean} connection state
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.CHANNEL_JOIN_CONFIRM), 2),
per.writeEnumerates(int(confirm)),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channelId),
per.writeInteger16(channelId)))

View File

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

179
rdpy/core/tpkt.py Normal file
View File

@@ -0,0 +1,179 @@
#
# 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/>.
#
"""
Transport packet layer implementation
Use to build correct size packet and handle slow path and fast path mode
"""
import asyncio
import ssl
from rdpy.core.nla import cssp, sspi
from rdpy.model.layer import RawLayer
from rdpy.model.message import UInt8, UInt16Be, sizeof, Stream
class Action:
"""
@see: http://msdn.microsoft.com/en-us/library/cc240621.aspx
@see: http://msdn.microsoft.com/en-us/library/cc240589.aspx
"""
FASTPATH_ACTION_FASTPATH = 0x0
FASTPATH_ACTION_X224 = 0x3
class SecFlags:
"""
@see: http://msdn.microsoft.com/en-us/library/cc240621.aspx
"""
FASTPATH_OUTPUT_SECURE_CHECKSUM = 0x1
FASTPATH_OUTPUT_ENCRYPTED = 0x2
class Tpkt:
"""
@summary: TPKT layer in RDP protocol stack
represent the Raw Layer in stack (first layer)
This layer only handle size of packet and determine if is a fast path packet
"""
def __init__(self, reader, writer):
self.reader = reader
self.writer = writer
def readHeader(self, data):
"""
@summary: Read header of TPKT packet
@param data: {Stream} received from twisted layer
"""
#first read packet version
version = UInt8()
data.read_type(version)
#classic packet
if version.value == Action.FASTPATH_ACTION_X224:
#padding
data.read_type(UInt8())
#read end header
self.expect(2, self.readExtendedHeader)
else:
#is fast path packet
self._secFlag = ((version.value >> 6) & 0x3)
data.read_type(self._lastShortLength)
if self._lastShortLength.value & 0x80:
#size is 1 byte more
self.expect(1, self.readExtendedFastPathHeader)
return
self.expect(self._lastShortLength.value - 2, self.readFastPath)
def readExtendedHeader(self, data):
"""
@summary: Header may be on 4 bytes
@param data: {Stream} from twisted layer
"""
#next state is read data
size = UInt16Be()
data.read_type(size)
self.expect(size.value - 4, self.readData)
def readExtendedFastPathHeader(self, data):
"""
@summary: Fast path header may be on 1 byte more
@param data: {Stream} from twisted layer
"""
leftPart = UInt8()
data.read_type(leftPart)
self._lastShortLength.value &= ~0x80
packetSize = (self._lastShortLength.value << 8) + leftPart.value
#next state is fast patn data
self.expect(packetSize - 3, self.readFastPath)
def readFastPath(self, data):
"""
@summary: Fast path data
@param data: {Stream} from twisted layer
"""
self._fastPathListener.recvFastPath(self._secFlag, data)
self.expect(2, self.readHeader)
def readData(self, data):
"""
@summary: Read classic TPKT packet, last state in tpkt automata
@param data: {Stream} with correct size
"""
#next state is pass to
self._presentation.recv(data)
self.expect(2, self.readHeader)
async def write(self, message):
"""
@summary: Send encompassed data
@param message: {network.Type} message to send
"""
s = Stream()
s.write_type((UInt8(Action.FASTPATH_ACTION_X224), UInt8(0), UInt16Be(sizeof(message) + 4), message))
self.writer.write(s.getvalue())
await self.writer.drain()
async def read(self):
"""
Read an entire payload from the reader stream
"""
header = Stream(await self.reader.readexactly(2))
action = UInt8()
header.read_type(action)
if action.value == Action.FASTPATH_ACTION_X224:
# read padding
header.read_type(UInt8())
size = UInt16Be()
Stream(await self.reader.readexactly(2)).read_type(size)
return Stream(await self.reader.readexactly(size.value - 4))
def sendFastPath(self, secFlag, fastPathS):
"""
@param fastPathS: {Type | Tuple} type transform to stream and send as fastpath
@param secFlag: {integer} Security flag for fastpath packet
"""
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH | ((secFlag & 0x3) << 6)), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS))
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

270
rdpy/core/x224.py Normal file
View File

@@ -0,0 +1,270 @@
#
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Implement transport PDU layer
This layer have main goal to negociate SSL transport
RDP basic security is supported only on client side
"""
from rdpy.core import tpkt
from rdpy.core.nla import sspi
from rdpy.model import log
from rdpy.model.message import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, Buffer, Stream
from rdpy.model.error import InvalidExpectedDataException, RDPSecurityNegoFail
class MessageType:
"""
@summary: Message type
"""
X224_TPDU_CONNECTION_REQUEST = 0xE0
X224_TPDU_CONNECTION_CONFIRM = 0xD0
X224_TPDU_DISCONNECT_REQUEST = 0x80
X224_TPDU_DATA = 0xF0
X224_TPDU_ERROR = 0x70
class NegociationType:
"""
@summary: Negotiation header
"""
TYPE_RDP_NEG_REQ = 0x01
TYPE_RDP_NEG_RSP = 0x02
TYPE_RDP_NEG_FAILURE = 0x03
class Protocols:
"""
@summary: Protocols available for x224 layer
@see: https://msdn.microsoft.com/en-us/library/cc240500.aspx
"""
PROTOCOL_RDP = 0x00000000
PROTOCOL_SSL = 0x00000001
PROTOCOL_HYBRID = 0x00000002
PROTOCOL_HYBRID_EX = 0x00000008
class NegotiationFailureCode:
"""
@summary: Protocol negotiation failure code
"""
SSL_REQUIRED_BY_SERVER = 0x00000001
SSL_NOT_ALLOWED_BY_SERVER = 0x00000002
SSL_CERT_NOT_ON_SERVER = 0x00000003
INCONSISTENT_FLAGS = 0x00000004
HYBRID_REQUIRED_BY_SERVER = 0x00000005
SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 0x00000006
class ConnectionRequestPDU(CompositeType):
"""
Connection Request PDU
Use to send protocol security level available for the client
:see: http://msdn.microsoft.com/en-us/library/cc240470.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.len = UInt8(lambda:sizeof(self) - 1)
self.code = UInt8(MessageType.X224_TPDU_CONNECTION_REQUEST, constant=True)
self.padding = (UInt16Be(), UInt16Be(), UInt8())
self.cookie = Buffer(until=b"\x0d\x0a", conditional=lambda: (self.len._is_readed and self.len.value > 14))
# read if there is enough data
self.protocolNeg = Negotiation(optional = True)
class ConnectionConfirmPDU(CompositeType):
"""
@summary: Server response
@see: http://msdn.microsoft.com/en-us/library/cc240506.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.len = UInt8(lambda:sizeof(self) - 1)
self.code = UInt8(MessageType.X224_TPDU_CONNECTION_CONFIRM, constant = True)
self.padding = (UInt16Be(), UInt16Be(), UInt8())
#read if there is enough data
self.protocolNeg = Negotiation(optional = True)
class X224DataHeader(CompositeType):
"""
@summary: Header send when x224 exchange application data
"""
def __init__(self):
CompositeType.__init__(self)
self.header = UInt8(2)
self.messageType = UInt8(MessageType.X224_TPDU_DATA, constant = True)
self.separator = UInt8(0x80, constant = True)
class Negotiation(CompositeType):
"""
@summary: Negociate request message
@see: request -> http://msdn.microsoft.com/en-us/library/cc240500.aspx
@see: response -> http://msdn.microsoft.com/en-us/library/cc240506.aspx
@see: failure ->http://msdn.microsoft.com/en-us/library/cc240507.aspx
"""
def __init__(self, optional = False):
CompositeType.__init__(self, optional = optional)
self.code = UInt8()
self.flag = UInt8(0)
#always 8
self.len = UInt16Le(0x0008, constant = True)
self.selectedProtocol = UInt32Le(conditional = lambda: (self.code.value != NegociationType.TYPE_RDP_NEG_FAILURE))
self.failureCode = UInt32Le(conditional = lambda: (self.code.value == NegociationType.TYPE_RDP_NEG_FAILURE))
class X224:
"""
"""
def __init__(self, tpkt: tpkt.Tpkt, selected_protocol: int):
"""
"""
self.tpkt = tpkt
self.selected_protocol = selected_protocol
async def read(self) -> Stream:
"""
"""
header = X224DataHeader()
payload = await self.tpkt.read()
payload.read_type(header)
return payload
async def write(self, message):
"""
"""
await self.tpkt.write((X224DataHeader(), message))
def get_selected_protocol(self):
return self.selected_protocol
async def connect(tpkt: tpkt.Tpkt, authentication_protocol: sspi.IAuthenticationProtocol) -> X224:
"""
Negotiate the security level and generate a X224 configured layer
:ivar tpkt: this is the tpkt layer use to negotiate the security level
:ivar authentication_protocol: Authentication protocol is used by NLA authentication
Actually only NTLMv2 is available
:see: http://msdn.microsoft.com/en-us/library/cc240500.aspx
"""
request = ConnectionRequestPDU()
request.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_REQ
request.protocolNeg.selectedProtocol.value = Protocols.PROTOCOL_HYBRID | Protocols.PROTOCOL_SSL
await tpkt.write(request)
respond = (await tpkt.read()).read_type(ConnectionConfirmPDU())
if respond.protocolNeg.failureCode._is_readed:
raise RDPSecurityNegoFail("negotiation failure code %x"%respond.protocolNeg.failureCode.value)
selected_protocol = Protocols.PROTOCOL_RDP
if respond.protocolNeg._is_readed:
selected_protocol = respond.protocolNeg.selectedProtocol.value
if selected_protocol in [Protocols.PROTOCOL_HYBRID_EX]:
raise InvalidExpectedDataException("RDPY doesn't support PROTOCOL_HYBRID_EX security Layer")
if selected_protocol == Protocols.PROTOCOL_RDP:
return X224(tpkt, selected_protocol)
elif selected_protocol == Protocols.PROTOCOL_SSL:
return X224(await tpkt.start_tls(), selected_protocol)
elif selected_protocol == Protocols.PROTOCOL_HYBRID:
return X224(await tpkt.start_nla(authentication_protocol), selected_protocol)
class Server(X224):
"""
@summary: Server automata of X224 layer
"""
def __init__(self, presentation, privateKeyFileName = None, certificateFileName = None, forceSSL = False):
"""
@param presentation: {layer} upper layer, MCS layer in RDP case
@param privateKeyFileName: {str} file contain server private key
@param certficiateFileName: {str} file that contain public key
@param forceSSL: {boolean} reject old client that doerasn't support SSL
"""
X224Layer.__init__(self, presentation)
#Server mode informations for TLS connection
self._serverPrivateKeyFileName = privateKeyFileName
self._serverCertificateFileName = certificateFileName
self._forceSSL = forceSSL and not self._serverPrivateKeyFileName is None and not self._serverCertificateFileName is None
def connect(self):
"""
@summary: Connection request for server wait connection request packet from client
"""
self.setNextState(self.recvConnectionRequest)
def recvConnectionRequest(self, data):
"""
@summary: Read connection confirm packet
Next state is send connection confirm
@param data: {Stream}
@see : http://msdn.microsoft.com/en-us/library/cc240470.aspx
"""
message = ConnectionRequestPDU()
data.read_type(message)
if not message.protocolNeg._is_readed:
self._requestedProtocol = Protocols.PROTOCOL_RDP
else:
self._requestedProtocol = message.protocolNeg.selectedProtocol.value
#match best security layer available
if not self._serverPrivateKeyFileName is None and not self._serverCertificateFileName is None:
self._selectedProtocol = self._requestedProtocol & Protocols.PROTOCOL_SSL
else:
self._selectedProtocol = self._requestedProtocol & Protocols.PROTOCOL_RDP
#if force ssl is enable
if not self._selectedProtocol & Protocols.PROTOCOL_SSL and self._forceSSL:
log.warning("server reject client because doesn't support SSL")
#send error message and quit
message = ConnectionConfirmPDU()
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_FAILURE
message.protocolNeg.failureCode.value = NegotiationFailureCode.SSL_REQUIRED_BY_SERVER
self._transport.send(message)
self.close()
return
self.sendConnectionConfirm()
def sendConnectionConfirm(self):
"""
@summary: Write connection confirm message
Start TLS connection
Next state is recvData
@see : http://msdn.microsoft.com/en-us/library/cc240501.aspx
"""
message = ConnectionConfirmPDU()
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_RSP
message.protocolNeg.selectedProtocol.value = self._selectedProtocol
self._transport.send(message)
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
#_transport is TPKT and transport is TCP layer of twisted
#self._transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
#connection is done send to presentation
self.setNextState(self.recvData)
self._presentation.connect()

View File

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

View File

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

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

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

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -22,8 +22,9 @@ Join RDPY design with twisted design
RDPY use Layer Protocol design (like twisted)
"""
import asyncio
from rdpy.model.error import CallPureVirtualFuntion
from rdpy.base.error import CallPureVirtualFuntion
class IStreamListener(object):
"""
@@ -36,6 +37,7 @@ class IStreamListener(object):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
class IStreamSender(object):
"""
@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"))
class Layer(object):
"""
@summary: A simple double linked list with presentation and transport layer
@@ -80,6 +83,7 @@ class Layer(object):
if not self._transport is None:
self._transport.close()
class LayerAutomata(Layer, IStreamListener):
"""
@summary: Layer with automata callback
@@ -99,17 +103,12 @@ class LayerAutomata(Layer, IStreamListener):
@param callback: a callable object
"""
if callback is None:
callback = self.__class__.recv
callback = lambda x:self.__class__.recv(self, x)
self.recv = callback
#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
"""
@@ -129,42 +128,45 @@ class RawLayerClientFactory(protocol.ClientFactory):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildRawLayer", "RawLayerClientFactory"))
def connectionLost(self, rawlayer):
def connectionLost(self, rawlayer, reason):
"""
@summary: Override this method to handle connection lost
@param rawlayer: rawLayer that cause connectionLost event
@param reason: twisted reason
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "connectionLost", "RawLayerClientFactory"))
class RawLayerServerFactory(protocol.ClientFactory):
"""
@summary: Abstract class for Raw layer server factory
"""
def buildProtocol(self, addr):
"""
@summary: Function call from twisted
@param addr: destination address
"""
rawLayer = self.buildRawLayer(addr)
rawLayer.setFactory(self)
return rawLayer
def buildRawLayer(self, addr):
"""
@summary: Override this function to build raw layer
@param addr: destination address
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
def connectionLost(self, rawlayer):
"""
@summary: Override this method to handle connection lost
@param rawlayer: rawLayer that cause connectionLost event
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
# class RawLayerServerFactory(protocol.ServerFactory):
# """
# @summary: Abstract class for Raw layer server factory
# """
# def buildProtocol(self, addr):
# """
# @summary: Function call from twisted
# @param addr: destination address
# """
# rawLayer = self.buildRawLayer(addr)
# rawLayer.setFactory(self)
# return rawLayer
#
# def buildRawLayer(self, addr):
# """
# @summary: Override this function to build raw layer
# @param addr: destination address
# """
# raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
#
# def connectionLost(self, rawlayer, reason):
# """
# @summary: Override this method to handle connection lost
# @param rawlayer: rawLayer that cause connectionLost event
# @param reason: twisted reason
# """
# raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
#
#
class RawLayer(asyncio.Protocol, LayerAutomata, IStreamSender):
"""
@summary: Wait event from twisted engine
And format correct size packet
@@ -198,7 +200,7 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
#add in buffer
self._buffer += data
#while buffer have expected size call local callback
while len(self._buffer) >= self._expectedLen:
while self._expectedLen > 0 and len(self._buffer) >= self._expectedLen:
#expected data is first expected bytes
expectedData = Stream(self._buffer[0:self._expectedLen])
#rest is for next event of automata
@@ -218,7 +220,13 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
@summary: Call from twisted engine when protocol is closed
@param reason: str represent reason of close connection
"""
self._factory.connectionLost(self)
self._factory.connectionLost(self, reason)
def getDescriptor(self):
"""
@return: the twited file descriptor
"""
return self.transport
def close(self):
"""
@@ -226,7 +234,7 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
Use File descriptor directly to not use TLS close
Because is bugged
"""
FileDescriptor.loseConnection(self.transport)
FileDescriptor.loseConnection(self.getDescriptor())
def expect(self, expectedLen, callback = None):
"""
@@ -248,5 +256,5 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
@param message: (tuple | Type)
"""
s = Stream()
s.writeType(message)
s.write_type(message)
self.transport.write(s.getvalue())

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -30,15 +30,21 @@ class Level(object):
INFO = 1
WARNING = 2
ERROR = 3
NONE = 4
_LOG_LEVEL = Level.DEBUG
_LOG_FILE = False
def log(message):
"""
@summary: Main log function
@param message: string to print
"""
print message
if _LOG_FILE:
f = open(_LOG_FILE, "a+")
f.write("%s\n"%message)
f.close()
print("[*] %s"%message)
def error(message):
"""
@@ -47,7 +53,7 @@ def error(message):
"""
if _LOG_LEVEL > Level.ERROR:
return
log("ERROR : %s"%message)
log("ERROR:\t%s"%message)
def warning(message):
"""
@@ -56,7 +62,7 @@ def warning(message):
"""
if _LOG_LEVEL > Level.WARNING:
return
log("WARNING : %s"%message)
log("WARNING:\t%s"%message)
def info(message):
"""
@@ -65,7 +71,7 @@ def info(message):
"""
if _LOG_LEVEL > Level.INFO:
return
log("INFO : %s"%message)
log("INFO:\t%s"%message)
def debug(message):
"""
@@ -74,4 +80,4 @@ def debug(message):
"""
if _LOG_LEVEL > Level.DEBUG:
return
log("DEBUG : %s"%message)
log("DEBUG:\t%s"%message)

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -26,9 +26,10 @@ We are in python!
import struct
from copy import deepcopy
from StringIO import StringIO
from rdpy.base.error import InvalidExpectedDataException, InvalidSize, CallPureVirtualFuntion, InvalidValue
import rdpy.base.log as log
from io import BytesIO
from rdpy.model.error import InvalidExpectedDataException, InvalidSize, CallPureVirtualFuntion, InvalidValue
import rdpy.model.log as log
def sizeof(element):
"""
@@ -42,31 +43,27 @@ def sizeof(element):
for i in element:
size += sizeof(i)
return size
elif isinstance(element, Type) and element._conditional():
elif isinstance(element, Message) and element._conditional():
return element.__sizeof__()
return 0
class Type(object):
class Message:
"""
@summary: Root type object inheritance
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._optional = optional
self._constant = constant
#use to record read state
#if type is optional and not present during read
#this boolean stay false
# use to record read state
# if type is optional and not present during read
# this boolean stay false
self._is_readed = False
#use to know if type was written
# use to know if type was written
self._is_writed = False
def write(self, s):
@@ -92,18 +89,18 @@ class Type(object):
if not self._is_readed:
return
#not constant mode direct reading
# not constant mode direct reading
if not self._constant:
self.__read__(s)
return
#constant mode
# constant mode
old = deepcopy(self)
self.__read__(s)
#check constant value
# check constant value
if old != self:
#rollback read value
s.pos -= sizeof(self)
# rollback read value
s.seek(-sizeof(self), 1)
raise InvalidExpectedDataException("%s const value expected %s != %s"%(self.__class__, old.value, self.value))
def __read__(self, s):
@@ -127,22 +124,23 @@ class Type(object):
"""
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
Ex: Type contain length of array and array
To know the size of array you need to 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
Expression evaluate when is get or set
Ex: Type contain length of array and array
To know the size of array you need to 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
"""
def __init__(self, value):
def __init__(self, **kwargs):
"""
@param value: value will be wrapped (raw python type | lambda | function)
"""
super().__init__(**kwargs)
self._value = None
self.value = value
def __getValue__(self):
def get_value(self):
"""
@summary: Call when value is get -> Evaluate inner expression
Can be overwritten to add specific check before
@@ -151,7 +149,7 @@ class CallableValue(object):
"""
return self._value()
def __setValue__(self, value):
def set_value(self, value):
"""
@summary: Call when value is set
Can be overwritten to add specific check before
@@ -164,23 +162,10 @@ class CallableValue(object):
self._value = value_callable
@property
def value(self):
"""
@summary: Evaluate callable expression
@return: result of callable value
"""
return self.__getValue__()
value = property(get_value, set_value)
@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
leaf in type tree
@@ -198,46 +183,11 @@ class SimpleType(Type, CallableValue):
And optional is True, read type is ignored
@param constant: Check if object value doesn't change after read operation
"""
super().__init__(conditional=conditional, optional=optional, constant=constant)
self._signed = signed
self._typeSize = typeSize
self._structFormat = structFormat
Type.__init__(self, conditional = conditional, optional = optional, constant = constant)
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)
self.value = value
def __write__(self, s):
"""
@@ -256,33 +206,11 @@ class SimpleType(Type, CallableValue):
@param s: Stream that will be read
@raise InvalidSize: if there is not enough data in stream
"""
if s.dataLen() < self._typeSize:
raise InvalidSize("Stream is too small to read expected Simple")
self.value = struct.unpack(self._structFormat, s.read(self._typeSize))[0]
if s.data_len() < self._typeSize:
raise InvalidSize("Stream is too small to read expected SimpleType")
value = struct.unpack(self._structFormat, s.read(self._typeSize))[0]
def mask(self):
"""
@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())
self.value = value
def __sizeof__(self):
"""
@@ -291,7 +219,7 @@ class SimpleType(Type, CallableValue):
"""
return self._typeSize
def __cmp__(self, other):
def __eq__(self, other):
"""
@summary: Compare two simple type
Call inner value compare operator
@@ -301,7 +229,19 @@ class SimpleType(Type, CallableValue):
"""
if not isinstance(other, SimpleType):
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):
"""
@@ -412,72 +352,53 @@ class SimpleType(Type, CallableValue):
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)
#list of ordoned type
self._typeName = []
self._readLen = readLen
super().__init__(conditional=conditional, optional=optional, constant=constant)
# list of ordorred type
self._type_name = []
self._read_len = read_len
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:
self._typeName.append(name)
if name[0] != '_' and (isinstance(value, Message) or isinstance(value, tuple)) and name not in self._type_name:
self._type_name.append(name)
self.__dict__[name] = value
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
for name in self._typeName:
read_len = 0
for name in self._type_name:
try:
s.readType(self.__dict__[name])
readLen += sizeof(self.__dict__[name])
#read is ok but read out of bound
if not self._readLen is None and readLen > self._readLen.value:
#roll back
s.pos -= sizeof(self.__dict__[name])
#and notify
raise InvalidSize("Impossible to read type %s : read length is too small"%(self.__class__))
s.read_type(self.__dict__[name])
read_len += sizeof(self.__dict__[name])
# read is ok but read out of bound
if self._read_len is not None and read_len > self._read_len():
# roll back
s.seek(-sizeof(self.__dict__[name]), 1)
# and notify if not optional
if not self.__dict__[name]._optional:
raise InvalidSize("Impossible to read type %s : read length is too small"%(self.__class__))
except Exception as e:
log.error("Error during read %s::%s"%(self.__class__, name))
#roll back already read
for tmpName in self._typeName:
if tmpName == name:
# roll back already read
for tmp_name in self._type_name:
if tmp_name == name:
break
s.pos -= sizeof(self.__dict__[tmpName])
s.seek(-sizeof(self.__dict__[tmp_name]), 1)
raise e
if not self._readLen is None and readLen < self._readLen.value:
log.debug("Still have correct data in packet %s, read it as padding"%self.__class__)
s.read(self._readLen.value - readLen)
if self._read_len is not None and read_len < self._read_len():
log.debug("Still have correct data in packet %s, read %s bytes as padding"%(self.__class__, self._read_len() - read_len))
s.read(self._read_len() - read_len)
def __write__(self, s):
"""
@@ -485,9 +406,9 @@ class CompositeType(Type):
Call write on each ordered sub type
@param s: Stream
"""
for name in self._typeName:
for name in self._type_name:
try:
s.writeType(self.__dict__[name])
s.write_type(self.__dict__[name])
except Exception as e:
log.error("Error during write %s::%s"%(self.__class__, name))
raise e
@@ -497,8 +418,11 @@ class CompositeType(Type):
@summary: Call sizeof on each sub type
@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
for name in self._typeName:
for name in self._type_name:
size += sizeof(self.__dict__[name])
return size
@@ -509,9 +433,9 @@ class CompositeType(Type):
@param other: CompositeType
@return: True if each sub-type are equals
"""
if self._typeName != other._typeName:
if self._type_name != other._typeName:
return False
for name in self._typeName:
for name in self._type_name:
if self.__dict__[name] != other.__dict__[name]:
return False
return True
@@ -701,6 +625,7 @@ class UInt24Be(SimpleType):
"""
self.value = struct.unpack(self._structFormat, '\x00' + s.read(self._typeSize))[0]
class UInt24Le(SimpleType):
"""
@summary: unsigned 24 bit integer
@@ -722,7 +647,7 @@ class UInt24Le(SimpleType):
@summary: special write for a special type
@param s: Stream
"""
#don't write first byte
# don't write first byte
s.write(struct.pack("<I", self.value)[:3])
def __read__(self, s):
@@ -730,90 +655,64 @@ class UInt24Le(SimpleType):
@summary: special read for a special type
@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
Leaf in Type tree
This a raw binary bytes data
"""
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)
CallableValue.__init__(self, value)
#type use to know read length
self._readLen = readLen
self._unicode = unicode
super().__init__(conditional=conditional, optional=optional, constant=constant)
# type use to know read length
self._read_len = read_len
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):
"""
@summary: hash function to treat simple type in hash collection
@return: hash of inner value
"""
return hash(self.value)
def __str__(self):
"""
@summary: call when str function is call
@return: inner python string
"""
return self.value
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:
toWrite += self._until
if self._unicode:
s.write(encodeUnicode(self.value))
else:
s.write(self.value)
to_write += self._until
s.write(to_write)
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:
self.value = s.getvalue()[s.pos:]
self.value = s.getvalue()[s.tell():]
else:
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)
else:
self.value = s.read(self._readLen.value)
if self._unicode:
self.value = decodeUnicode(self.value)
self.value = s.read(self._read_len())
def __sizeof__(self):
"""
@@ -821,10 +720,8 @@ class String(Type, CallableValue):
if string is unicode encode return 2*len(str) + 2
@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):
"""
@@ -848,105 +745,98 @@ def decodeUnicode(s):
i += 1
return r
class Stream(StringIO):
class Stream(BytesIO):
"""
@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
@return: read size of stream
Compute already read size
: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
or iterate over tuple elements
rollback read if error occurred during read value
@param value: (tuple | Type) object
Call specific read on type object
or iterate over tuple elements
rollback read if error occurred during read value
:ivar tuple | Type object
"""
#read each tuple
# read each tuple
if isinstance(value, tuple) or isinstance(value, list):
for element in value:
try:
self.readType(element)
self.read_type(element)
except Exception as e:
#rollback already readed elements
# rollback already readed elements
for tmpElement in value:
if tmpElement == element:
break
self.pos -= sizeof(tmpElement)
raise e
return
return value
#optional value not present
if self.dataLen() == 0 and value._optional:
# optional value not present
if self.data_len() == 0 and value._optional:
return
value.read(self)
return value
def readNextType(self, t):
"""
@summary: read next type but didn't consume it
@param t: Type element
"""
self.readType(t)
self.read_type(t)
self.pos -= sizeof(t)
def writeType(self, value):
def write_type(self, value: Message):
"""
@summary: Call specific write on type object
or iterate over tuple element
@param value: (tuple | Type)
Call specific write on type object
or iterate over tuple element
:ivar Type: Type to write
"""
#write each element of tuple
# write each element of tuple
if isinstance(value, tuple) or isinstance(value, list):
for element in value:
self.writeType(element)
return
self.write_type(element)
return self
value.write(self)
return self
class ArrayType(Type):
class ArrayType(Message):
"""
@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)
self._typeFactory = typeFactory
self._readLen = readLen
self._array = []
if not init is None:
self._array = init
super().__init__(conditional, optional, constant)
self._type_factory = type_factory
self._read_len = read_len
self._array = init or []
def __read__(self, s):
"""
@summary: Create readLen new object and read it
@param s: Stream
"""
self._array = []
i = 0
#self._readLen is None means that array will be read until end of stream
while self._readLen is None or i < self._readLen.value:
element = self._typeFactory()
element._optional = self._readLen is None
s.readType(element)
# self._read_len is None means that array will be read until end of stream
while self._read_len is None or i < self._read_len():
element = self._type_factory()
element._optional = self._read_len is None
s.read_type(element)
if not element._is_readed:
break
self._array.append(element)
@@ -954,10 +844,15 @@ class ArrayType(Type):
def __write__(self, s):
"""
@summary: Just write array
@param s: Stream
"""
s.writeType(self._array)
s.write_type(self._array)
def __getitem__(self, item):
"""
@summary: Magic function to be FactoryType as transparent as possible
@return: index of _value
"""
return self._array.__getitem__(item)
def __sizeof__(self):
"""
@@ -965,7 +860,10 @@ class ArrayType(Type):
"""
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
Wrapp attribute access to inner type
@@ -979,7 +877,7 @@ class FactoryType(Type):
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)
Message.__init__(self, conditional, optional, constant)
self._factory = factory
if not callable(factory):
self._factory = lambda:factory
@@ -992,7 +890,7 @@ class FactoryType(Type):
@param s: Stream
"""
self._value = self._factory()
s.readType(self._value)
s.read_type(self._value)
def __write__(self, s):
"""
@@ -1000,7 +898,7 @@ class FactoryType(Type):
@param s: Stream
"""
self._value = self._factory()
s.writeType(self._value)
s.write_type(self._value)
def __getattr__(self, name):
"""

300
rdpy/model/rss.py Normal file
View 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
View File

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

View File

@@ -1,442 +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 GCC structure use in RDP protocol
http://msdn.microsoft.com/en-us/library/cc240508.aspx
"""
from rdpy.network.type import UInt8, UInt16Le, UInt32Le, CompositeType, String, Stream, sizeof, FactoryType, ArrayType
import per, mcs
from rdpy.base.error import InvalidExpectedDataException
import rdpy.base.log as log
t124_02_98_oid = ( 0, 0, 20, 124, 0, 1 )
h221_cs_key = "Duca";
h221_sc_key = "McDn";
class MessageType(object):
"""
Server to Client block
GCC conference messages
@see: http://msdn.microsoft.com/en-us/library/cc240509.aspx
"""
#server -> client
SC_CORE = 0x0C01
SC_SECURITY = 0x0C02
SC_NET = 0x0C03
#client -> server
CS_CORE = 0xC001
CS_SECURITY = 0xC002
CS_NET = 0xC003
CS_CLUSTER = 0xC004
CS_MONITOR = 0xC005
class ColorDepth(object):
"""
Depth color
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
RNS_UD_COLOR_8BPP = 0xCA01
RNS_UD_COLOR_16BPP_555 = 0xCA02
RNS_UD_COLOR_16BPP_565 = 0xCA03
RNS_UD_COLOR_24BPP = 0xCA04
class HighColor(object):
"""
High color of client
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
HIGH_COLOR_4BPP = 0x0004
HIGH_COLOR_8BPP = 0x0008
HIGH_COLOR_15BPP = 0x000f
HIGH_COLOR_16BPP = 0x0010
HIGH_COLOR_24BPP = 0x0018
class Support(object):
"""
Supported depth flag
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
RNS_UD_24BPP_SUPPORT = 0x0001
RNS_UD_16BPP_SUPPORT = 0x0002
RNS_UD_15BPP_SUPPORT = 0x0004
RNS_UD_32BPP_SUPPORT = 0x0008
class CapabilityFlags(object):
"""
For more details on each flags click above
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
RNS_UD_CS_SUPPORT_ERRINFO_PDU = 0x0001
RNS_UD_CS_WANT_32BPP_SESSION = 0x0002
RNS_UD_CS_SUPPORT_STATUSINFO_PDU = 0x0004
RNS_UD_CS_STRONG_ASYMMETRIC_KEYS = 0x0008
RNS_UD_CS_UNUSED = 0x0010
RNS_UD_CS_VALID_CONNECTION_TYPE = 0x0020
RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU = 0x0040
RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT = 0x0080
RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL = 0x0100
RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE = 0x0200
RNS_UD_CS_SUPPORT_HEARTBEAT_PDU = 0x0400
class ConnectionType(object):
"""
This information is correct if
RNS_UD_CS_VALID_CONNECTION_TYPE flag is set on capabilityFlag
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
CONNECTION_TYPE_MODEM = 0x01
CONNECTION_TYPE_BROADBAND_LOW = 0x02
CONNECTION_TYPE_SATELLITE = 0x03
CONNECTION_TYPE_BROADBAND_HIGH = 0x04
CONNECTION_TYPE_WAN = 0x05
CONNECTION_TYPE_LAN = 0x06
CONNECTION_TYPE_AUTODETECT = 0x07
class Version(object):
"""
Supported version of RDP
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
RDP_VERSION_4 = 0x00080001
RDP_VERSION_5_PLUS = 0x00080004
class Sequence(object):
RNS_UD_SAS_DEL = 0xAA03
class Encryption(object):
"""
Encryption methods supported
@deprecated: because rdpy use SSL but need to send to server...
@see: http://msdn.microsoft.com/en-us/library/cc240511.aspx
"""
ENCRYPTION_FLAG_40BIT = 0x00000001
ENCRYPTION_FLAG_128BIT = 0x00000002
ENCRYPTION_FLAG_56BIT = 0x00000008
FIPS_ENCRYPTION_FLAG = 0x00000010
class ChannelOptions(object):
"""
Channel options
@see: http://msdn.microsoft.com/en-us/library/cc240513.aspx
"""
CHANNEL_OPTION_INITIALIZED = 0x80000000
CHANNEL_OPTION_ENCRYPT_RDP = 0x40000000
CHANNEL_OPTION_ENCRYPT_SC = 0x20000000
CHANNEL_OPTION_ENCRYPT_CS = 0x10000000
CHANNEL_OPTION_PRI_HIGH = 0x08000000
CHANNEL_OPTION_PRI_MED = 0x04000000
CHANNEL_OPTION_PRI_LOW = 0x02000000
CHANNEL_OPTION_COMPRESS_RDP = 0x00800000
CHANNEL_OPTION_COMPRESS = 0x00400000
CHANNEL_OPTION_SHOW_PROTOCOL = 0x00200000
REMOTE_CONTROL_PERSISTENT = 0x00100000
class KeyboardType(object):
"""
Keyboard type
IBM_101_102_KEYS is the most common keyboard type
"""
IBM_PC_XT_83_KEY = 0x00000001
OLIVETTI = 0x00000002
IBM_PC_AT_84_KEY = 0x00000003
IBM_101_102_KEYS = 0x00000004
NOKIA_1050 = 0x00000005
NOKIA_9140 = 0x00000006
JAPANESE = 0x00000007
class KeyboardLayout(object):
"""
Keyboard layout definition
@see: http://technet.microsoft.com/en-us/library/cc766503%28WS.10%29.aspx
"""
ARABIC = 0x00000401
BULGARIAN = 0x00000402
CHINESE_US_KEYBOARD = 0x00000404
CZECH = 0x00000405
DANISH = 0x00000406
GERMAN = 0x00000407
GREEK = 0x00000408
US = 0x00000409
SPANISH = 0x0000040a
FINNISH = 0x0000040b
FRENCH = 0x0000040c
HEBREW = 0x0000040d
HUNGARIAN = 0x0000040e
ICELANDIC = 0x0000040f
ITALIAN = 0x00000410
JAPANESE = 0x00000411
KOREAN = 0x00000412
DUTCH = 0x00000413
NORWEGIAN = 0x00000414
class DataBlock(CompositeType):
"""
Block settings
"""
def __init__(self, dataBlock = None):
CompositeType.__init__(self)
self.type = UInt16Le(lambda:self.dataBlock.__class__._TYPE_)
self.length = UInt16Le(lambda:sizeof(self))
def DataBlockFactory():
"""
build settings in accordance of type self.type.value
"""
for c in [ClientCoreData, ClientSecurityData, ClientNetworkData, ServerCoreData, ServerNetworkData, ServerSecurityData]:
if self.type.value == c._TYPE_:
return c(readLen = self.length - 4)
log.debug("unknown GCC block type : %s"%hex(self.type.value))
#read entire packet
return String(readLen = self.length - 4)
if dataBlock is None:
dataBlock = FactoryType(DataBlockFactory)
elif not "_TYPE_" in dataBlock.__class__.__dict__:
raise InvalidExpectedDataException("Try to send an invalid GCC blocks")
self.dataBlock = dataBlock
class ClientCoreData(CompositeType):
"""
Class that represent core setting of client
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
_TYPE_ = MessageType.CS_CORE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS)
self.desktopWidth = UInt16Le(1280)
self.desktopHeight = UInt16Le(800)
self.colorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP)
self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL)
self.kbdLayout = UInt32Le(KeyboardLayout.FRENCH)
self.clientBuild = UInt32Le(3790)
self.clientName = String("rdpy" + "\x00"*11, readLen = UInt8(32), unicode = True)
self.keyboardType = UInt32Le(KeyboardType.IBM_101_102_KEYS)
self.keyboardSubType = UInt32Le(0)
self.keyboardFnKeys = UInt32Le(12)
self.imeFileName = String("\x00"*64, readLen = UInt8(64))
self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP)
self.clientProductId = UInt16Le(1)
self.serialNumber = UInt32Le(0)
self.highColorDepth = UInt16Le(HighColor.HIGH_COLOR_24BPP)
self.supportedColorDepths = UInt16Le(Support.RNS_UD_15BPP_SUPPORT | Support.RNS_UD_16BPP_SUPPORT | Support.RNS_UD_24BPP_SUPPORT | Support.RNS_UD_32BPP_SUPPORT)
self.earlyCapabilityFlags = UInt16Le(CapabilityFlags.RNS_UD_CS_SUPPORT_ERRINFO_PDU)
self.clientDigProductId = String("\x00"*64, readLen = UInt8(64))
self.connectionType = UInt8()
self.pad1octet = UInt8()
self.serverSelectedProtocol = UInt32Le()
class ServerCoreData(CompositeType):
"""
Server side core settings structure
@see: http://msdn.microsoft.com/en-us/library/cc240517.aspx
"""
_TYPE_ = MessageType.SC_CORE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS)
self.clientRequestedProtocol = UInt32Le()
class ClientSecurityData(CompositeType):
"""
Client security setting
@deprecated: because we use ssl
@see: http://msdn.microsoft.com/en-us/library/cc240511.aspx
"""
_TYPE_ = MessageType.CS_SECURITY
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.encryptionMethods = UInt32Le()
self.extEncryptionMethods = UInt32Le()
class ServerSecurityData(CompositeType):
"""
Server security settings
May be ignored because rdpy don't use
RDP security level
@deprecated: because we use SSL
@see: http://msdn.microsoft.com/en-us/library/cc240518.aspx
"""
_TYPE_ = MessageType.SC_SECURITY
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.encryptionMethod = UInt32Le()
self.encryptionLevel = UInt32Le()
class ChannelDef(CompositeType):
"""
Channels structure share between client and server
@see: http://msdn.microsoft.com/en-us/library/cc240513.aspx
"""
def __init__(self, name = "", options = 0):
CompositeType.__init__(self)
#name of channel
self.name = String(name[0:8] + "\x00" * (8 - len(name)), readLen = UInt8(8))
#unknown
self.options = UInt32Le()
class ClientNetworkData(CompositeType):
"""
GCC client network block
All channels asked by client are listed here
@see: http://msdn.microsoft.com/en-us/library/cc240512.aspx
"""
_TYPE_ = MessageType.CS_NET
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.channelCount = UInt32Le(lambda:len(self.channelDefArray._array))
self.channelDefArray = ArrayType(ChannelDef, readLen = self.channelCount)
class ServerNetworkData(CompositeType):
"""
GCC server network block
All channels asked by client are listed here
@see: All channels asked by client are listed here
"""
_TYPE_ = MessageType.SC_NET
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.MCSChannelId = UInt16Le(mcs.Channel.MCS_GLOBAL_CHANNEL)
self.channelCount = UInt16Le(lambda:len(self.channelIdArray._array))
self.channelIdArray = ArrayType(UInt16Le, readLen = self.channelCount)
self.pad = UInt16Le(conditional = lambda:((self.channelCount.value % 2) == 1))
class Settings(CompositeType):
"""
Class which group all clients settings supported by RDPY
"""
def __init__(self, init = [], readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.settings = ArrayType(DataBlock, [DataBlock(i) for i in init])
def getBlock(self, messageType):
"""
@param messageType: type of block
@return: specific block of type messageType
"""
for i in self.settings._array:
if i.type.value == messageType:
return i.dataBlock
return None
def clientSettings():
"""
Build settings for client
@return: Settings
"""
return Settings([ClientCoreData(), ClientNetworkData(), ClientSecurityData()])
def serverSettings():
"""
Build settings for server
@return Settings
"""
return Settings([ServerCoreData(), ServerSecurityData(), ServerNetworkData()])
def readConferenceCreateRequest(s):
"""
Read a response from client
GCC create request
@param s: Stream
@param client settings (Settings)
"""
per.readChoice(s)
per.readObjectIdentifier(s, t124_02_98_oid)
per.readLength(s)
per.readChoice(s)
per.readSelection(s)
per.readNumericString(s, 1)
per.readPadding(s, 1)
if per.readNumberOfSet(s) != 1:
raise InvalidExpectedDataException("Invalid number of set in readConferenceCreateRequest")
if per.readChoice(s) != 0xc0:
raise InvalidExpectedDataException("Invalid choice in readConferenceCreateRequest")
per.readOctetStream(s, h221_cs_key, 4)
length = per.readLength(s)
clientSettings = Settings(readLen = UInt32Le(length))
s.readType(clientSettings)
return clientSettings
def readConferenceCreateResponse(s):
"""
Read response from server
and return server settings read from this response
@param s: Stream
@return: ServerSettings
"""
per.readChoice(s)
per.readObjectIdentifier(s, t124_02_98_oid)
per.readLength(s)
per.readChoice(s)
per.readInteger16(s, 1001)
per.readInteger(s)
per.readEnumerates(s)
per.readNumberOfSet(s)
per.readChoice(s)
if not per.readOctetStream(s, h221_sc_key, 4):
raise InvalidExpectedDataException("cannot read h221_sc_key")
length = per.readLength(s)
serverSettings = Settings(readLen = UInt32Le(length))
s.readType(serverSettings)
return serverSettings
def writeConferenceCreateRequest(userData):
"""
Write conference create request structure
@param userData: Settings for client
@return: GCC packet
"""
userDataStream = Stream()
userDataStream.writeType(userData)
return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
per.writeLength(len(userDataStream.getvalue()) + 14), per.writeChoice(0),
per.writeSelection(0x08), per.writeNumericString("1", 1), per.writePadding(1),
per.writeNumberOfSet(1), per.writeChoice(0xc0),
per.writeOctetStream(h221_cs_key, 4), per.writeOctetStream(userDataStream.getvalue()))
def writeConferenceCreateResponse(serverData):
"""
Write a conference create response packet
@param serverData: Settings for server
@return: gcc packet
"""
serverDataStream = Stream()
serverDataStream.writeType(serverData)
return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
per.writeLength(len(serverDataStream.getvalue()) + 14), per.writeChoice(0x14),
per.writeInteger16(0x79F3, 1001), per.writeInteger(1), per.writeEnumerates(16),
per.writeNumberOfSet(1), per.writeChoice(0xc0),
per.writeOctetStream(h221_sc_key, 4), per.writeOctetStream(serverDataStream.getvalue()))

View File

@@ -1,571 +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 Multi-Channel Service
Each channel have a particular role.
The main channel is the graphical channel.
It exist channel for file system order, audio channel, clipboard etc...
"""
from rdpy.network.layer import LayerAutomata, IStreamSender, Layer
from rdpy.network.type import sizeof, Stream, UInt8, UInt16Le, String
from rdpy.base.error import InvalidExpectedDataException, InvalidValue, InvalidSize
from rdpy.protocol.rdp.ber import writeLength
import rdpy.base.log as log
import ber, gcc, per
class Message(object):
"""
Message type
"""
MCS_TYPE_CONNECT_INITIAL = 0x65
MCS_TYPE_CONNECT_RESPONSE = 0x66
class DomainMCSPDU:
"""
Domain MCS PDU header
"""
ERECT_DOMAIN_REQUEST = 1
DISCONNECT_PROVIDER_ULTIMATUM = 8
ATTACH_USER_REQUEST = 10
ATTACH_USER_CONFIRM = 11
CHANNEL_JOIN_REQUEST = 14
CHANNEL_JOIN_CONFIRM = 15
SEND_DATA_REQUEST = 25
SEND_DATA_INDICATION = 26
class Channel:
"""
Channel id of main channels use in RDP
"""
MCS_GLOBAL_CHANNEL = 1003
MCS_USERCHANNEL_BASE = 1001
class MCSLayer(LayerAutomata):
"""
Multiple Channel Service layer
the main layer of RDP protocol
is why he can do everything and more!
"""
class MCSProxySender(Layer, IStreamSender):
"""
Proxy use to set as transport layer for upper channel
use to abstract channel id for presentation layer
"""
def __init__(self, mcs, channelId):
"""
@param mcs: MCS layer use as proxy
@param channelId: channel id for presentation layer
"""
self._mcs = mcs
self._channelId = channelId
def send(self, data):
"""
A send proxy function, use channel id and specific
send function of MCS layer
"""
self._mcs.send(self._channelId, data)
def close(self):
"""
Close wrapped layer
"""
self._mcs.close()
def getUserId(self):
"""
@return: mcs user id
"""
return self._mcs._userId
def getChannelId(self):
"""
@return: return channel id of proxy
"""
return self._channelId
def getGCCClientSettings(self):
"""
@return: mcs layer gcc client settings
"""
return self._mcs._clientSettings
def getGCCServerSettings(self):
"""
@return: mcs layer gcc server settings
"""
return self._mcs._serverSettings
def __init__(self, presentation, receiveOpcode, sendOpcode, virtualChannels = []):
"""
@param presentation: presentation layer
@param virtualChannels: list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
@param receiveOpcode: opcode check when receive data
@param sendOpcode: opcode use when send data
"""
LayerAutomata.__init__(self, presentation)
self._clientSettings = gcc.clientSettings()
self._serverSettings = gcc.serverSettings()
#default user Id
self._userId = 1 + Channel.MCS_USERCHANNEL_BASE
#list of channel use in this layer and connection state
self._channels = {Channel.MCS_GLOBAL_CHANNEL: presentation}
#virtual channels
self._virtualChannels = virtualChannels
#send opcode
self._sendOpcode = sendOpcode
#receive opcode
self._receiveOpcode = receiveOpcode
def close(self):
"""
Send disconnect provider ultimatum
"""
self._transport.send((UInt8(self.writeMCSPDUHeader(DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM, 1)),
per.writeEnumerates(0x80), String("\x00" * 6)))
self._transport.close()
def allChannelConnected(self):
"""
All channels are connected to MCS layer
Send connect to upper channel
And prepare MCS layer to receive data
"""
#connection is done
self.setNextState(self.recvData)
#try connection on all requested channel
for (channelId, layer) in self._channels.iteritems():
#use proxy for each channel
layer._transport = MCSLayer.MCSProxySender(self, channelId)
layer.connect()
def send(self, channelId, data):
"""
Specific send function for channelId
@param channelId: Channel use to send
@param data: message to send
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(self._sendOpcode)),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channelId),
UInt8(0x70),
per.writeLength(sizeof(data)), data))
def recvData(self, data):
"""
Main receive method
@param data: Stream
"""
opcode = UInt8()
data.readType(opcode)
if self.readMCSPDUHeader(opcode.value, DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM):
log.info("MCS DISCONNECT_PROVIDER_ULTIMATUM")
self._transport.close()
return
#client case
elif not self.readMCSPDUHeader(opcode.value, self._receiveOpcode):
raise InvalidExpectedDataException("Invalid expected MCS opcode receive data")
#server user id
per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
channelId = per.readInteger16(data)
per.readEnumerates(data)
per.readLength(data)
#channel id doesn't match a requested layer
if not self._channels.has_key(channelId):
log.error("receive data for an unconnected layer")
return
self._channels[channelId].recv(data)
def writeDomainParams(self, maxChannels, maxUsers, maxTokens, maxPduSize):
"""
Write a special domain parameter structure
use in connection sequence
@param maxChannels: number of MCS channel use
@param maxUsers: number of MCS user used (1)
@param maxTokens: unknown
@param maxPduSize: unknown
@return: domain parameter structure
"""
domainParam = (ber.writeInteger(maxChannels), ber.writeInteger(maxUsers), ber.writeInteger(maxTokens),
ber.writeInteger(1), ber.writeInteger(0), ber.writeInteger(1),
ber.writeInteger(maxPduSize), ber.writeInteger(2))
return (ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True), writeLength(sizeof(domainParam)), domainParam)
def writeMCSPDUHeader(self, mcsPdu, options = 0):
"""
Write MCS PDU header
@param mcsPdu: PDU code
@param options: option contains in header
@return: UInt8
"""
return (mcsPdu << 2) | options
def readMCSPDUHeader(self, opcode, mcsPdu):
"""
Read mcsPdu header and return options parameter
@param opcode: opcode
@param mcsPdu: mcsPdu will be checked
@return: true if opcode is correct
"""
return (opcode >> 2) == mcsPdu
def readDomainParams(self, s):
"""
Read domain parameters structure
@return: (max_channels, max_users, max_tokens, max_pdu_size)
"""
if not ber.readUniversalTag(s, ber.Tag.BER_TAG_SEQUENCE, True):
raise InvalidValue("bad BER tags")
ber.readLength(s)#length
max_channels = ber.readInteger(s)
max_users = ber.readInteger(s)
max_tokens = ber.readInteger(s)
ber.readInteger(s)
ber.readInteger(s)
ber.readInteger(s)
max_pdu_size = ber.readInteger(s)
ber.readInteger(s)
return (max_channels, max_users, max_tokens, max_pdu_size)
class Client(MCSLayer):
"""
Client automata of multiple channel service layer
"""
def __init__(self, presentation, virtualChannels = []):
"""
@param presentation: presentation layer
@param virtualChannels: list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
"""
MCSLayer.__init__(self, presentation, DomainMCSPDU.SEND_DATA_INDICATION, DomainMCSPDU.SEND_DATA_REQUEST, virtualChannels)
#use to know state of static channel
self._isGlobalChannelRequested = False
self._isUserChannelRequested = False
#nb channel requested
self._nbChannelRequested = 0
def connect(self):
"""
Connect message in client automata case
Send ConnectInitial
Wait ConnectResponse
"""
self._clientSettings.getBlock(gcc.MessageType.CS_CORE).serverSelectedProtocol.value = self._transport._selectedProtocol
#ask for virtual channel
self._clientSettings.getBlock(gcc.MessageType.CS_NET).channelDefArray._array = [x for (x, _) in self._virtualChannels]
#send connect initial
self.sendConnectInitial()
#next wait response
self.setNextState(self.recvConnectResponse)
def connectNextChannel(self):
"""
Send sendChannelJoinRequest message on next disconnect channel
Send channel request or connect upper layer if all channels are connected
Wait channel confirm
"""
self.setNextState(self.recvChannelJoinConfirm)
#global channel
if not self._isGlobalChannelRequested:
self.sendChannelJoinRequest(Channel.MCS_GLOBAL_CHANNEL)
self._isGlobalChannelRequested = True
return
#user channel
if not self._isUserChannelRequested:
self.sendChannelJoinRequest(self._userId)
self._isUserChannelRequested = True
return
#static virtual channel
if self._nbChannelRequested < self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelCount.value:
channelId = self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray._array[self._nbChannelRequested]
self._nbChannelRequested += 1
self.sendChannelJoinRequest(channelId)
return
self.allChannelConnected()
def recvConnectResponse(self, data):
"""
Receive MCS connect response from server
Send Erect domain Request
Send Attach User Request
Wait Attach User Confirm
@param data: Stream
"""
ber.readApplicationTag(data, UInt8(Message.MCS_TYPE_CONNECT_RESPONSE))
ber.readEnumerated(data)
ber.readInteger(data)
self.readDomainParams(data)
if not ber.readUniversalTag(data, ber.Tag.BER_TAG_OCTET_STRING, False):
raise InvalidExpectedDataException("invalid expected BER tag")
gccRequestLength = ber.readLength(data)
if data.dataLen() != gccRequestLength:
raise InvalidSize("bad size of GCC request")
self._serverSettings = gcc.readConferenceCreateResponse(data)
#send domain request
self.sendErectDomainRequest()
#send attach user request
self.sendAttachUserRequest()
#now wait user confirm from server
self.setNextState(self.recvAttachUserConfirm)
def recvAttachUserConfirm(self, data):
"""
Receive an attach user confirm
Send Connect Channel
@param data: Stream
"""
opcode = UInt8()
data.readType(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ATTACH_USER_CONFIRM):
raise InvalidExpectedDataException("Invalid MCS PDU : ATTACH_USER_CONFIRM expected")
if per.readEnumerates(data) != 0:
raise InvalidExpectedDataException("Server reject user")
self._userId = per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
self.connectNextChannel()
def recvChannelJoinConfirm(self, data):
"""
Receive a channel join confirm from server
client automata function
@param data: Stream
"""
opcode = UInt8()
data.readType(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.CHANNEL_JOIN_CONFIRM):
raise InvalidExpectedDataException("Invalid MCS PDU : CHANNEL_JOIN_CONFIRM expected")
confirm = per.readEnumerates(data)
userId = per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
if self._userId != userId:
raise InvalidExpectedDataException("Invalid MCS User Id")
channelId = per.readInteger16(data)
#must confirm global channel and user channel
if (confirm != 0) and (channelId == Channel.MCS_GLOBAL_CHANNEL or channelId == self._userId):
raise InvalidExpectedDataException("Server must confirm static channel")
if confirm == 0:
serverNet = self._serverSettings.getBlock(gcc.MessageType.SC_NET)
for i in range(0, serverNet.channelCount.value):
if channelId == serverNet.channelIdArray._array[i].value:
self._channels[channelId] = self._virtualChannels[i][1]
self.connectNextChannel()
def sendConnectInitial(self):
"""
Send connect initial packet
client automata function
"""
ccReq = gcc.writeConferenceCreateRequest(self._clientSettings)
ccReqStream = Stream()
ccReqStream.writeType(ccReq)
tmp = (ber.writeOctetstring("\x01"), ber.writeOctetstring("\x01"), ber.writeBoolean(True),
self.writeDomainParams(34, 2, 0, 0xffff),
self.writeDomainParams(1, 1, 1, 0x420),
self.writeDomainParams(0xffff, 0xfc17, 0xffff, 0xffff),
ber.writeOctetstring(ccReqStream.getvalue()))
self._transport.send((ber.writeApplicationTag(Message.MCS_TYPE_CONNECT_INITIAL, sizeof(tmp)), tmp))
def sendErectDomainRequest(self):
"""
Send a formated erect domain request for RDP connection
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ERECT_DOMAIN_REQUEST)),
per.writeInteger(0),
per.writeInteger(0)))
def sendAttachUserRequest(self):
"""
Send a formated attach user request for RDP connection
"""
self._transport.send(self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_REQUEST)))
def sendChannelJoinRequest(self, channelId):
"""
Send a formated Channel join request from client to server
client automata function
@param channelId: id of channel requested
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.CHANNEL_JOIN_REQUEST)),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channelId)))
class Server(MCSLayer):
"""
Server automata of multiple channel service layer
"""
def __init__(self, presentation, virtualChannels = []):
"""
@param presentation: presentation layer
@param virtualChannels: list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
"""
MCSLayer.__init__(self, presentation, DomainMCSPDU.SEND_DATA_REQUEST, DomainMCSPDU.SEND_DATA_INDICATION, virtualChannels)
#nb channel requested
self._nbChannelConfirmed = 0
def connect(self):
"""
Connect message for server automata
Wait Connect Initial
"""
self._serverSettings.getBlock(gcc.MessageType.SC_CORE).clientRequestedProtocol.value = self._transport._requestedProtocol
self.setNextState(self.recvConnectInitial)
def recvConnectInitial(self, data):
"""
Receive MCS connect initial from client
Send Connect Response
Wait Erect Domain Request
@param data: Stream
"""
ber.readApplicationTag(data, UInt8(Message.MCS_TYPE_CONNECT_INITIAL))
ber.readOctetString(data)
ber.readOctetString(data)
if not ber.readBoolean(data):
raise InvalidExpectedDataException("invalid expected BER boolean tag")
self.readDomainParams(data)
self.readDomainParams(data)
self.readDomainParams(data)
self._clientSettings = gcc.readConferenceCreateRequest(Stream(ber.readOctetString(data)))
i = 1
for channelDef in self._clientSettings.getBlock(gcc.MessageType.CS_NET).channelDefArray._array:
self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray._array.append(UInt16Le(i + Channel.MCS_GLOBAL_CHANNEL))
#if channel can be handle by serve add it
for serverChannelDef, layer in self._virtualChannels:
if channelDef.name == serverChannelDef.name:
self._channels[i + Channel.MCS_GLOBAL_CHANNEL] = layer
i += 1
self.sendConnectResponse()
self.setNextState(self.recvErectDomainRequest)
def recvErectDomainRequest(self, data):
"""
Receive erect domain request
Wait Attach User Request
@param data: Stream
"""
opcode = UInt8()
data.readType(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ERECT_DOMAIN_REQUEST):
raise InvalidExpectedDataException("Invalid MCS PDU : ERECT_DOMAIN_REQUEST expected")
per.readInteger(data)
per.readInteger(data)
self.setNextState(self.recvAttachUserRequest)
def recvAttachUserRequest(self, data):
"""
Receive Attach user request
Send Attach User Confirm
Wait Channel Join Request
@param data: Stream
"""
opcode = UInt8()
data.readType(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.ATTACH_USER_REQUEST):
raise InvalidExpectedDataException("Invalid MCS PDU : ATTACH_USER_REQUEST expected")
self.sendAttachUserConfirm()
self.setNextState(self.recvChannelJoinRequest)
def recvChannelJoinRequest(self, data):
"""
Receive for each client channel a request
Send Channel Join Confirm or Connect upper layer when all channel are joined
@param data: Stream
"""
opcode = UInt8()
data.readType(opcode)
if not self.readMCSPDUHeader(opcode.value, DomainMCSPDU.CHANNEL_JOIN_REQUEST):
raise InvalidExpectedDataException("Invalid MCS PDU : CHANNEL_JOIN_REQUEST expected")
userId = per.readInteger16(data, Channel.MCS_USERCHANNEL_BASE)
if self._userId != userId:
raise InvalidExpectedDataException("Invalid MCS User Id")
channelId = per.readInteger16(data)
#actually algo support virtual channel but RDPY have no virtual channel
confirm = 0 if channelId in self._channels.keys() or channelId == self._userId else 1
self.sendChannelJoinConfirm(channelId, confirm)
self._nbChannelConfirmed += 1
if self._nbChannelConfirmed == self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelCount.value + 2:
self.allChannelConnected()
def sendConnectResponse(self):
"""
Send connect response
"""
ccReq = gcc.writeConferenceCreateResponse(self._serverSettings)
ccReqStream = Stream()
ccReqStream.writeType(ccReq)
tmp = (ber.writeEnumerated(0), ber.writeInteger(0), self.writeDomainParams(22, 3, 0, 0xfff8),
ber.writeOctetstring(ccReqStream.getvalue()))
self._transport.send((ber.writeApplicationTag(Message.MCS_TYPE_CONNECT_RESPONSE, sizeof(tmp)), tmp))
def sendAttachUserConfirm(self):
"""
Send attach user confirm
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_CONFIRM), 2),
per.writeEnumerates(0),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE)))
def sendChannelJoinConfirm(self, channelId, confirm):
"""
Send a confirm channel (or not) to client
@param channelId: id of channel
@param confirm: connection state
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.CHANNEL_JOIN_CONFIRM), 2),
per.writeEnumerates(int(confirm)),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channelId),
per.writeInteger16(channelId)))

View File

@@ -1,231 +0,0 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
RDP extended license
@see: http://msdn.microsoft.com/en-us/library/cc241880.aspx
"""
from rdpy.network.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType
from rdpy.base.error import InvalidExpectedDataException
import rdpy.base.log as log
class MessageType(object):
"""
License packet message type
"""
LICENSE_REQUEST = 0x01
PLATFORM_CHALLENGE = 0x02
NEW_LICENSE = 0x03
UPGRADE_LICENSE = 0x04
LICENSE_INFO = 0x12
NEW_LICENSE_REQUEST = 0x13
PLATFORM_CHALLENGE_RESPONSE = 0x15
ERROR_ALERT = 0xFF
class ErrorCode(object):
"""
License error message code
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
"""
ERR_INVALID_SERVER_CERTIFICATE = 0x00000001
ERR_NO_LICENSE = 0x00000002
ERR_INVALID_SCOPE = 0x00000004
ERR_NO_LICENSE_SERVER = 0x00000006
STATUS_VALID_CLIENT = 0x00000007
ERR_INVALID_CLIENT = 0x00000008
ERR_INVALID_PRODUCTID = 0x0000000B
ERR_INVALID_MESSAGE_LEN = 0x0000000C
ERR_INVALID_MAC = 0x00000003
class StateTransition(object):
"""
Automata state transition
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
"""
ST_TOTAL_ABORT = 0x00000001
ST_NO_TRANSITION = 0x00000002
ST_RESET_PHASE_TO_START = 0x00000003
ST_RESEND_LAST_MESSAGE = 0x00000004
class BinaryBlobType(object):
"""
Binary blob data type
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
"""
BB_DATA_BLOB = 0x0001
BB_RANDOM_BLOB = 0x0002
BB_CERTIFICATE_BLOB = 0x0003
BB_ERROR_BLOB = 0x0004
BB_ENCRYPTED_DATA_BLOB = 0x0009
BB_KEY_EXCHG_ALG_BLOB = 0x000D
BB_SCOPE_BLOB = 0x000E
BB_CLIENT_USER_NAME_BLOB = 0x000F
BB_CLIENT_MACHINE_NAME_BLOB = 0x0010
class Preambule(object):
"""
Preambule version
"""
PREAMBLE_VERSION_2_0 = 0x2
PREAMBLE_VERSION_3_0 = 0x3
class LicenseBinaryBlob(CompositeType):
"""
Blob use by license manager to exchange security data
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
"""
def __init__(self, blobType = 0):
CompositeType.__init__(self)
self.wBlobType = UInt16Le(blobType, constant = True)
self.wBlobLen = UInt16Le(lambda:sizeof(self.blobData))
self.blobData = String(readLen = self.wBlobLen)
class LicensingErrorMessage(CompositeType):
"""
License error message
@see: http://msdn.microsoft.com/en-us/library/cc240482.aspx
"""
_MESSAGE_TYPE_ = MessageType.ERROR_ALERT
def __init__(self):
CompositeType.__init__(self)
self.dwErrorCode = UInt32Le()
self.dwStateTransition = UInt32Le()
self.blob = LicenseBinaryBlob(BinaryBlobType.BB_ERROR_BLOB)
class ProductInformation(CompositeType):
"""
License server product information
@see: http://msdn.microsoft.com/en-us/library/cc241915.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.dwVersion = UInt32Le()
self.cbCompanyName = UInt32Le(lambda:sizeof(self.pbCompanyName))
#may contain "Microsoft Corporation" from server microsoft
self.pbCompanyName = String(readLen = self.cbCompanyName)
self.cbProductId = UInt32Le(lambda:sizeof(self.pbProductId))
#may contain "A02" from microsoft license server
self.pbProductId = String(readLen = self.cbProductId)
class Scope(CompositeType):
"""
Use in license nego
@see: http://msdn.microsoft.com/en-us/library/cc241917.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.scope = LicenseBinaryBlob(BinaryBlobType.BB_SCOPE_BLOB)
class ScopeList(CompositeType):
"""
Use in license nego
@see: http://msdn.microsoft.com/en-us/library/cc241916.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.scopeCount = UInt32Le(lambda:sizeof(self.scopeArray))
self.scopeArray = ArrayType(Scope, readLen = self.scopeCount)
class ServerLicenseRequest(CompositeType):
"""
Send by server to signal license request
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc241914.aspx
"""
_MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST
def __init__(self):
CompositeType.__init__(self)
self.serverRandom = String(readLen = UInt8(32))
self.productInfo = ProductInformation()
self.keyExchangeList = LicenseBinaryBlob(BinaryBlobType.BB_KEY_EXCHG_ALG_BLOB)
self.serverCertificate = LicenseBinaryBlob(BinaryBlobType.BB_CERTIFICATE_BLOB)
self.scopeList = ScopeList()
class ClientNewLicenseRequest(CompositeType):
"""
Send by client to ask new license for client.
RDPY doesn'support license reuse, need it in futur version
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
"""
_MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST
def __init__(self):
CompositeType.__init__(self)
#RSA and must be only RSA
self.preferredKeyExchangeAlg = UInt32Le(0x00000001, constant = True)
#pure microsoft client ;-)
#http://msdn.microsoft.com/en-us/library/1040af38-c733-4fb3-acd1-8db8cc979eda#id10
self.platformId = UInt32Le(0x04000000 | 0x00010000)
self.clientRandom = String("\x00" * 32)
self.encryptedPreMasterSecret = LicenseBinaryBlob(BinaryBlobType.BB_RANDOM_BLOB)
self.ClientUserName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_USER_NAME_BLOB)
self.ClientMachineName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_MACHINE_NAME_BLOB)
class LicPacket(CompositeType):
"""
A license packet
"""
def __init__(self, message = None):
CompositeType.__init__(self)
#preambule
self.bMsgtype = UInt8(lambda:self.licensingMessage.__class__._MESSAGE_TYPE_)
self.flag = UInt8(Preambule.PREAMBLE_VERSION_3_0)
self.wMsgSize = UInt16Le(lambda: sizeof(self))
def LicensingMessageFactory():
"""
factory for message nego
Use in read mode
"""
for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest]:
if self.bMsgtype.value == c._MESSAGE_TYPE_:
return c()
log.debug("unknown license message : %s"%self.bMsgtype.value)
return String()
if message is None:
message = FactoryType(LicensingMessageFactory)
elif not "_MESSAGE_TYPE_" in message.__class__.__dict__:
raise InvalidExpectedDataException("Try to send an invalid license message")
self.licensingMessage = message
def createValidClientLicensingErrorMessage():
"""
Create a licensing error message that accept client
server automata message
"""
message = LicensingErrorMessage()
message.dwErrorCode.value = ErrorCode.STATUS_VALID_CLIENT
message.dwStateTransition.value = StateTransition.ST_NO_TRANSITION
return LicPacket(message = message)
def createNewLicenseRequest(serverLicenseRequest):
"""
Create new license request in response to server license request
@see: http://msdn.microsoft.com/en-us/library/cc241989.aspx
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
@todo: need RDP license server
"""
return LicPacket(message = ClientNewLicenseRequest())

View File

@@ -1,174 +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/>.
#
"""
Transport packet layer implementation
Use to build correct size packet and handle slow path and fast path mode
"""
from rdpy.network.layer import RawLayer
from rdpy.network.type import UInt8, UInt16Be, sizeof
from rdpy.base.error import CallPureVirtualFuntion
class Action(object):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240621.aspx
@see: http://msdn.microsoft.com/en-us/library/cc240589.aspx
"""
FASTPATH_ACTION_FASTPATH = 0x0
FASTPATH_ACTION_X224 = 0x3
class IFastPathListener(object):
"""
@summary: Fast path packet listener
Usually X224 layer
"""
def recvFastPath(self, fastPathS):
"""
@summary: Call when fast path packet is received
@param fastPathS: Stream
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recvFastPath", "recvFastPath"))
def setFastPathSender(self, fastPathSender):
"""
@summary: Call to set a fast path sender to listener
@param fastPathSender: IFastPathSender
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "setFastPathSender", "recvFastPath"))
class IFastPathSender(object):
"""
@summary: Fast path send capability
"""
def sendFastPath(self, fastPathS):
"""
@summary: Send fastPathS Type as fast path packet
@param fastPathS: type transform to stream and send as fastpath
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendFastPath", "IFastPathSender"))
class TPKT(RawLayer, IFastPathSender):
"""
@summary: TPKT layer in RDP protocol stack
represent the Raw Layer in stack (first layer)
This layer only handle size of packet and determine if is a fast path packet
"""
def __init__(self, presentation, fastPathListener = None):
"""
@param presentation: presentation layer, in RDP case is x224 layer
@param fastPathListener: IFastPathListener
"""
RawLayer.__init__(self, presentation)
#last packet version read from header
self._lastPacketVersion = UInt8()
#length may be coded on more than 1 bytes
self._lastShortLength = UInt8()
#fast path listener
self._fastPathListener = fastPathListener
if not fastPathListener is None:
#set me as fast path sender
fastPathListener.setFastPathSender(self)
def connect(self):
"""
@summary: Call when transport layer connection
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):
"""
Read header of TPKT packet
@param data: Stream received from twisted layer
"""
#first read packet version
data.readType(self._lastPacketVersion)
#classic packet
if self._lastPacketVersion.value == Action.FASTPATH_ACTION_X224:
#padding
data.readType(UInt8())
#read end header
self.expect(2, self.readExtendedHeader)
else:
#is fast path packet
data.readType(self._lastShortLength)
if self._lastShortLength.value & 0x80:
#size is 1 byte more
self.expect(1, self.readExtendedFastPathHeader)
return
self.expect(self._lastShortLength.value - 2, self.readFastPath)
def readExtendedHeader(self, data):
"""
Header may be on 4 bytes
@param data: Stream from twisted layer
"""
#next state is read data
size = UInt16Be()
data.readType(size)
self.expect(size.value - 4, self.readData)
def readExtendedFastPathHeader(self, data):
"""
Fast path header may be on 1 byte more
@param data: Stream from twisted layer
"""
leftPart = UInt8()
data.readType(leftPart)
self._lastShortLength.value &= ~0x80
packetSize = (self._lastShortLength.value << 8) + leftPart.value
#next state is fast patn data
self.expect(packetSize - 3, self.readFastPath)
def readFastPath(self, data):
"""
Fast path data
@param data: Stream from twisted layer
"""
self._fastPathListener.recvFastPath(data)
self.expect(2, self.readHeader)
def readData(self, data):
"""
Read classic TPKT packet, last state in tpkt automata
@param data: Stream with correct size
"""
#next state is pass to
self._presentation.recv(data)
self.expect(2, self.readHeader)
def send(self, message):
"""
Send encompassed data
@param message: network.Type message to send
"""
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_X224), UInt8(0), UInt16Be(sizeof(message) + 4), message))
def sendFastPath(self, fastPathS):
"""
@param fastPathS: type transform to stream and send as fastpath
"""
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS))

View File

@@ -1,311 +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 transport PDU layer
This layer have main goal to negociate SSL transport
RDP basic security is not supported by RDPY (because is not a true security layer...)
"""
from rdpy.network.layer import LayerAutomata, IStreamSender
from rdpy.network.type import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, String
from rdpy.base.error import InvalidExpectedDataException
class MessageType(object):
"""
@summary: Message type
"""
X224_TPDU_CONNECTION_REQUEST = 0xE0
X224_TPDU_CONNECTION_CONFIRM = 0xD0
X224_TPDU_DISCONNECT_REQUEST = 0x80
X224_TPDU_DATA = 0xF0
X224_TPDU_ERROR = 0x70
class NegociationType(object):
"""
@summary: Negotiation header
"""
TYPE_RDP_NEG_REQ = 0x01
TYPE_RDP_NEG_RSP = 0x02
TYPE_RDP_NEG_FAILURE = 0x03
class Protocols(object):
"""
@summary: Protocols available for x224 layer
"""
PROTOCOL_RDP = 0x00000000
PROTOCOL_SSL = 0x00000001
PROTOCOL_HYBRID = 0x00000002
PROTOCOL_HYBRID_EX = 0x00000008
class NegotiationFailureCode(object):
"""
@summary: Protocol negotiation failure code
"""
SSL_REQUIRED_BY_SERVER = 0x00000001
SSL_NOT_ALLOWED_BY_SERVER = 0x00000002
SSL_CERT_NOT_ON_SERVER = 0x00000003
INCONSISTENT_FLAGS = 0x00000004
HYBRID_REQUIRED_BY_SERVER = 0x00000005
SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 0x00000006
class ClientConnectionRequestPDU(CompositeType):
"""
@summary: Connection request
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240470.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.len = UInt8(lambda:sizeof(self) - 1)
self.code = UInt8(MessageType.X224_TPDU_CONNECTION_REQUEST, constant = True)
self.padding = (UInt16Be(), UInt16Be(), UInt8())
self.cookie = String(until = "\x0d\x0a", conditional = lambda:(self.len._is_readed and self.len.value > 14))
#read if there is enough data
self.protocolNeg = Negotiation(optional = True)
class ServerConnectionConfirm(CompositeType):
"""
@summary: Server response
@see: http://msdn.microsoft.com/en-us/library/cc240506.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.len = UInt8(lambda:sizeof(self) - 1)
self.code = UInt8(MessageType.X224_TPDU_CONNECTION_CONFIRM, constant = True)
self.padding = (UInt16Be(), UInt16Be(), UInt8())
#read if there is enough data
self.protocolNeg = Negotiation(optional = True)
class X224DataHeader(CompositeType):
"""
@summary: Header send when x224 exchange application data
"""
def __init__(self):
CompositeType.__init__(self)
self.header = UInt8(2)
self.messageType = UInt8(MessageType.X224_TPDU_DATA, constant = True)
self.separator = UInt8(0x80, constant = True)
class Negotiation(CompositeType):
"""
@summary: Negociate request message
@see: request -> http://msdn.microsoft.com/en-us/library/cc240500.aspx
@see: response -> http://msdn.microsoft.com/en-us/library/cc240506.aspx
@see: failure ->http://msdn.microsoft.com/en-us/library/cc240507.aspx
"""
def __init__(self, optional = False):
CompositeType.__init__(self, optional = optional)
self.code = UInt8()
self.flag = UInt8(0)
#always 8
self.len = UInt16Le(0x0008, constant = True)
self.selectedProtocol = UInt32Le(conditional = lambda: (self.code.value != NegociationType.TYPE_RDP_NEG_FAILURE))
self.failureCode = UInt32Le(conditional = lambda: (self.code.value == NegociationType.TYPE_RDP_NEG_FAILURE))
class X224Layer(LayerAutomata, IStreamSender):
"""
@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)
#default selectedProtocol is SSl because is the only supported
#in this version of RDPY
#client requested selectedProtocol
self._requestedProtocol = Protocols.PROTOCOL_SSL
#server selected selectedProtocol
self._selectedProtocol = Protocols.PROTOCOL_SSL
def recvData(self, data):
"""
@summary: Read data header from packet
And pass to presentation layer
@param data: Stream
"""
header = X224DataHeader()
data.readType(header)
self._presentation.recv(data)
def send(self, message):
"""
@summary: Write message packet for TPDU layer
Add TPDU header
@param message: network.Type message
"""
self._transport.send((X224DataHeader(), message))
class Client(X224Layer):
"""
@summary: Client automata of TPDU layer
"""
def __init__(self, presentation):
"""
@param presentation: upper layer, MCS layer in RDP case
"""
X224Layer.__init__(self, presentation)
def connect(self):
"""
@summary: Connection request for client send a connection request packet
"""
self.sendConnectionRequest()
def sendConnectionRequest(self):
"""
@summary: Write connection request message
Next state is recvConnectionConfirm
@see: http://msdn.microsoft.com/en-us/library/cc240500.aspx
"""
message = ClientConnectionRequestPDU()
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_REQ
message.protocolNeg.selectedProtocol.value = self._requestedProtocol
self._transport.send(message)
self.setNextState(self.recvConnectionConfirm)
def recvConnectionConfirm(self, data):
"""
@summary: Receive connection confirm message
Next state is recvData
Call connect on presentation layer if all is good
@param data: Stream that contain connection confirm
@see: response -> http://msdn.microsoft.com/en-us/library/cc240506.aspx
@see: failure ->http://msdn.microsoft.com/en-us/library/cc240507.aspx
"""
message = ServerConnectionConfirm()
data.readType(message)
#check presence of negotiation response
if not message.protocolNeg._is_readed:
raise InvalidExpectedDataException("server must support negotiation protocol to use SSL")
if message.protocolNeg.failureCode._is_readed:
raise InvalidExpectedDataException("negotiation failure code %x"%message.protocolNeg.failureCode.value)
self._selectedProtocol = message.protocolNeg.selectedProtocol.value
if self._selectedProtocol != Protocols.PROTOCOL_SSL:
raise InvalidExpectedDataException("only SSL protocol is supported in RDPY version")
#_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
"""
def __init__(self, presentation, privateKeyFileName, certificateFileName):
"""
@param presentation: upper layer, MCS layer in RDP case
@param privateKeyFileName: file contain server private key
@param certficiateFileName: file that contain public key
"""
X224Layer.__init__(self, presentation)
#Server mode informations for TLS connection
self._serverPrivateKeyFileName = privateKeyFileName
self._serverCertificateFileName = certificateFileName
def connect(self):
"""
@summary: Connection request for server wait connection request packet from client
"""
self.setNextState(self.recvConnectionRequest)
def recvConnectionRequest(self, data):
"""
@summary: Read connection confirm packet
Next state is send connection confirm
@param data: Stream
@see : http://msdn.microsoft.com/en-us/library/cc240470.aspx
"""
message = ClientConnectionRequestPDU()
data.readType(message)
if not message.protocolNeg._is_readed or message.protocolNeg.failureCode._is_readed:
raise InvalidExpectedDataException("Too older RDP client")
self._requestedProtocol = message.protocolNeg.selectedProtocol.value
if not self._requestedProtocol & Protocols.PROTOCOL_SSL:
#send error message and quit
message = ServerConnectionConfirm()
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_FAILURE
message.protocolNeg.failureCode.value = NegotiationFailureCode.SSL_REQUIRED_BY_SERVER
self._transport.send(message)
raise InvalidExpectedDataException("rdpy needs ssl client compliant")
self._selectedProtocol = Protocols.PROTOCOL_SSL
self.sendConnectionConfirm()
def sendConnectionConfirm(self):
"""
@summary: Write connection confirm message
Start TLS connection
Next state is recvData
@see : http://msdn.microsoft.com/en-us/library/cc240501.aspx
"""
message = ServerConnectionConfirm()
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_RSP
message.protocolNeg.selectedProtocol.value = self._selectedProtocol
self._transport.send(message)
#_transport is TPKT and transport is TCP layer of twisted
self._transport.transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
#connection is done send to presentation
self.setNextState(self.recvData)
self._presentation.connect()
#open ssl needed
from twisted.internet import ssl
from OpenSSL import SSL
class ClientTLSContext(ssl.ClientContextFactory):
"""
@summary: client context factory for open ssl
"""
def getContext(self):
context = SSL.Context(SSL.TLSv1_METHOD)
context.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
context.set_options(SSL.OP_TLS_BLOCK_PADDING_BUG)
return context
class ServerTLSContext(ssl.DefaultOpenSSLContextFactory):
"""
@summary: Server context factory for open ssl
@param privateKeyFileName: Name of a file containing a private key
@param certificateFileName: Name of a file containing a certificate
"""
def __init__(self, privateKeyFileName, certificateFileName):
class TPDUSSLContext(SSL.Context):
def __init__(self, method):
SSL.Context.__init__(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)

View File

@@ -1,764 +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.network.layer import RawLayer, RawLayerClientFactory
from rdpy.network.type import UInt8, UInt16Be, UInt32Be, SInt32Be, String, CompositeType
from rdpy.base.error import InvalidValue, CallPureVirtualFuntion
from rdpy.protocol.rfb.pyDes import des
import rdpy.base.log as log
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):
"""
@summary: Override this method to handle connection lost
@param rfblayer: rfblayer that cause connectionLost event
"""
#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"))

59
rdpy/security/rc4.py Normal file
View File

@@ -0,0 +1,59 @@
"""
Copyright (C) 2012 Bo Zhu http://about.bozhu.me
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
def KSA(key):
keylength = len(key)
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % keylength]) % 256
S[i], S[j] = S[j], S[i] # swap
return S
def PRGA(S):
i = 0
j = 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i] # swap
K = S[(S[i] + S[j]) % 256]
yield K
def RC4(key):
S = KSA(key)
return PRGA(S)
def RC4Key(key):
return RC4(key)
def crypt(keystream, plaintext: bytes) -> bytes:
return bytes([c ^ next(keystream) for c in plaintext])

View File

@@ -0,0 +1,99 @@
#
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Wrapper around RSA library
"""
import rsa
def newkeys(size):
"""
@summary: wrapper around rsa.newkeys function
@param size: {integer} size of key
"""
return rsa.newkeys(size)
def PublicKey(e, n):
"""
@param e: {long | str}public exponent
@param n: {long | str}modulus
"""
if isinstance(e, str):
e = rsa.transform.bytes2int(e)
if isinstance(n, str):
n = rsa.transform.bytes2int(n)
return { 'e' : e, 'n' : n }
def PrivateKey(d, n):
"""
@param d: {long | str}private exponent
@param n: {long | str}modulus
"""
if isinstance(d, str):
d = rsa.transform.bytes2int(d)
if isinstance(n, str):
n = rsa.transform.bytes2int(n)
return { 'd' : d, 'n' : n }
def int2bytes(i, fill_size=None):
"""
@summary: wrapper of rsa.transform.int2bytes
"""
return rsa.transform.int2bytes(i,fill_size)
def random(size):
"""
@summary: wrapper around rsa.randnum.read_random_bits function
@param size: {integer] size in bits
@return: {str} random bytes array
"""
return rsa.randnum.read_random_bits(size)
def encrypt(message, publicKey):
"""
@summary: wrapper around rsa.core.encrypt_int function
@param message: {str} source message
@param publicKey: {rsa.PublicKey}
"""
return rsa.transform.int2bytes(rsa.core.encrypt_int(rsa.transform.bytes2int(message), publicKey['e'], publicKey['n']), rsa.common.byte_size(publicKey['n']))
def decrypt(message, privateKey):
"""
@summary: wrapper around rsa.core.decrypt_int function
@param message: {str} source message
@param publicKey: {rsa.PrivateKey}
"""
return rsa.transform.int2bytes(rsa.core.decrypt_int(rsa.transform.bytes2int(message), privateKey['d'], privateKey['n']))
def sign(message, privateKey):
"""
@summary: sign message with private key
@param message: {str} message to sign
@param privateKey : {rsa.privateKey} key use to sugn
"""
return rsa.transform.int2bytes(rsa.core.encrypt_int(rsa.transform.bytes2int(message), privateKey['d'], privateKey['n']), rsa.common.byte_size(privateKey['n']))
def verify(message, publicKey):
"""
@summary: return hash
@param message: {str} message to verify
@param publicKey : {rsa.publicKey} key use to sugn
"""
return rsa.transform.int2bytes(rsa.core.decrypt_int(rsa.transform.bytes2int(message), publicKey['e'], publicKey['n']))

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

@@ -0,0 +1,157 @@
#
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
@see: https://github.com/filippog/pyasn1/blob/master/examples/x509.py
"""
from pyasn1.type import tag, namedtype, namedval, univ, constraint, char, useful
from pyasn1.codec.ber import decoder
MAX = 64
class DirectoryString(univ.Choice):
componentType = namedtype.NamedTypes(
namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType('ia5String', char.IA5String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))) # hm, this should not be here!? XXX
)
class AttributeValue(DirectoryString): pass
class AttributeType(univ.ObjectIdentifier): pass
class AttributeTypeAndValue(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('type', AttributeType()),
namedtype.NamedType('value', AttributeValue())
)
class RelativeDistinguishedName(univ.SetOf):
componentType = AttributeTypeAndValue()
class RDNSequence(univ.SequenceOf):
componentType = RelativeDistinguishedName()
class Name(univ.Choice):
componentType = namedtype.NamedTypes(
namedtype.NamedType('', RDNSequence())
)
class AlgorithmIdentifier(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('algorithm', univ.ObjectIdentifier()),
namedtype.OptionalNamedType('parameters', univ.Null())
# XXX syntax screwed?
# namedtype.OptionalNamedType('parameters', univ.ObjectIdentifier())
)
class Extension(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('extnID', univ.ObjectIdentifier()),
namedtype.DefaultedNamedType('critical', univ.Boolean('False')),
namedtype.NamedType('extnValue', univ.OctetString())
)
class Extensions(univ.SequenceOf):
componentType = Extension()
sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)
class SubjectPublicKeyInfo(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('algorithm', AlgorithmIdentifier()),
namedtype.NamedType('subjectPublicKey', univ.BitString())
)
class UniqueIdentifier(univ.BitString): pass
class Time(univ.Choice):
componentType = namedtype.NamedTypes(
namedtype.NamedType('utcTime', useful.UTCTime()),
namedtype.NamedType('generalTime', useful.GeneralizedTime())
)
class Validity(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('notBefore', Time()),
namedtype.NamedType('notAfter', Time())
)
class CertificateSerialNumber(univ.Integer): pass
class Version(univ.Integer):
namedValues = namedval.NamedValues(
('v1', 0), ('v2', 1), ('v3', 2)
)
class TBSCertificate(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.DefaultedNamedType('version', Version('v1', tagSet=Version.tagSet.tagExplicitly(tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))),
namedtype.NamedType('serialNumber', CertificateSerialNumber()),
namedtype.NamedType('signature', AlgorithmIdentifier()),
namedtype.NamedType('issuer', Name()),
namedtype.NamedType('validity', Validity()),
namedtype.NamedType('subject', Name()),
namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()),
namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
namedtype.OptionalNamedType('extensions', Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)
class X509Certificate(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('tbsCertificate', TBSCertificate()),
namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
namedtype.NamedType('signatureValue', univ.BitString())
)
class RSAPublicKey(univ.Sequence):
"""
@summary: asn1 public rsa key
@see: https://tools.ietf.org/html/rfc3447
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('modulus', univ.Integer()),
namedtype.NamedType('publicExponent', univ.Integer()),
)
def load(stream):
"""
@summary: load X509 certificate
@param stream: {str}
"""
return decoder.decode(stream, asn1Spec=X509Certificate())[0]
def extractRSAKey(certificate):
"""
@summary: try to extract rsa key
@return: (modulus, public exponent)
"""
#http://www.alvestrand.no/objectid/1.2.840.113549.1.1.1.html
binaryTuple = certificate.getComponentByName('tbsCertificate').getComponentByName('subjectPublicKeyInfo').getComponentByName('subjectPublicKey')
l = int("".join([str(i) for i in binaryTuple]), 2)
return extractRSAKeyFromASN1(hex(l)[2:-1].decode('hex'))
def extractRSAKeyFromASN1(subjectPublicKey):
rsaKey = decoder.decode(subjectPublicKey, asn1Spec=RSAPublicKey())[0]
return rsaKey.getComponentByName('modulus')._value , rsaKey.getComponentByName('publicExponent')._value

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -23,23 +23,22 @@ Qt specific code
QRemoteDesktop is a widget use for render in rdpy
"""
from PyQt4 import QtGui, QtCore
from rdpy.protocol.rfb.rfb import RFBClientObserver
from rdpy.protocol.rdp.rdp import RDPClientObserver
from rdpy.base.error import CallPureVirtualFuntion
from PyQt5 import QtWidgets
from rdpy.core.rdp import RDPClientObserver
from rdpy.model.error import CallPureVirtualFuntion
import sys
import rdpy.base.log as log
import rdpy.model.log as log
import rle
class QAdaptor(object):
"""
Adaptor model with link between protocol
And Qt widget
@summary: Adaptor model with link between protocol
And Qt widget
"""
def sendMouseEvent(self, e, isPressed):
"""
Interface to send mouse event to protocol stack
@summary: Interface to send mouse event to protocol stack
@param e: QMouseEvent
@param isPressed: event come from press or release action
"""
@@ -47,21 +46,22 @@ class QAdaptor(object):
def sendKeyEvent(self, e, isPressed):
"""
Interface to send key event to protocol stack
@summary: Interface to send key event to protocol stack
@param e: QEvent
@param isPressed: event come from press or release action
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendKeyEvent", "QAdaptor"))
def getWidget(self):
def sendWheelEvent(self, e):
"""
@return: widget use for render
@summary: Interface to send wheel event to protocol stack
@param e: QWheelEvent
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getWidget", "QAdaptor"))
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendWheelEvent", "QAdaptor"))
def closeEvent(self, e):
"""
Call when you want to close connection
@summary: Call when you want to close connection
@param: QCloseEvent
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "closeEvent", "QAdaptor"))
@@ -71,110 +71,14 @@ def qtImageFormatFromRFBPixelFormat(pixelFormat):
@summary: convert RFB pixel format to QtGui.QImage format
"""
if pixelFormat.BitsPerPixel.value == 32:
return QtGui.QImage.Format_RGB32
return QtWidgets.QImage.Format_RGB32
elif pixelFormat.BitsPerPixel.value == 16:
return QtGui.QImage.Format_RGB16
return QtWidgets.QImage.Format_RGB16
class RFBClientQt(RFBClientObserver, QAdaptor):
def RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data):
"""
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):
"""
@return: widget use for render
"""
return self._widget
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
"""
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
"""
pass
def onBell(self):
"""
@summary: event when server send biiip
"""
pass
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):
"""
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):
"""
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 closeEvent(self, e):
"""
Call when you want to close connection
@param: QCloseEvent
"""
self._controller.close()
def onClose(self):
"""
Call when stack is close
"""
#do something maybe a message
pass
def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
"""
Bitmap transformation to Qt object
@summary: Bitmap transformation to Qt object
@param width: width of bitmap
@param height: height of bitmap
@param bitsPerPixel: number of bit per pixel
@@ -188,50 +92,50 @@ def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
if isCompress:
buf = bytearray(width * height * 2)
rle.bitmap_decompress(buf, width, height, data, 2)
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB555)
image = QtWidgets.QImage(buf, width, height, QtWidgets.QImage.Format_RGB555)
else:
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB555).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
image = QtWidgets.QImage(data, width, height, QtWidgets.QImage.Format_RGB555).transformed(QtWidgets.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
elif bitsPerPixel == 16:
if isCompress:
buf = bytearray(width * height * 2)
rle.bitmap_decompress(buf, width, height, data, 2)
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB16)
image = QtWidgets.QImage(buf, width, height, QtWidgets.QImage.Format_RGB16)
else:
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB16).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
image = QtWidgets.QImage(data, width, height, QtWidgets.QImage.Format_RGB16).transformed(QtWidgets.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
elif bitsPerPixel == 24:
if isCompress:
buf = bytearray(width * height * 3)
rle.bitmap_decompress(buf, width, height, data, 3)
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB24)
image = QtWidgets.QImage(buf, width, height, QtWidgets.QImage.Format_RGB888)
else:
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB24).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
image = QtWidgets.QImage(data, width, height, QtWidgets.QImage.Format_RGB888).transformed(QtWidgets.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
elif bitsPerPixel == 32:
if isCompress:
buf = bytearray(width * height * 4)
rle.bitmap_decompress(buf, width, height, data, 4)
image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB24)
image = QtWidgets.QImage(buf, width, height, QtWidgets.QImage.Format_RGB32)
else:
image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB32).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
image = QtWidgets.QImage(data, width, height, QtWidgets.QImage.Format_RGB32).transformed(QtWidgets.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))
else:
log.error("Receive image in bad format")
image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
image = QtWidgets.QImage(width, height, QtWidgets.QImage.Format_RGB32)
return image
class RDPClientQt(RDPClientObserver, QAdaptor):
"""
Adaptor for RDP client
@summary: Adaptor for RDP client
"""
def __init__(self, controller, width, height):
"""
@param controller: RDP controller
@param width: width of widget
@param height: height of widget
@param controller: {RDPClientController} RDP controller
@param width: {int} width of widget
@param height: {int} height of widget
"""
RDPClientObserver.__init__(self, controller)
self._widget = QRemoteDesktop(self, width, height)
self._widget = QRemoteDesktop(width, height, self)
#set widget screen to RDP stack
controller.setScreen(width, height)
@@ -243,7 +147,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
def sendMouseEvent(self, e, isPressed):
"""
Convert Qt mouse event to RDP mouse event
@summary: Convert Qt mouse event to RDP mouse event
@param e: qMouseEvent
@param isPressed: event come from press(true) or release(false) action
"""
@@ -251,15 +155,15 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
buttonNumber = 0
if button == QtCore.Qt.LeftButton:
buttonNumber = 1
elif button == QtCore.Qt.MidButton:
buttonNumber = 2
elif button == QtCore.Qt.RightButton:
buttonNumber = 2
elif button == QtCore.Qt.MidButton:
buttonNumber = 3
self._controller.sendPointerEvent(e.pos().x(), e.pos().y(), buttonNumber, isPressed)
def sendKeyEvent(self, e, isPressed):
"""
Convert Qt key press event to RFB press event
@summary: Convert Qt key press event to RDP press event
@param e: QKeyEvent
@param isPressed: event come from press or release action
"""
@@ -268,136 +172,157 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
code -= 8
self._controller.sendKeyEventScancode(code, isPressed)
def sendWheelEvent(self, e):
"""
@summary: Convert Qt wheel event to RDP Wheel event
@param e: QKeyEvent
@param isPressed: event come from press or release action
"""
self._controller.sendWheelEvent(e.pos().x(), e.pos().y(), (abs(e.delta()) / 8) / 15, e.delta() < 0, e.orientation() == QtCore.Qt.Horizontal)
def closeEvent(self, e):
"""
Convert Qt close widget event into close stack event
@summary: Convert Qt close widget event into close stack event
@param e: QCloseEvent
"""
self._controller.close()
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
Notify bitmap update
@param destLeft: xmin position
@param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding
@param destBottom: ymax position because RDP can send bitmap with padding
@param width: width of bitmap
@param height: height of bitmap
@param bitsPerPixel: number of bit per pixel
@param isCompress: use RLE compression
@param data: bitmap data
@summary: Notify bitmap update
@param destLeft: {int} xmin position
@param destTop: {int} ymin position
@param destRight: {int} xmax position because RDP can send bitmap with padding
@param destBottom: {int} ymax position because RDP can send bitmap with padding
@param width: {int} width of bitmap
@param height: {int} height of bitmap
@param bitsPerPixel: {int} number of bit per pixel
@param isCompress: {bool} use RLE compression
@param data: {str} bitmap data
"""
image = RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data);
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data)
#if image need to be cut
#For bit alignement server may send more than image pixel
self._widget.notifyImage(destLeft, destTop, image, destRight - destLeft + 1, destBottom - destTop + 1)
def onReady(self):
"""
Call when stack is ready
@summary: Call when stack is ready
@see: rdp.RDPClientObserver.onReady
"""
#do something maybe a loader
def onSessionReady(self):
"""
@summary: Windows session is ready
@see: rdp.RDPClientObserver.onSessionReady
"""
pass
def onClose(self):
"""
Call when stack is close
@summary: Call when stack is close
@see: rdp.RDPClientObserver.onClose
"""
#do something maybe a message
pass
class QRemoteDesktop(QtGui.QWidget):
class QRemoteDesktop(QtWidgets.QWidget):
"""
Qt display widget
@summary: Qt display widget
"""
def __init__(self, adaptor, width, height):
def __init__(self, width, height, adaptor):
"""
@param adaptor: QAdaptor
@param adaptor: {QAdaptor}
@param width: {int} width of widget
@param height: {int} height of widget
"""
super(QRemoteDesktop, self).__init__()
#adaptor use to send
self._adaptor = adaptor
#set correct size
self.resize(width, height)
#refresh stack of image
#because we can update image only in paint
#event function. When protocol receive image
#we will stock into refresh list
#and in paint event paint list of all refresh images
self._refresh = []
#bind mouse event
self.setMouseTracking(True)
#buffer image
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
self._buffer = QtWidgets.QImage(width, height, QtWidgets.QImage.Format_RGB32)
def notifyImage(self, x, y, qimage, width, height):
"""
Function call from QAdaptor
@summary: Function call from QAdaptor
@param x: x position of new image
@param y: y position of new image
@param qimage: new QImage
"""
#save in refresh list (order is important)
self._refresh.append((x, y, qimage, width, height))
#fill buffer image
with QtWidgets.QPainter(self._buffer) as qp:
qp.drawImage(x, y, qimage, 0, 0, width, height)
#force update
self.update()
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):
"""
Call when Qt renderer engine estimate that is needed
@summary: Call when Qt renderer engine estimate that is needed
@param e: QEvent
"""
#fill buffer image
with QtGui.QPainter(self._buffer) as qp:
#draw image
for (x, y, image, width, height) in self._refresh:
qp.drawImage(x, y, image, 0, 0, width, height)
#draw in widget
with QtGui.QPainter(self) as qp:
with QtWidgets.QPainter(self) as qp:
qp.drawImage(0, 0, self._buffer)
self._refresh = []
def mouseMoveEvent(self, event):
"""
Call when mouse move
@summary: Call when mouse move
@param event: QMouseEvent
"""
self._adaptor.sendMouseEvent(event, False)
def mousePressEvent(self, event):
"""
Call when button mouse is pressed
@summary: Call when button mouse is pressed
@param event: QMouseEvent
"""
self._adaptor.sendMouseEvent(event, True)
def mouseReleaseEvent(self, event):
"""
Call when button mouse is released
@summary: Call when button mouse is released
@param event: QMouseEvent
"""
self._adaptor.sendMouseEvent(event, False)
def keyPressEvent(self, event):
"""
Call when button key is pressed
@summary: Call when button key is pressed
@param event: QKeyEvent
"""
self._adaptor.sendKeyEvent(event, True)
def keyReleaseEvent(self, event):
"""
Call when button key is released
@summary: Call when button key is released
@param event: QKeyEvent
"""
self._adaptor.sendKeyEvent(event, False)
def wheelEvent(self, event):
"""
@summary: Call on wheel event
@param event: QWheelEvent
"""
self._adaptor.sendWheelEvent(event)
def closeEvent(self, event):
"""
Call when widget is closed
@summary: Call when widget is closed
@param event: QCloseEvent
"""
self._adaptor.closeEvent(event)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,128 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
unit test for rdpy.protocol.rdp.nla.cssp and ntlm module
"""
import unittest
import os, sys
# Change path so we find rdpy
sys.path.insert(1, os.path.join(sys.path[0], '..'))
from rdpy.core.nla import ntlm, cssp
from rdpy.security import rc4
pubKeyHex = """
MIGJAoGBAJ6VtUEDxTPqKWUrZe8wcd1zuzA77Mpyz73g+C
H/ppd2oQi10saVgdK6cRBKrCU0N6DD\nV/DqH4yE63vmbF
AmH7dBCljTgIc9C0HZvFQ6D3cUefW5pDjrEwg1rr+zF1ri
WIk5xCJ/FleQCK+R\nO5XIU9DAjhmK8xC8yMdC+xLeLV6D
AgMBAAE=
"""
peer0_0 = """
MC+gAwIBAqEoMCYwJKAiBCBOVExNU1NQAAEAAAA1gghgAA
AAAAAAAAAAAAAAAAAAAA==
"""
peer1_0 = """
MIIBCaADAgECoYIBADCB/TCB+qCB9wSB9E5UTE1TU1AAAg
AAAA4ADgA4AAAANYKJYnPHQ6nn/Lv8\nAAAAAAAAAACuAK
4ARgAAAAYBsR0AAAAPUwBJAFIAQQBEAEUATAACAA4AUwBJ
AFIAQQBEAEUATAAB\nABYAVwBBAFYALQBHAEwAVwAtADAA
MAA5AAQAGgBTAGkAcgBhAGQAZQBsAC4AbABvAGMAYQBsAA
MA\nMgB3AGEAdgAtAGcAbAB3AC0AMAAwADkALgBTAGkAcg
BhAGQAZQBsAC4AbABvAGMAYQBsAAUAGgBT\nAGkAcgBhAG
QAZQBsAC4AbABvAGMAYQBsAAcACABWkzQyx1XQAQAAAAA=
"""
peer0_1 = """
MIICD6ADAgECoYIBYjCCAV4wggFaoIIBVgSCAVJOVExNU1
NQAAMAAAAYABgAUAAAANoA2gBoAAAA\nCAAIAEAAAAAIAA
gASAAAAAAAAABQAAAAEAAQAEIBAAA1gghgYwBvAGMAbwB0
AG8AdABvABqKwrxk\n2sAom6gUCFFt1rgpCdKZGTNwnlGg
bsU5R/OelmrD/LLrx+ABAQAAAAAAAABFCDLHVdABKQnSmR
kz\ncJ4AAAAAAgAOAFMASQBSAEEARABFAEwAAQAWAFcAQQ
BWAC0ARwBMAFcALQAwADAAOQAEABoAUwBp\nAHIAYQBkAG
UAbAAuAGwAbwBjAGEAbAADADIAdwBhAHYALQBnAGwAdwAt
ADAAMAA5AC4AUwBpAHIA\nYQBkAGUAbAAuAGwAbwBjAGEA
bAAFABoAUwBpAHIAYQBkAGUAbAAuAGwAbwBjAGEAbAAHAA
gAVpM0\nMsdV0AEAAAAAv+z19mkBOu9b0Kv+P991MKOCAK
AEggCcAQAAAMQOzZaPZ8DdAAAAAM+IvTDiU0pL\njUnU6a
NjH+gZWeaIlqpQNYECmpElixwPj8aRRFVfTtkbw66U3gmo
3YBkUoVK8tfHESkivuWtV2tP\n3KGuAFv/6GzbFYQYlA7r
zZ1Bw072ps8s9cWeoNmAX6oiZmFW3j7LX3xkr7+nJoOoXI
jzvorm5kz3\nldCo8Iwh+IZ3SSnj0/h4H1GR
"""
class TestCsspNtlm(unittest.TestCase):
"""
@summary: test generate ntlmv2 over cssp authentication protocol
"""
def testCSSPNTLMAuthentication(self):
negotiate_data_request = cssp.decode_der_trequest(peer0_0.decode('base64'))
challenge_data_request = cssp.decode_der_trequest(peer1_0.decode('base64'))
authenticate_data_request = cssp.decode_der_trequest(peer0_1.decode('base64'))
negotiate_data = cssp.getNegoTokens(negotiate_data_request)[0]
challenge_data = cssp.getNegoTokens(challenge_data_request)[0]
authenticate_data = cssp.getNegoTokens(authenticate_data_request)[0]
negotiate = ntlm.NegotiateMessage()
negotiate_data.read_type(negotiate)
challenge = ntlm.ChallengeMessage()
challenge_data.read_type(challenge)
ServerChallenge = challenge.ServerChallenge.value
ServerName = challenge.getTargetInfo()
authenticate = ntlm.AuthenticateMessage()
authenticate_data.read_type(authenticate)
NtChallengeResponseTemp = authenticate.getNtChallengeResponse()
NTProofStr = NtChallengeResponseTemp[:16]
temp = NtChallengeResponseTemp[16:]
Timestamp = temp[8:16]
ClientChallenge = temp[16:24]
EncryptedRandomSessionKey = authenticate.getEncryptedRandomSession()
domain = "coco"
user = "toto"
password = "lolo"
ResponseKeyNT = ntlm.NTOWFv2(password, user, domain)
ResponseKeyLM = ntlm.LMOWFv2(password, user, domain)
NtChallengeResponse, LmChallengeResponse, SessionBaseKey = ntlm.ComputeResponsev2(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ClientChallenge, Timestamp, ServerName)
KeyExchangeKey = ntlm.KXKEYv2(SessionBaseKey, LmChallengeResponse, ServerChallenge)
ExportedSessionKey = ntlm.RC4K(KeyExchangeKey, EncryptedRandomSessionKey)
domain, user = domain, user
if challenge.NegotiateFlags.value & ntlm.Negotiate.NTLMSSP_NEGOTIATE_UNICODE:
domain, user = ntlm.UNICODE(domain), ntlm.UNICODE(user)
ClientSigningKey = ntlm.SIGNKEY(ExportedSessionKey, True)
ServerSigningKey = ntlm.SIGNKEY(ExportedSessionKey, False)
ClientSealingKey = ntlm.SEALKEY(ExportedSessionKey, True)
ServerSealingKey = ntlm.SEALKEY(ExportedSessionKey, False)
interface = ntlm.NTLMv2SecurityInterface(rc4.RC4Key(ClientSealingKey), rc4.RC4Key(ServerSealingKey), ClientSigningKey, ServerSigningKey)
EncryptedPubKeySrc = cssp.getPubKeyAuth(authenticate_data_request)
EncryptedPubKeyDst = interface.GSS_WrapEx(pubKeyHex.decode('base64'))
self.assertTrue(EncryptedPubKeySrc == EncryptedPubKeyDst, "Public key must be equals")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,11 +26,11 @@ import os, sys
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.protocol.rdp.x224 as x224
import rdpy.network.type as type
import rdpy.base.error as error
import rdpy.core.x224 as x224
import rdpy.core.type as type
import rdpy.core.error as error
class X224Case(unittest.TestCase):
class X224Test(unittest.TestCase):
"""
@summary: test case for x224 layer (RDP)
"""
@@ -53,16 +53,16 @@ class X224Case(unittest.TestCase):
"""
class Presentation(object):
def recv(self, data):
data.readType(type.String('test_x224_layer_recvData', constant = True))
raise X224Case.X224_PASS()
data.read_type(type.String('test_x224_layer_recvData', constant = True))
raise X224Test.X224_PASS()
layer = x224.X224Layer(Presentation())
s = type.Stream()
s.writeType((x224.X224DataHeader(), type.String('test_x224_layer_recvData')))
s.write_type((x224.X224DataHeader(), type.String('test_x224_layer_recvData')))
#reinit position
s.pos = 0
self.assertRaises(X224Case.X224_PASS, layer.recvData, s)
self.assertRaises(X224Test.X224_PASS, layer.recvData, s)
def test_x224_layer_send(self):
"""
@@ -71,16 +71,16 @@ class X224Case(unittest.TestCase):
class Transport(object):
def send(self, data):
s = type.Stream()
s.writeType(data)
s.write_type(data)
s.pos = 0
s.readType(x224.X224DataHeader())
s.readType(type.String('test_x224_layer_send', constant = True))
raise X224Case.X224_PASS()
s.read_type(x224.X224DataHeader())
s.read_type(type.String('test_x224_layer_send', constant = True))
raise X224Test.X224_PASS()
layer = x224.X224Layer(None)
layer._transport = Transport()
self.assertRaises(X224Case.X224_PASS, layer.send, type.String('test_x224_layer_send'))
self.assertRaises(X224Test.X224_PASS, layer.send, type.String('test_x224_layer_send'))
def test_x224_client_connect(self):
"""
@@ -89,65 +89,36 @@ class X224Case(unittest.TestCase):
class Transport(object):
def send(self, data):
s = type.Stream()
s.writeType(data)
s.write_type(data)
s.pos = 0
t = x224.ClientConnectionRequestPDU()
s.readType(t)
t = x224.ConnectionRequestPDU()
s.read_type(t)
if t.protocolNeg.code != x224.NegociationType.TYPE_RDP_NEG_REQ:
raise X224Case.X224_FAIL()
if t.protocolNeg.selectedProtocol.value != x224.Protocols.PROTOCOL_SSL:
raise X224Case.X224_FAIL()
raise X224Test.X224_FAIL()
def nextAutomata(data):
raise X224Case.X224_PASS()
raise X224Test.X224_PASS()
layer = x224.Client(None)
layer._transport = Transport()
layer.recvConnectionConfirm = nextAutomata
layer.connect()
self.assertRaises(X224Case.X224_PASS, layer.recv, type.String('\x01\x02'))
def test_x224_client_recvConnectionConfirm_negotiation_old(self):
"""
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
whithout protocol negotiation (doesn't support)
"""
message = x224.ServerConnectionConfirm()
del message._typeName[message._typeName.index("protocolNeg")]
s = type.Stream()
s.writeType(message)
s.pos = 0
layer = x224.Client(None)
self.assertRaises(error.InvalidExpectedDataException, layer.recvConnectionConfirm, s)
self.assertRaises(X224Test.X224_PASS, layer.recv, type.String('\x01\x02'))
def test_x224_client_recvConnectionConfirm_negotiation_failure(self):
"""
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
check negotiation failure
"""
message = x224.ServerConnectionConfirm()
message = x224.ConnectionConfirmPDU()
message.protocolNeg.code.value = x224.NegociationType.TYPE_RDP_NEG_FAILURE
s = type.Stream()
s.writeType(message)
s.write_type(message)
s.pos = 0
layer = x224.Client(None)
self.assertRaises(error.InvalidExpectedDataException, layer.recvConnectionConfirm, s)
def test_x224_client_recvConnectionConfirm_negotiation_bad_protocol(self):
"""
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
Server ask another protocol than SSL
"""
message = x224.ServerConnectionConfirm()
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_RDP
s = type.Stream()
s.writeType(message)
s.pos = 0
layer = x224.Client(None)
self.assertRaises(error.InvalidExpectedDataException, layer.recvConnectionConfirm, s)
self.assertRaises(error.RDPSecurityNegoFail, layer.recvConnectionConfirm, s)
def test_x224_client_recvConnectionConfirm_ok(self):
"""
@@ -157,12 +128,10 @@ class X224Case(unittest.TestCase):
tls_begin = False
presentation_connect = False
class Transport(object):
def __init__(self):
class TLSTransport(object):
def startTLS(self, context):
global tls_begin
tls_begin = True
self.transport = TLSTransport()
def startTLS(self, context):
global tls_begin
tls_begin = True
class Presentation(object):
def connect(self):
@@ -170,13 +139,13 @@ class X224Case(unittest.TestCase):
presentation_connect = True
def recvData(data):
raise X224Case.X224_PASS()
raise X224Test.X224_PASS()
message = x224.ServerConnectionConfirm()
message = x224.ConnectionConfirmPDU()
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_SSL
s = type.Stream()
s.writeType(message)
s.write_type(message)
s.pos = 0
layer = x224.Client(Presentation())
layer._transport = Transport()
@@ -186,38 +155,7 @@ class X224Case(unittest.TestCase):
self.assertTrue(tls_begin, "TLS is not started")
self.assertTrue(presentation_connect, "connect event is not forwarded")
self.assertRaises(X224Case.X224_PASS, layer.recv, type.String('\x01\x02'))
def test_x224_server_recvConnectionRequest_invalid_old_client(self):
"""
@summary: unit test for X224Server.recvConnectionRequest function
old client with non protocol neg
"""
message = x224.ClientConnectionRequestPDU()
del message._typeName[message._typeName.index("protocolNeg")]
s = type.Stream()
s.writeType(message)
s.pos = 0
layer = x224.Server(None, "key", "cert")
layer.connect()
self.assertRaises(error.InvalidExpectedDataException, layer.recv, s)
def test_x224_server_recvConnectionRequest_invalid_protocol_neg_failure(self):
"""
@summary: unit test for X224Server.recvConnectionRequest function
"""
message = x224.ClientConnectionRequestPDU()
message.protocolNeg.code.value = x224.NegociationType.TYPE_RDP_NEG_FAILURE
s = type.Stream()
s.writeType(message)
s.pos = 0
layer = x224.Server(None, "key", "cert")
layer.connect()
self.assertRaises(error.InvalidExpectedDataException, layer.recv, s)
self.assertRaises(X224Test.X224_PASS, layer.recv, type.String('\x01\x02'))
def test_x224_server_recvConnectionRequest_client_accept_ssl(self):
"""
@@ -227,22 +165,24 @@ class X224Case(unittest.TestCase):
class Transport(object):
def send(self, data):
if not isinstance(data, x224.ServerConnectionConfirm):
raise X224Case.X224_FAIL()
if not isinstance(data, x224.ConnectionConfirmPDU):
raise X224Test.X224_FAIL()
if data.protocolNeg.code.value != x224.NegociationType.TYPE_RDP_NEG_FAILURE or data.protocolNeg.failureCode.value != x224.NegotiationFailureCode.SSL_REQUIRED_BY_SERVER:
raise X224Case.X224_FAIL()
raise X224Test.X224_FAIL()
def close(self):
raise X224Test.X224_PASS()
message = x224.ClientConnectionRequestPDU()
message = x224.ConnectionRequestPDU()
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_HYBRID
s = type.Stream()
s.writeType(message)
s.write_type(message)
s.pos = 0
layer = x224.Server(None, "key", "cert")
layer = x224.Server(None, "key", "cert", True)
layer._transport = Transport()
layer.connect()
self.assertRaises(error.InvalidExpectedDataException, layer.recv, s)
self.assertRaises(X224Test.X224_PASS, layer.recv, s)
def test_x224_server_recvConnectionRequest_valid(self):
"""
@@ -259,28 +199,25 @@ class X224Case(unittest.TestCase):
x224.ServerTLSContext = ServerTLSContext
class Transport(object):
def __init__(self):
class TLS(object):
def startTLS(self, context):
global tls
tls = True
self.transport = TLS()
def startTLS(self, context):
global tls
tls = True
def send(self, data):
if not isinstance(data, x224.ServerConnectionConfirm):
raise X224Case.X224_FAIL()
if not isinstance(data, x224.ConnectionConfirmPDU):
raise X224Test.X224_FAIL()
if data.protocolNeg.code.value != x224.NegociationType.TYPE_RDP_NEG_RSP or data.protocolNeg.selectedProtocol.value != x224.Protocols.PROTOCOL_SSL:
raise X224Case.X224_FAIL()
raise X224Test.X224_FAIL()
class Presentation(object):
def connect(self):
global connect_event
connect_event = True
message = x224.ClientConnectionRequestPDU()
message = x224.ConnectionRequestPDU()
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_SSL | x224.Protocols.PROTOCOL_RDP
s = type.Stream()
s.writeType(message)
s.write_type(message)
s.pos = 0
layer = x224.Server(Presentation(), "key", "cert")