From 6c2c32a1357f64f560a968a18a13d65d82bd0cf5 Mon Sep 17 00:00:00 2001 From: Olivier Bilodeau Date: Tue, 5 Dec 2023 10:45:02 -0500 Subject: [PATCH] Modernize the packaging (#416) - Gets rid of deprecation notices (#440) - Will be publishable to Pypi - Using modern pyproject.toml - While we are there, breaking change: scripts names without .py - Minimum Python version is 3.7 --- .coveragerc | 1 - .github/workflows/ci.yml | 36 ++++---- CHANGELOG.adoc | 6 ++ Dockerfile | 24 +++--- Dockerfile.slim | 18 ++-- README.md | 52 ++++++------ docs/bettercap-rdp-mitm.md | 4 +- docs/cert-extraction.md | 2 +- docs/devel.adoc | 8 +- docs/pyrdp-convert.md | 4 +- docs/transparent-proxy.md | 4 +- pyproject.toml | 85 +++++++++++++++++++ pyrdp/bin/__init__.py | 5 ++ .../bin/clonecert.py | 3 +- bin/pyrdp-convert.py => pyrdp/bin/convert.py | 7 +- bin/pyrdp-mitm.py => pyrdp/bin/mitm.py | 3 +- bin/pyrdp-player.py => pyrdp/bin/player.py | 1 + setup.py | 50 +---------- test/integration.sh | 35 +++++--- 19 files changed, 205 insertions(+), 143 deletions(-) create mode 100644 pyproject.toml create mode 100644 pyrdp/bin/__init__.py rename bin/pyrdp-clonecert.py => pyrdp/bin/clonecert.py (99%) rename bin/pyrdp-convert.py => pyrdp/bin/convert.py (98%) rename bin/pyrdp-mitm.py => pyrdp/bin/mitm.py (97%) rename bin/pyrdp-player.py => pyrdp/bin/player.py (99%) diff --git a/.coveragerc b/.coveragerc index 96e20a217..d7ddcd9ac 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,7 +3,6 @@ branch = True source = pyrdp twisted - bin [report] # Regexes for lines to exclude from consideration diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8eab0585a..cfa917c14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,20 +16,20 @@ jobs: - uses: actions/checkout@v2 - name: Build Docker image run: docker build -t pyrdp . - - name: "Smoke test docker image: pyrdp-convert.py" - run: docker run pyrdp pyrdp-convert.py -h - - name: "Smoke test docker image: pyrdp-player.py" - run: docker run pyrdp pyrdp-player.py -h - - name: "Smoke test docker image: pyrdp-mitm.py" - run: docker run pyrdp pyrdp-mitm.py -h + - name: "Smoke test docker image: pyrdp-convert" + run: docker run pyrdp pyrdp-convert -h + - name: "Smoke test docker image: pyrdp-player" + run: docker run pyrdp pyrdp-player -h + - name: "Smoke test docker image: pyrdp-mitm" + run: docker run pyrdp pyrdp-mitm -h - name: Build slim Docker image run: docker build -f Dockerfile.slim -t pyrdp . - - name: "Smoke test docker image: pyrdp-convert.py" - run: docker run pyrdp pyrdp-convert.py -h - - name: "Smoke test docker image: pyrdp-player.py" - run: docker run pyrdp pyrdp-player.py -h - - name: "Smoke test docker image: pyrdp-mitm.py" - run: docker run pyrdp pyrdp-mitm.py -h + - name: "Smoke test docker image: pyrdp-convert" + run: docker run pyrdp pyrdp-convert -h + - name: "Smoke test docker image: pyrdp-player" + run: docker run pyrdp pyrdp-player -h + - name: "Smoke test docker image: pyrdp-mitm" + run: docker run pyrdp pyrdp-mitm -h install-and-test-ubuntu: runs-on: ubuntu-latest @@ -83,7 +83,7 @@ jobs: working-directory: ./ run: coverage run test/test_prerecorded.py - - name: pyrdp-mitm.py initialization integration test + - name: pyrdp-mitm initialization integration test working-directory: ./ run: coverage run --append test/test_mitm_initialization.py dummy_value @@ -141,17 +141,17 @@ jobs: working-directory: ./ run: coverage run test/test_prerecorded.py - - name: pyrdp-mitm.py initialization test + - name: pyrdp-mitm initialization test working-directory: ./ run: coverage run --append test/test_mitm_initialization.py dummy_value - - name: pyrdp-player.py read a replay in headless mode test + - name: pyrdp-player read a replay in headless mode test working-directory: ./ - run: coverage run --append bin/pyrdp-player.py --headless test/files/test_session.replay + run: coverage run --append -m pyrdp.bin.player --headless test/files/test_session.replay - - name: pyrdp-convert.py to MP4 + - name: pyrdp-convert to MP4 working-directory: ./ - run: coverage run --append bin/pyrdp-convert.py test/files/test_convert.pyrdp -f mp4 + run: coverage run --append -m pyrdp.bin.convert test/files/test_convert.pyrdp -f mp4 - name: Verify the MP4 file working-directory: ./ diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index d3c7cb65f..942eebbe5 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -9,6 +9,11 @@ For a detailed view of what has changed, refer to the {uri-repo}/commits/master[ == v - +=== Backwards Compatibility Changes + +* All tools lost their `.py` suffix. For example, `pyrdp-mitm.py` is now `pyrdp-mitm`. +* Requires Python 3.7 + === Enhancements * Support for RDP version 10.11 ({uri-issue}433[#433]) @@ -24,6 +29,7 @@ For a detailed view of what has changed, refer to the {uri-repo}/commits/master[ === Infrastructure +* Project packaging modernized, getting rid of `pkg_resources` deprecation warnings ({uri-issue}416[#416], {uri-issue}440[#440]) * Added Python 3.10 tests on Windows to CI test configuration ({uri-issue}439[#439]) * Replaced Python 3.10 with Python 3.11 for CI test configuration ({uri-issue}438[#438]) diff --git a/Dockerfile b/Dockerfile index 45fe611b3..695cb1101 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,23 +19,27 @@ RUN python3 -m venv /opt/venv # Make sure we use the virtualenv: ENV PATH="/opt/venv/bin:$PATH" +# Python packaging tooling evolved quickly, we need to get latest, especially on old Pythons +RUN pip --no-cache-dir install -U pip setuptools wheel + # Install dependencies only (speeds repetitive builds) COPY requirements.txt /pyrdp/requirements.txt RUN cd /pyrdp && \ - pip3 install wheel && \ - pip3 --no-cache-dir install --default-timeout=100 -r requirements.txt + pip --no-cache-dir install --default-timeout=100 -r requirements.txt # Compile only our C extension and install # This way changes to source tree will not trigger full images rebuilds -COPY ext/rle.c /pyrdp/ext/rle.c -COPY setup.py /pyrdp/setup.py +COPY ext/rle.c /pyrdp/ext/ +COPY setup.py /pyrdp/ +COPY README.md /pyrdp/ +COPY pyproject.toml /pyrdp/ +COPY pyrdp/ /pyrdp/pyrdp/ RUN cd /pyrdp \ - && python setup.py build_ext \ - && python setup.py install_lib + && pip --no-cache-dir install '.[full]' # Handles runtime only (minimize size for distribution) -FROM ubuntu:20.04 AS docker-image +FROM ubuntu:20.04 AS runtime-image # Install runtime dependencies except pre-built venv ARG DEBIAN_FRONTEND=noninteractive @@ -63,14 +67,10 @@ RUN useradd --create-home --home-dir /home/pyrdp pyrdp # Make sure we use the virtualenv ENV PATH="/opt/venv/bin:$PATH" -# Install python source and package +# Install python source # NOTE: we are no longer doing this in the compile image to avoid long image rebuilds in development COPY --from=compile-image /pyrdp /pyrdp -COPY bin/ /pyrdp/bin/ COPY pyrdp/ /pyrdp/pyrdp/ -COPY setup.py /pyrdp/setup.py -RUN cd /pyrdp \ - && python setup.py install USER pyrdp diff --git a/Dockerfile.slim b/Dockerfile.slim index 0614630fd..9047a6b2b 100644 --- a/Dockerfile.slim +++ b/Dockerfile.slim @@ -24,7 +24,7 @@ ENV PATH="/opt/venv/bin:$PATH" # Required for ARM builds # Building dependencies didn't work without an upgraded pip and wheel on ARM -RUN pip3 --no-cache-dir install -U pip wheel +RUN pip3 --no-cache-dir install -U pip setuptools wheel # Install dependencies only (speeds repetitive builds) COPY requirements-slim.txt /pyrdp/requirements.txt @@ -32,15 +32,17 @@ RUN cd /pyrdp && pip3 --no-cache-dir install -r requirements.txt # Compile only our C extension and install # This way changes to source tree will not trigger full images rebuilds -COPY ext/rle.c /pyrdp/ext/rle.c -COPY setup.py /pyrdp/setup.py +COPY ext/rle.c /pyrdp/ext/ +COPY setup.py /pyrdp/ +COPY README.md /pyrdp/ +COPY pyproject.toml /pyrdp/ +COPY pyrdp/ /pyrdp/pyrdp/ RUN cd /pyrdp \ - && python setup.py build_ext \ - && python setup.py install_lib + && pip --no-cache-dir install . # Handles runtime only (minimize size for distribution) -FROM ubuntu:20.04 AS docker-image +FROM ubuntu:20.04 AS runtime-image # Install runtime dependencies except pre-built venv ARG DEBIAN_FRONTEND=noninteractive @@ -63,11 +65,7 @@ ENV PATH="/opt/venv/bin:$PATH" # Install python source and package # NOTE: we are no longer doing this in the compile image to avoid long image rebuilds in development COPY --from=compile-image /pyrdp /pyrdp -COPY bin/ /pyrdp/bin/ COPY pyrdp/ /pyrdp/pyrdp/ -COPY setup.py /pyrdp/setup.py -RUN cd /pyrdp \ - && python setup.py install USER pyrdp diff --git a/README.md b/README.md index f482cad81..5d3dafba7 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,9 @@ research use cases in mind. ## Supported Systems -PyRDP should work on Python 3.6 and up on the x86-64, ARM and ARM64 platforms. +PyRDP should work on Python 3.7 and up on the x86-64, ARM and ARM64 platforms. -This tool has been tested to work on Python 3.6 on Linux (Ubuntu 18.04, 20.04), Raspberry Pi and Windows +This tool has been tested to work on Python 3.7 on Linux (Ubuntu 20.04, 22.04), Raspberry Pi and Windows (see section [Installing on Windows](#installing-on-windows)). It has not been tested on macOS. ## Installing @@ -192,7 +192,7 @@ This should install all the dependencies required to run PyRDP. For example, to open the player: ``` -python venv\Scripts\pyrdp-player.py +python venv\Scripts\pyrdp-player ``` If you ever want to leave your virtual environment, you can simply deactivate it: @@ -232,12 +232,12 @@ docker buildx build --platform linux/arm,linux/amd64 -t pyrdp -f Dockerfile.slim ## Using PyRDP ### Using the PyRDP Monster-in-the-Middle -Use `pyrdp-mitm.py ` or `pyrdp-mitm.py :` to run the MITM. +Use `pyrdp-mitm ` or `pyrdp-mitm :` to run the MITM. Assuming you have an RDP server running on `192.168.1.10` and listening on port 3389, you would run: ``` -pyrdp-mitm.py 192.168.1.10 +pyrdp-mitm 192.168.1.10 ``` When running the MITM for the first time a directory called `pyrdp_output/` @@ -289,7 +289,7 @@ If key generation didn't work or you want to use a custom key and certificate, y `-c` and `-k` arguments: ``` -pyrdp-mitm.py 192.168.1.10 -k private_key.pem -c certificate.pem +pyrdp-mitm 192.168.1.10 -k private_key.pem -c certificate.pem ``` ##### Monster-in-the-Middle Network Level Authentication (NLA) connections @@ -344,7 +344,7 @@ If you want to see live RDP connections through the PyRDP player, you will need player is listening using the `-i` and `-d` arguments. Note: the port argument is optional, the default port is 3000. ``` -pyrdp-mitm.py 192.168.1.10 -i 127.0.0.1 -d 3000 +pyrdp-mitm 192.168.1.10 -i 127.0.0.1 -d 3000 ``` ##### Connecting to a PyRDP player when the MITM is running on a server @@ -355,7 +355,7 @@ port as arguments to the MITM. For example, if port 4000 on the server is forwar this would be the command to use: ``` -pyrdp-mitm.py 192.168.1.10 -i 127.0.0.1 -d 4000 +pyrdp-mitm 192.168.1.10 -i 127.0.0.1 -d 4000 ``` #### Running payloads on new connections @@ -412,7 +412,7 @@ This will block the client's input / output for 5 seconds to hide the console an After 5 seconds, input / output is restored back to normal. #### Other MITM arguments -Run `pyrdp-mitm.py --help` for a full list of arguments. +Run `pyrdp-mitm --help` for a full list of arguments. ##### `--no-downgrade` @@ -461,7 +461,7 @@ support can be added as required. (Make sure that the trace does not contain sen [gdi]: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpegdi/745f2eee-d110-464c-8aca-06fc1814f6ad ### Using the PyRDP Player -Use `pyrdp-player.py` to run the player. +Use `pyrdp-player` to run the player. #### Playing a replay file You can use the menu to open a new replay file: File > Open. @@ -469,14 +469,14 @@ You can use the menu to open a new replay file: File > Open. You can also open replay files when launching the player: ``` -pyrdp-player.py ... +pyrdp-player ... ``` #### Listening for live connections The player always listens for live connections. By default, the listening port is 3000, but it can be changed: ``` -pyrdp-player.py -p +pyrdp-player -p ``` #### Changing the listening address @@ -484,11 +484,11 @@ By default, the player only listens to connections coming from the local machine to other machines. If you still want to change the listening address, you can do it with `-b`: ``` -pyrdp-player.py -b
+pyrdp-player -b
``` #### Other player arguments -Run `pyrdp-player.py --help` for a full list of arguments. +Run `pyrdp-player --help` for a full list of arguments. ### Using the PyRDP Certificate Cloner @@ -502,10 +502,10 @@ you're trying to trick a legitimate user into going through your MITM. Using a c certificate could increase your success rate. #### Cloning a certificate -You can clone a certificate by using `pyrdp-clonecert.py`: +You can clone a certificate by using `pyrdp-clonecert`: ``` -pyrdp-clonecert.py 192.168.1.10 cert.pem -o key.pem +pyrdp-clonecert 192.168.1.10 cert.pem -o key.pem ``` The `-o` parameter defines the path name to use for the generated private key. @@ -514,11 +514,11 @@ The `-o` parameter defines the path name to use for the generated private key. If you want to use your own private key instead of generating a new one: ``` -pyrdp-clonecert.py 192.168.1.10 cert.pem -i input_key.pem +pyrdp-clonecert 192.168.1.10 cert.pem -i input_key.pem ``` #### Other cloner arguments -Run `pyrdp-clonecert.py --help` for a full list of arguments. +Run `pyrdp-clonecert --help` for a full list of arguments. ### Using PyRDP Convert @@ -535,19 +535,19 @@ The following outputs are supported: - MP4 video file - JSON: a sequence of low-level events serialized in JSON format -- Replay file compatible with `pyrdp-player.py` +- Replay file compatible with `pyrdp-player` Encrypted (TLS) network captures require the TLS master secrets to be provided using `--secrets ssl.log`. ``` # Export the session coming client 10.2.0.198 to a .pyrdp file. -pyrdp-convert.py --src 10.2.0.198 --secrets ssl.log -o path/to/output capture.pcap +pyrdp-convert --src 10.2.0.198 --secrets ssl.log -o path/to/output capture.pcap # Or as an MP4 video -pyrdp-convert.py --src 10.2.0.198 --secrets ssl.log -o path/to/output -f mp4 capture.pcap +pyrdp-convert --src 10.2.0.198 --secrets ssl.log -o path/to/output -f mp4 capture.pcap # List the sessions in a network trace, along with the decryptable ones. -pyrdp-convert.py --list-only capture.pcap +pyrdp-convert --list-only capture.pcap ``` Note that MP4 conversion requires libavcodec and ffmpeg, so this may require extra steps on Windows. @@ -609,7 +609,7 @@ In most of the monster-in-the-middle cases you will need to map a port of your h For example, to listen on 3389 (RDP's default port) on all interfaces, use: ``` -docker run -p 3389:3389 gosecure/pyrdp pyrdp-mitm.py 192.168.1.10 +docker run -p 3389:3389 gosecure/pyrdp pyrdp-mitm 192.168.1.10 ``` #### Logs and Artifacts Storage @@ -617,7 +617,7 @@ docker run -p 3389:3389 gosecure/pyrdp pyrdp-mitm.py 192.168.1.10 To store the PyRDP output permanently (logs, files, etc.), add the `--volume` (`-v`) option to the previous command. In this example we store the files relatively to the current directory in `pyrdp_output`: ``` -docker run -v $PWD/pyrdp_output:/home/pyrdp/pyrdp_output -p 3389:3389 gosecure/pyrdp pyrdp-mitm.py 192.168.1.10 +docker run -v $PWD/pyrdp_output:/home/pyrdp/pyrdp_output -p 3389:3389 gosecure/pyrdp pyrdp-mitm 192.168.1.10 ``` Make sure that your destination directory is owned by a user with a UID of 1000, otherwise you will get permission denied errors. @@ -628,7 +628,7 @@ If you are the only non-root user on the system, usually your user will be assig If you want PyRDP to log the host IP address in its logs, you can set the `HOST_IP` environment variable when using `docker run`: ``` -docker run -p 3389:3389 -e HOST_IP=192.168.1.9 gosecure/pyrdp pyrdp-mitm.py 192.168.1.10 +docker run -p 3389:3389 -e HOST_IP=192.168.1.9 gosecure/pyrdp pyrdp-mitm 192.168.1.10 ``` #### Using the GUI Player in Docker @@ -639,7 +639,7 @@ You also need to expose the host's network and prevent Qt from using the MIT-SHM To do so, add the `-e` and `--net` options to the run command: ``` -docker run -e DISPLAY=$DISPLAY -e QT_X11_NO_MITSHM=1 --net=host gosecure/pyrdp pyrdp-player.py +docker run -e DISPLAY=$DISPLAY -e QT_X11_NO_MITSHM=1 --net=host gosecure/pyrdp pyrdp-player ``` Keep in mind that exposing the host's network to docker can compromise the isolation between your container and the host. diff --git a/docs/bettercap-rdp-mitm.md b/docs/bettercap-rdp-mitm.md index 8e7dd8635..52b4fa34c 100644 --- a/docs/bettercap-rdp-mitm.md +++ b/docs/bettercap-rdp-mitm.md @@ -44,11 +44,11 @@ Assuming that you installed PyRDP using a venv, you need to activate your PyRDP Start bettercap with : - sudo bettercap -iface -caplet -eval "set rdp.proxy.command $(which pyrdp-mitm.py)" + sudo bettercap -iface -caplet -eval "set rdp.proxy.command $(which pyrdp-mitm)" Basic example : - sudo bettercap -iface wlp2s0 -caplet rdp-proxy/rdp-sniffer.cap -eval "set rdp.proxy.command $(which pyrdp-mitm.py)" + sudo bettercap -iface wlp2s0 -caplet rdp-proxy/rdp-sniffer.cap -eval "set rdp.proxy.command $(which pyrdp-mitm)" ## Caplets diff --git a/docs/cert-extraction.md b/docs/cert-extraction.md index 3ea98641f..5cc373560 100644 --- a/docs/cert-extraction.md +++ b/docs/cert-extraction.md @@ -35,6 +35,6 @@ It requires Administrative privileges on the target server and the use of Mimika > **NOTE**: If `token::elevate` doesn't work. Make sure you are running mimikatz as SYSTEM (ie: under `psexec -s cmd.exe`) -You can now run `pyrdp-mitm.py` by specifying `-k privkey.key -c pubkey.pem` and PyRDP will serve the same certificate as the server. +You can now run `pyrdp-mitm` by specifying `-k privkey.key -c pubkey.pem` and PyRDP will serve the same certificate as the server. With the certificate and the private key, RDP servers with Network Level Authentication (NLA) enabled can be MITM. Use `--auth ssp` to do that. diff --git a/docs/devel.adoc b/docs/devel.adoc index 08ec6710f..b012deb52 100644 --- a/docs/devel.adoc +++ b/docs/devel.adoc @@ -6,7 +6,7 @@ NOTE: This is a work in progress .Update dependencies -For developement and normal installation we rely on `setup.py` with loose dependencies. +For developement and normal installation we rely on `pyproject.toml` with loose dependencies. For docker images we rely on `requirements.txt` and `requirements-slim.txt` with strict dependencies. It is important to keep both approaches in sync and to peridiocally update the requirements files (like before releases). See https://github.com/GoSecure/pyrdp/pull/219[the PR where we improved our use of the docker cache] for the reasoning behind this approach. @@ -30,7 +30,7 @@ docker images (latest, slim), on linux, on Windows git log .. --format="%aN" --reverse | perl -e 'my %dedupe; while () { print unless $dedupe{$_}++}' | sort .Prepare release commit -* Update version in `setup.py` +* Update version in `pyproject.toml` * commit msg: Prepare %version% release * You can optionally do a release commit (with `--allow-empty` if needed) msg: Release %version% @@ -46,7 +46,7 @@ docker images (latest, slim), on linux, on Windows * asciidoc links to markdown with vim: `%s/link:\([^\[]\+\)\(\[[^\[]\+\]\)/\2(\1)/gc` .Post-release -* Update version in `setup.py` (+1 bugfix, append '.dev0') and commit +* Update version in `pyproject.toml` (+1 bugfix, append '.dev0') and commit * commit msg: Begin development on next release @@ -55,4 +55,4 @@ docker images (latest, slim), on linux, on Windows By default we log to stdout and in JSON format. Please use the recommended logging style to best leverage the JSON output: -https://docs.python.org/3/howto/logging-cookbook.html#formatting-styles \ No newline at end of file +https://docs.python.org/3/howto/logging-cookbook.html#formatting-styles diff --git a/docs/pyrdp-convert.md b/docs/pyrdp-convert.md index cccd3bbd7..e5b6387c1 100644 --- a/docs/pyrdp-convert.md +++ b/docs/pyrdp-convert.md @@ -1,10 +1,10 @@ # PyRDP Convert -`pyrdp-convert.py` is a helper tool to help manipulate network traces and PyRDP session replay files. +`pyrdp-convert` is a helper tool to help manipulate network traces and PyRDP session replay files. It supports conversions of network captures or replay files into various formats. This document contains format-specific notes. -Use `pyrdp-convert.py -h` for a full list of supported arguments and formats. +Use `pyrdp-convert -h` for a full list of supported arguments and formats. ## MP4 diff --git a/docs/transparent-proxy.md b/docs/transparent-proxy.md index 63685264f..8395a856f 100644 --- a/docs/transparent-proxy.md +++ b/docs/transparent-proxy.md @@ -71,7 +71,7 @@ ip route add local default dev lo table $TABLE_ID Then launch PyRDP. In this case it should be launched like this: ``` -pyrdp-mitm.py --transparent 10.2.2.2 +pyrdp-mitm --transparent 10.2.2.2 ``` @@ -301,7 +301,7 @@ iptables -t mangle -A PREROUTING -s $SERVER_IP -m tcp -p tcp --sport 3389 \ Then launch PyRDP. In this case it should be launched like this: ``` -pyrdp-mitm.py --transparent $SERVER_IP +pyrdp-mitm --transparent $SERVER_IP ``` ### Stop diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..6f146c964 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,85 @@ +[build-system] +# can't use setuptools-scm for docker builds w/o a git tree +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "pyrdp" +description = "Remote Desktop Protocol (RDP) Monster-in-the-Middle tool and Python library" +readme = "README.md" +#license = "GPL-3.0-or-later" +# TODO automatically pull from this in the code +version = "1.2.1.dev0" +requires-python = ">=3.7" +authors = [ + { name = "Olivier Bilodeau", email = "obilodeau@gosecure.net" }, + { name = "Émilio Gonzalez" }, + { name = "Francis Labelle" }, + { name = "Alexandre Beaulieu" }, +] +keywords = ["RDP", "MITM", "interception", "attack", "pentest", "honeypots"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Environment :: X11 Applications :: Qt", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Natural Language :: English", + "Operating System :: POSIX", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Communications", + "Topic :: Security", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [ + 'appdirs>=1,<2', + 'cryptography>=3.3.2,<37', + 'names>=0,<1', + 'progressbar2>=3.20,<5', + 'pyasn1>=0,<1', + 'pycryptodome>=3.5,<4', + 'pyopenssl>=19,<22', + 'pytz', + 'rsa>=4,<5', + 'scapy>=2.4,<3', + 'service_identity>=18', + 'twisted>=18' +] + +[project.optional-dependencies] +full = [ + 'wheel>=0.34.2', + 'av>=8,<12', + 'PySide2>=5.12,<6', + 'qimage2ndarray>=1.6,<2', + 'py-notifier>=0.3.0', + 'win10toast>=0.9;platform_system=="Windows"', +] + +[project.urls] +Homepage = "https://github.com/GoSecure/pyrdp" + +[project.scripts] +pyrdp-clonecert = "pyrdp.bin.clonecert:main" +pyrdp-mitm = "pyrdp.bin.mitm:main" +pyrdp-player = "pyrdp.bin.player:main" +pyrdp-convert = "pyrdp.bin.convert:main" + +[project.gui-scripts] +pyrdp-player-gui = "pyrdp.bin.player:main" + +[tool.setuptools.packages.find] +where = ["."] +include = ["pyrdp*", "twisted*"] + +[tool.setuptools.package-data] +"*" = ["*.txt", "*.ini", "twisted/*.py"] + +[tool.ruff.extend-per-file-ignores] +"__init__.py" = ["F401"] diff --git a/pyrdp/bin/__init__.py b/pyrdp/bin/__init__.py new file mode 100644 index 000000000..adca3b9b7 --- /dev/null +++ b/pyrdp/bin/__init__.py @@ -0,0 +1,5 @@ +# +# This file is part of the PyRDP project. +# Copyright (C) 2023 GoSecure Inc. +# Licensed under the GPLv3 or later. +# diff --git a/bin/pyrdp-clonecert.py b/pyrdp/bin/clonecert.py similarity index 99% rename from bin/pyrdp-clonecert.py rename to pyrdp/bin/clonecert.py index 28797e002..ee4d6e679 100755 --- a/bin/pyrdp-clonecert.py +++ b/pyrdp/bin/clonecert.py @@ -2,10 +2,11 @@ # # This file is part of the PyRDP project. -# Copyright (C) 2018, 2019, 2022 GoSecure Inc. +# Copyright (C) 2018-2023 GoSecure Inc. # Licensed under the GPLv3 or later. # # Need to install this reactor before importing any other code +# ruff: noqa: E402 import asyncio import sys # We need a special asyncio loop on Windows above Python 3.8. See #316 diff --git a/bin/pyrdp-convert.py b/pyrdp/bin/convert.py similarity index 98% rename from bin/pyrdp-convert.py rename to pyrdp/bin/convert.py index 870cfab5e..fd8e6ad74 100755 --- a/bin/pyrdp-convert.py +++ b/pyrdp/bin/convert.py @@ -2,7 +2,7 @@ # # This file is part of the PyRDP project. -# Copyright (C) 2020-2022 GoSecure Inc. +# Copyright (C) 2020-2023 GoSecure Inc. # Licensed under the GPLv3 or later. # import argparse @@ -16,7 +16,7 @@ from pyrdp.player import HAS_GUI -if __name__ == "__main__": +def main(): parser = argparse.ArgumentParser() parser.add_argument("input", help="Path to a .pcap or .pyrdp file. " "Converting from a .pcap will always extract file transfer artifacts in addition to the actual replay.") @@ -98,3 +98,6 @@ exitCode = converter.process() sys.exit(exitCode) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/bin/pyrdp-mitm.py b/pyrdp/bin/mitm.py similarity index 97% rename from bin/pyrdp-mitm.py rename to pyrdp/bin/mitm.py index 7b936998d..19c292759 100755 --- a/bin/pyrdp-mitm.py +++ b/pyrdp/bin/mitm.py @@ -2,10 +2,11 @@ # # This file is part of the PyRDP project. -# Copyright (C) 2018-2022 GoSecure Inc. +# Copyright (C) 2018-2023 GoSecure Inc. # Licensed under the GPLv3 or later. # # Need to install this reactor before importing any other code +# ruff: noqa: E402 import asyncio import sys # We need a special asyncio loop on Windows above Python 3.8. See #316 diff --git a/bin/pyrdp-player.py b/pyrdp/bin/player.py similarity index 99% rename from bin/pyrdp-player.py rename to pyrdp/bin/player.py index e4aad9e5b..c70655bbe 100755 --- a/bin/pyrdp-player.py +++ b/pyrdp/bin/player.py @@ -6,6 +6,7 @@ # Licensed under the GPLv3 or later. # # Need to install this reactor before importing any other code +# ruff: noqa: E402 import asyncio import sys # We need a special asyncio loop on Windows above Python 3.8. See #316 diff --git a/setup.py b/setup.py index 458df989c..9416b7225 100644 --- a/setup.py +++ b/setup.py @@ -3,55 +3,11 @@ # # This file is part of the PyRDP project. -# Copyright (C) 2019-2021 GoSecure Inc. +# Copyright (C) 2019-2023 GoSecure Inc. # Licensed under the GPLv3 or later. # +from setuptools import Extension, setup -# setuptools MUST be imported first, otherwise we get an error with the ext_modules argument. -import setuptools -from distutils.core import Extension, setup - -setup(name='pyrdp', - version='1.2.1.dev0', - description='Remote Desktop Protocol Monster-in-the-Middle tool and Python library', - long_description="""Remote Desktop Protocol Monster-in-the-Middle tool and Python library""", - author='Émilio Gonzalez, Francis Labelle, Olivier Bilodeau, Alexandre Beaulieu', - author_email='obilodeau@gosecure.net', - url='https://github.com/GoSecure/pyrdp', - packages=setuptools.find_packages(include=["pyrdp", "pyrdp.*"]), - package_data={ - "pyrdp": ["mitm/crawler_config/*.txt"], - "": ["*.default.ini"] - }, +setup( ext_modules=[Extension('rle', ['ext/rle.c'])], - scripts=[ - 'bin/pyrdp-clonecert.py', - 'bin/pyrdp-mitm.py', - 'bin/pyrdp-player.py', - 'bin/pyrdp-convert.py', - ], - install_requires=[ - 'appdirs>=1,<2', - 'cryptography>=3.3.2,<37', - 'names>=0,<1', - 'progressbar2>=3.20,<5', - 'pyasn1>=0,<1', - 'pycryptodome>=3.5,<4', - 'pyopenssl>=19,<22', - 'pytz', - 'rsa>=4,<5', - 'scapy>=2.4,<3', - 'service_identity>=18', - 'twisted>=21.2.0', - ], - extras_require={ - "full": [ - 'wheel>=0.34.2', - 'av>=8,<11', - 'PySide2>=5.12,<6', - 'qimage2ndarray>=1.6,<2', - 'py-notifier>=0.3.0', - 'win10toast>=0.9;platform_system=="Windows"', - ] - } ) diff --git a/test/integration.sh b/test/integration.sh index 906debdb5..b74985f8a 100755 --- a/test/integration.sh +++ b/test/integration.sh @@ -1,7 +1,7 @@ #!/bin/bash # # This file is part of the PyRDP project. -# Copyright (C) 2022 GoSecure Inc. +# Copyright (C) 2022-2023 GoSecure Inc. # Licensed under the GPLv3 or later. # # We extracted a set of important tests that were run as part of a GitHub @@ -16,21 +16,28 @@ set -e # Sets how to launch commands. GitHub workflows sets the CI environment variable if [[ -z "${CI}" ]]; then - PREPEND="" + SOURCE="local" else - PREPEND="coverage run --append bin/" + SOURCE="ci" fi +declare -A ENTRYPOINT + +ENTRYPOINT["player","local"]="pyrdp-player" +ENTRYPOINT["player","ci"]="coverage run --append -m pyrdp.bin.player" +ENTRYPOINT["convert","local"]="pyrdp-convert" +ENTRYPOINT["convert","ci"]="coverage run --append -m pyrdp.bin.convert" + export QT_QPA_PLATFORM=offscreen echo =================================================== -echo pyrdp-player.py read a replay in headless mode test -${PREPEND}pyrdp-player.py --headless test/files/test_session.replay +echo pyrdp-player read a replay in headless mode test +${ENTRYPOINT["player",${SOURCE}]} --headless test/files/test_session.replay echo echo =================================================== -echo pyrdp-convert.py to MP4 -${PREPEND}pyrdp-convert.py test/files/test_convert.pyrdp -f mp4 +echo pyrdp-convert to MP4 +${ENTRYPOINT["convert",${SOURCE}]} test/files/test_convert.pyrdp -f mp4 echo echo =================================================== @@ -40,8 +47,8 @@ rm test_convert.mp4 echo echo =================================================== -echo pyrdp-convert.py replay to JSON -${PREPEND}pyrdp-convert.py test/files/test_convert.pyrdp -f json +echo pyrdp-convert replay to JSON +${ENTRYPOINT["convert",${SOURCE}]} test/files/test_convert.pyrdp -f json echo echo =================================================== @@ -51,8 +58,8 @@ rm test_convert.json echo echo =================================================== -echo pyrdp-convert.py PCAP to JSON -${PREPEND}pyrdp-convert.py test/files/test_session.pcap -f json +echo pyrdp-convert PCAP to JSON +${ENTRYPOINT["convert",${SOURCE}]} test/files/test_session.pcap -f json echo echo =================================================== @@ -62,8 +69,8 @@ rm "20200319000716_192.168.38.1:20989-192.168.38.1:3389.json" echo echo =================================================== -echo pyrdp-convert.py PCAP to replay -${PREPEND}pyrdp-convert.py test/files/test_session.pcap -f replay +echo pyrdp-convert PCAP to replay +${ENTRYPOINT["convert",${SOURCE}]} test/files/test_session.pcap -f replay echo echo =================================================== @@ -73,7 +80,7 @@ rm "20200319000716_192.168.38.1:20989-192.168.38.1:3389.pyrdp" echo =================================================== echo pyrdp-convert.py regression issue 428 -${PREPEND}pyrdp-convert.py test/files/test_convert_428.pyrdp -f mp4 +${ENTRYPOINT["convert",${SOURCE}]} test/files/test_convert_428.pyrdp -f mp4 echo echo ===================================================