From 2aecb905b3a859abb041942b22b1bba10d2cee9c Mon Sep 17 00:00:00 2001 From: Ajin Abraham Date: Sun, 3 Dec 2023 02:01:10 -0800 Subject: [PATCH] iOS Dynamic Analysis with Corellium (#2194) * iOS Dynamic Analysis Support with Corellium Jailbroken iOS devices * Corellium API layer for complete device and project management * Frida instrumentation (attach, spawn and inject) over SSH local port forward * Shell access over SSH * MobSF httptools proxy integration over SSH remote port forward * Device File upload and download over SSH * Frida scripts for core defense bypass, monitoring, and tracing * Helper iOS Frida scripts for pentesting and malware analysis * Screen cast with touch, swipe and text input support from web UI * Dynamic Analysis device data dump and report Generation * Android Certificate analysis, replaced oscrypto with cryptography for public key parsing * Python minimum support is 3.10 * Bumped httptools to latest, fixes httptools repeat bug * Added unzip to docker to fix a bug --- .github/workflows/mobsf-test.yml | 2 +- .sonarcloud.properties | 2 +- Dockerfile | 4 +- README.md | 4 +- mobsf/DynamicAnalyzer/forms.py | 6 + .../{ => android}/auxiliary/class_trace.js | 0 .../auxiliary/get_loaded_classes.js | 0 .../{ => android}/auxiliary/get_methods.js | 0 .../auxiliary/search_class_pattern.js | 0 .../{ => android}/auxiliary/string_catch.js | 0 .../{ => android}/auxiliary/string_compare.js | 0 .../{ => android}/default/api_monitor.js | 0 .../default/debugger_check_bypass.js | 0 .../{ => android}/default/root_bypass.js | 4 +- .../default/ssl_pinning_bypass.js | 0 .../{ => android}/others/aes_key.js | 0 .../others/bypass_flag_secure.js | 0 .../{ => android}/others/bypass_method.js | 0 .../{ => android}/others/default.js | 0 .../{ => android}/others/file_trace.js | 0 .../others/fingerprint_bypass.js | 0 ...ngerprint_bypass_via_exception_handling.js | 0 .../{ => android}/others/get_android_id.js | 0 .../{ => android}/others/helper.js | 0 .../{ => android}/others/hook_constructor.js | 0 .../others/hook_java_reflection.js | 0 .../{ => android}/others/inputstream_dump.js | 0 .../{ => android}/others/intent_dumper.js | 0 .../others/jni_hook_by_address.js | 0 .../{ => android}/others/jni_trace.js | 0 .../others/keyguard_credential_intent.js | 0 .../{ => android}/others/tracer_cipher.js | 0 .../others/tracer_keygenparameterspec.js | 0 .../{ => android}/others/tracer_keystore.js | 0 .../others/tracer_secretkeyfactory.js | 0 .../others/webview_enable_debugging.js | 0 .../ios/auxiliary/class-trace.js | 49 + .../ios/auxiliary/find-app-classes-methods.js | 52 + .../ios/auxiliary/find-app-classes.js | 39 + .../ios/auxiliary/find-specific-method.js | 53 + .../ios/auxiliary/get-methods.js | 61 + .../ios/auxiliary/search-class-pattern.js | 57 + .../ios/auxiliary/string-capture.js | 14 + .../ios/auxiliary/string-compare.js | 25 + .../ios/default/jailbreak_bypass.js | 143 ++ .../ios/default/jailbreak_bypass2.js | 161 ++ .../tools/frida_scripts/ios/dump/cookies.js | 23 + .../tools/frida_scripts/ios/dump/crypto.js | 282 +++ .../tools/frida_scripts/ios/dump/data-dir.js | 136 ++ .../frida_scripts/ios/dump/file-access.js | 13 + .../tools/frida_scripts/ios/dump/json.js | 15 + .../tools/frida_scripts/ios/dump/keychain.js | 312 ++++ .../tools/frida_scripts/ios/dump/network.js | 44 + .../tools/frida_scripts/ios/dump/nslog.js | 33 + .../ios/dump/nsurlcredentialstorage.js | 39 + .../frida_scripts/ios/dump/nsuserdefaults.js | 32 + .../frida_scripts/ios/dump/pasteboard.js | 31 + .../tools/frida_scripts/ios/dump/sqlite.js | 23 + .../frida_scripts/ios/dump/text-inputs.js | 16 + .../ios/others/app-environment.js | 22 + .../ios/others/bypass-flutter-ssl.js | 43 + .../ios/others/bypass-ssl-ios10.js | 18 + .../ios/others/bypass-ssl-ios11.js | 19 + .../ios/others/bypass-ssl-ios12.js | 51 + .../ios/others/bypass-ssl-ios13.js | 48 + .../ios/others/check-keyboard-cache.js | 38 + .../ios/others/check-keyboard-thridparty.js | 31 + .../ios/others/device-environment.js | 11 + .../ios/others/dump-ios-url-scheme.js | 26 + .../ios/others/find-all-classes-methods.js | 38 + .../ios/others/find-all-classes.js | 30 + .../ios/others/flutter-trace-function.js | 244 +++ .../frida_scripts/ios/others/get-exports.js | 7 + .../frida_scripts/ios/others/get-modules.js | 6 + .../helper-get-memory-address-of-class.js | 7 + .../ios/others/helper-memory-hex-dump.js | 11 + .../ios/others/helper-method-hooking.js | 14 + .../ios/others/helper-read-plist-file.js | 16 + .../others/helper-replace-exported-method.js | 24 + .../helper-show-modify-function-arguments.js | 30 + .../helper-show-modify-method-return-value.js | 31 + .../ios/others/helper-ssh-commands.js | 15 + ...helper-view-and-modify-method-registers.js | 24 + .../others/helper-view-method-arguments.js | 13 + .../frida_scripts/ios/others/ui-ios-alert.js | 12 + .../ios/others/ui-ios-biometric-bypass.js | 22 + .../ios/others/ui-send-url-scheme.js | 58 + .../frida_scripts/ios/others/ui-trace.js | 13 + .../frida_scripts/ios/others/ui-view-trace.js | 15 + .../ios/rpc/get-app-container.js | 11 + .../tools/onDevice/xposed/hooks.json | 389 ---- .../DynamicAnalyzer/views/android/analysis.py | 113 +- .../views/android/dynamic_analyzer.py | 6 +- .../views/android/environment.py | 5 +- .../views/android/frida_core.py | 6 +- .../views/android/frida_scripts.py | 20 +- .../views/android/operations.py | 36 +- mobsf/DynamicAnalyzer/views/android/report.py | 73 +- .../views/android/tests_common.py | 9 +- .../views/android/tests_frida.py | 51 +- mobsf/DynamicAnalyzer/views/common/device.py | 86 + mobsf/DynamicAnalyzer/views/common/frida.py | 67 + mobsf/DynamicAnalyzer/views/common/shared.py | 162 ++ mobsf/DynamicAnalyzer/views/ios/analysis.py | 119 ++ .../views/ios/corellium_apis.py | 528 ++++++ .../views/ios/corellium_instance.py | 839 +++++++++ .../views/ios/corellium_ssh.py | 294 +++ .../views/ios/dynamic_analyzer.py | 179 ++ .../views/ios/frida_auxiliary_scripts.py | 69 + mobsf/DynamicAnalyzer/views/ios/frida_core.py | 262 +++ mobsf/DynamicAnalyzer/views/ios/report.py | 86 + .../DynamicAnalyzer/views/ios/tests_frida.py | 111 ++ mobsf/MobSF/init.py | 12 +- mobsf/MobSF/settings.py | 10 +- mobsf/MobSF/urls.py | 111 +- mobsf/MobSF/utils.py | 119 +- mobsf/MobSF/views/api/api_dynamic_analysis.py | 22 +- mobsf/MobSF/views/api/api_static_analysis.py | 7 +- mobsf/MobSF/views/home.py | 25 +- mobsf/StaticAnalyzer/forms.py | 8 +- .../views/android/cert_analysis.py | 44 +- mobsf/StaticAnalyzer/views/android/find.py | 11 +- .../views/android/manifest_view.py | 10 +- .../views/android/source_tree.py | 5 +- .../views/android/static_analyzer.py | 22 +- mobsf/StaticAnalyzer/views/common/appsec.py | 10 +- mobsf/StaticAnalyzer/views/common/pdf.py | 9 +- .../views/common/shared_func.py | 14 +- .../views/common/suppression.py | 2 +- .../views/ios/static_analyzer.py | 22 +- mobsf/StaticAnalyzer/views/windows/windows.py | 21 +- mobsf/install/windows/setup.py | 4 +- mobsf/static/others/css/devices.min.css | 2 +- mobsf/static/others/js/swipe.js | 133 ++ .../{ => android}/dynamic_analysis.html | 0 .../android/dynamic_analyzer.html | 8 +- .../ios/dynamic_analysis.html | 817 +++++++++ .../ios/dynamic_analyzer.html | 1211 ++++++++++++ .../dynamic_analysis/ios/dynamic_report.html | 1472 +++++++++++++++ .../dynamic_analysis/ios/system_logs.html | 53 + mobsf/templates/general/apidocs.html | 30 +- mobsf/templates/general/dynamic.html | 45 + mobsf/templates/general/recent.html | 4 + poetry.lock | 1632 +++++++++-------- pyproject.toml | 8 +- setup.bat | 6 +- setup.sh | 4 +- tox.ini | 2 +- 148 files changed, 10444 insertions(+), 1604 deletions(-) create mode 100644 mobsf/DynamicAnalyzer/forms.py rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/auxiliary/class_trace.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/auxiliary/get_loaded_classes.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/auxiliary/get_methods.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/auxiliary/search_class_pattern.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/auxiliary/string_catch.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/auxiliary/string_compare.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/default/api_monitor.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/default/debugger_check_bypass.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/default/root_bypass.js (98%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/default/ssl_pinning_bypass.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/aes_key.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/bypass_flag_secure.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/bypass_method.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/default.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/file_trace.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/fingerprint_bypass.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/fingerprint_bypass_via_exception_handling.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/get_android_id.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/helper.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/hook_constructor.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/hook_java_reflection.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/inputstream_dump.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/intent_dumper.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/jni_hook_by_address.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/jni_trace.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/keyguard_credential_intent.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/tracer_cipher.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/tracer_keygenparameterspec.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/tracer_keystore.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/tracer_secretkeyfactory.js (100%) rename mobsf/DynamicAnalyzer/tools/frida_scripts/{ => android}/others/webview_enable_debugging.js (100%) create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/class-trace.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/find-app-classes-methods.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/find-app-classes.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/find-specific-method.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/get-methods.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/search-class-pattern.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/string-capture.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/string-compare.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/default/jailbreak_bypass.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/default/jailbreak_bypass2.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/cookies.js create mode 100755 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/crypto.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/data-dir.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/file-access.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/json.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/keychain.js create mode 100755 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/network.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nslog.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nsurlcredentialstorage.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nsuserdefaults.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/pasteboard.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/sqlite.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/text-inputs.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/app-environment.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-flutter-ssl.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios10.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios11.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios12.js create mode 100755 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios13.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/check-keyboard-cache.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/check-keyboard-thridparty.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/device-environment.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/dump-ios-url-scheme.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/find-all-classes-methods.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/find-all-classes.js create mode 100755 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/flutter-trace-function.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/get-exports.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/get-modules.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-get-memory-address-of-class.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-memory-hex-dump.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-method-hooking.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-read-plist-file.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-replace-exported-method.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-show-modify-function-arguments.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-show-modify-method-return-value.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-ssh-commands.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-view-and-modify-method-registers.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-view-method-arguments.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-ios-alert.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-ios-biometric-bypass.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-send-url-scheme.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-trace.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-view-trace.js create mode 100644 mobsf/DynamicAnalyzer/tools/frida_scripts/ios/rpc/get-app-container.js delete mode 100755 mobsf/DynamicAnalyzer/tools/onDevice/xposed/hooks.json create mode 100644 mobsf/DynamicAnalyzer/views/common/device.py create mode 100644 mobsf/DynamicAnalyzer/views/common/frida.py create mode 100644 mobsf/DynamicAnalyzer/views/common/shared.py create mode 100644 mobsf/DynamicAnalyzer/views/ios/analysis.py create mode 100644 mobsf/DynamicAnalyzer/views/ios/corellium_apis.py create mode 100644 mobsf/DynamicAnalyzer/views/ios/corellium_instance.py create mode 100644 mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py create mode 100644 mobsf/DynamicAnalyzer/views/ios/dynamic_analyzer.py create mode 100644 mobsf/DynamicAnalyzer/views/ios/frida_auxiliary_scripts.py create mode 100644 mobsf/DynamicAnalyzer/views/ios/frida_core.py create mode 100644 mobsf/DynamicAnalyzer/views/ios/report.py create mode 100644 mobsf/DynamicAnalyzer/views/ios/tests_frida.py create mode 100644 mobsf/static/others/js/swipe.js rename mobsf/templates/dynamic_analysis/{ => android}/dynamic_analysis.html (100%) create mode 100644 mobsf/templates/dynamic_analysis/ios/dynamic_analysis.html create mode 100644 mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html create mode 100644 mobsf/templates/dynamic_analysis/ios/dynamic_report.html create mode 100644 mobsf/templates/dynamic_analysis/ios/system_logs.html create mode 100644 mobsf/templates/general/dynamic.html diff --git a/.github/workflows/mobsf-test.yml b/.github/workflows/mobsf-test.yml index a71f3117f6..045fa14134 100644 --- a/.github/workflows/mobsf-test.yml +++ b/.github/workflows/mobsf-test.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-22.04, macos-latest, windows-latest] - python-version: [3.9, '3.10', '3.11'] + python-version: ['3.10', '3.11'] runs-on: ${{ matrix.os }} steps: diff --git a/.sonarcloud.properties b/.sonarcloud.properties index e122090e90..28eead4702 100644 --- a/.sonarcloud.properties +++ b/.sonarcloud.properties @@ -1,4 +1,4 @@ sonar.sources=. sonar.exclusions=mobsf/static/**/*,mobsf/templates/**/* sonar.sourceEncoding=UTF-8 -sonar.python.version=3.7, 3.8, 3.9, 3.10, 3.11 \ No newline at end of file +sonar.python.version=3.10, 3.11 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 80696cb314..8e742fcfea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,8 +32,10 @@ RUN apt update -y && apt install -y --no-install-recommends \ curl \ git \ jq \ + unzip \ android-tools-adb && \ - locale-gen en_US.UTF-8 + locale-gen en_US.UTF-8 && \ + apt upgrade -y ENV MOBSF_USER=mobsf \ MOBSF_PLATFORM=docker \ diff --git a/README.md b/README.md index 6aef69b6f9..c2b8b9218a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Mobile Security Framework (MobSF) -Version: v3.7 beta +Version: v3.8 beta ![](https://cloud.githubusercontent.com/assets/4301109/20019521/cc61f7fc-a2f2-11e6-95f3-407030d9fdde.png) @@ -7,7 +7,7 @@ Mobile Security Framework (MobSF) is an automated, all-in-one mobile application Made with ![Love](https://cloud.githubusercontent.com/assets/4301109/16754758/82e3a63c-4813-11e6-9430-6015d98aeaab.png) in India -[![python](https://img.shields.io/badge/python-3.9+-blue.svg?logo=python&labelColor=yellow)](https://www.python.org/downloads/) +[![python](https://img.shields.io/badge/python-3.10+-blue.svg?logo=python&labelColor=yellow)](https://www.python.org/downloads/) [![PyPI version](https://badge.fury.io/py/mobsf.svg)](https://badge.fury.io/py/mobsf) [![platform](https://img.shields.io/badge/platform-osx%2Flinux%2Fwindows-green.svg)](https://github.com/MobSF/Mobile-Security-Framework-MobSF/) [![License](https://img.shields.io/:license-GPL--3.0--only-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.html) diff --git a/mobsf/DynamicAnalyzer/forms.py b/mobsf/DynamicAnalyzer/forms.py new file mode 100644 index 0000000000..2695513011 --- /dev/null +++ b/mobsf/DynamicAnalyzer/forms.py @@ -0,0 +1,6 @@ +"""File upload to iOS form.""" +from django import forms + + +class UploadFileForm(forms.Form): + file = forms.FileField() diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/auxiliary/class_trace.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/class_trace.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/auxiliary/class_trace.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/class_trace.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/auxiliary/get_loaded_classes.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/get_loaded_classes.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/auxiliary/get_loaded_classes.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/get_loaded_classes.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/auxiliary/get_methods.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/get_methods.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/auxiliary/get_methods.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/get_methods.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/auxiliary/search_class_pattern.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/search_class_pattern.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/auxiliary/search_class_pattern.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/search_class_pattern.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/auxiliary/string_catch.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/string_catch.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/auxiliary/string_catch.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/string_catch.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/auxiliary/string_compare.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/string_compare.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/auxiliary/string_compare.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/auxiliary/string_compare.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/default/api_monitor.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/api_monitor.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/default/api_monitor.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/api_monitor.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/default/debugger_check_bypass.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/debugger_check_bypass.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/default/debugger_check_bypass.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/debugger_check_bypass.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/default/root_bypass.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/root_bypass.js similarity index 98% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/default/root_bypass.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/root_bypass.js index 0c357cebe0..b056e441a5 100644 --- a/mobsf/DynamicAnalyzer/tools/frida_scripts/default/root_bypass.js +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/root_bypass.js @@ -33,8 +33,8 @@ Java.performNow(function () { }; // String.contains check - var String = Java.use('java.lang.String'); - String.contains.implementation = function (name) { + var javaString = Java.use('java.lang.String'); + javaString.contains.implementation = function (name) { if (name == "test-keys") { send("[RootDetection Bypass] test-keys check"); return false; diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/default/ssl_pinning_bypass.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/ssl_pinning_bypass.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/default/ssl_pinning_bypass.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/default/ssl_pinning_bypass.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/aes_key.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/aes_key.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/aes_key.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/aes_key.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/bypass_flag_secure.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/bypass_flag_secure.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/bypass_flag_secure.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/bypass_flag_secure.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/bypass_method.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/bypass_method.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/bypass_method.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/bypass_method.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/default.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/default.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/default.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/default.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/file_trace.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/file_trace.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/file_trace.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/file_trace.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/fingerprint_bypass.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/fingerprint_bypass.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/fingerprint_bypass.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/fingerprint_bypass.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/fingerprint_bypass_via_exception_handling.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/fingerprint_bypass_via_exception_handling.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/fingerprint_bypass_via_exception_handling.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/fingerprint_bypass_via_exception_handling.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/get_android_id.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/get_android_id.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/get_android_id.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/get_android_id.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/helper.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/helper.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/helper.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/helper.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/hook_constructor.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/hook_constructor.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/hook_constructor.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/hook_constructor.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/hook_java_reflection.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/hook_java_reflection.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/hook_java_reflection.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/hook_java_reflection.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/inputstream_dump.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/inputstream_dump.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/inputstream_dump.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/inputstream_dump.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/intent_dumper.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/intent_dumper.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/intent_dumper.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/intent_dumper.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/jni_hook_by_address.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/jni_hook_by_address.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/jni_hook_by_address.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/jni_hook_by_address.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/jni_trace.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/jni_trace.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/jni_trace.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/jni_trace.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/keyguard_credential_intent.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/keyguard_credential_intent.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/keyguard_credential_intent.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/keyguard_credential_intent.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/tracer_cipher.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/tracer_cipher.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/tracer_cipher.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/tracer_cipher.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/tracer_keygenparameterspec.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/tracer_keygenparameterspec.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/tracer_keygenparameterspec.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/tracer_keygenparameterspec.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/tracer_keystore.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/tracer_keystore.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/tracer_keystore.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/tracer_keystore.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/tracer_secretkeyfactory.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/tracer_secretkeyfactory.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/tracer_secretkeyfactory.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/tracer_secretkeyfactory.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/others/webview_enable_debugging.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/webview_enable_debugging.js similarity index 100% rename from mobsf/DynamicAnalyzer/tools/frida_scripts/others/webview_enable_debugging.js rename to mobsf/DynamicAnalyzer/tools/frida_scripts/android/others/webview_enable_debugging.js diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/class-trace.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/class-trace.js new file mode 100644 index 0000000000..ffcb179097 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/class-trace.js @@ -0,0 +1,49 @@ +/* Description: Hook all the methods of a particular class + * Mode: S+A + * Version: 1.0 + * Credit: https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +function hook_class_method(class_name, method_name) +{ + var hook = ObjC.classes[class_name][method_name]; + Interceptor.attach(hook.implementation, { + onEnter: function(args) { + send("[AUXILIARY] Detected call to: " + class_name + " -> " + method_name); + } + }); +} + +function run_hook_all_methods_of_specific_class(className_arg) +{ + send("Started: Hook all methods of a specific class"); + send("Class Name: " + className_arg); + //Your class name here + var className = className_arg; + //var methods = ObjC.classes[className].$methods; + var methods = ObjC.classes[className].$ownMethods; + for (var i = 0; i < methods.length; i++) + { + send("[AUXILIARY] [-] "+methods[i]); + send("[AUXILIARY] \t[*] Hooking into implementation"); + //eval('var className2 = "'+className+'"; var funcName2 = "'+methods[i]+'"; var hook = eval(\'ObjC.classes.\'+className2+\'["\'+funcName2+\'"]\'); Interceptor.attach(hook.implementation, { onEnter: function(args) { console.log("[*] Detected call to: " + className2 + " -> " + funcName2); } });'); + var className2 = className; + var funcName2 = methods[i]; + hook_class_method(className2, funcName2); + // send("[AUXILIARY] \t[*] Hooking successful"); + } + send("[AUXILIARY] Completed: Hook all methods of a specific class"); +} + +function hook_all_methods_of_specific_class(className_arg) +{ + try { + setImmediate(run_hook_all_methods_of_specific_class,[className_arg]) + } catch(err) {} +} + + +//Your class name goes here +hook_all_methods_of_specific_class('{{CLASS}}') \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/find-app-classes-methods.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/find-app-classes-methods.js new file mode 100644 index 0000000000..3b5016b91c --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/find-app-classes-methods.js @@ -0,0 +1,52 @@ +/* Description: Dump all methods inside classes owned by the app only + * Mode: S+A + * Version: 1.0 + * Credit: PassionFruit (https://github.com/chaitin/passionfruit/blob/master/agent/app/classdump.js) & https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +function run_show_app_classes_methods_only() +{ + send("Started: Find App's Classes and Methods") + var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer']) + var copyClassNamesForImage = new NativeFunction(Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer']) + var p = Memory.alloc(Process.pointerSize) + Memory.writeUInt(p, 0) + var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String() + var pPath = Memory.allocUtf8String(path) + var pClasses = copyClassNamesForImage(pPath, p) + var count = Memory.readUInt(p) + var classesArray = new Array(count) + for (var i = 0; i < count; i++) + { + var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize)) + classesArray[i] = Memory.readUtf8String(pClassName) + var className = classesArray[i] + send("[AUXILIARY] Class: " + className); + //var methods = ObjC.classes[className].$methods; + var methods = ObjC.classes[className].$ownMethods; + for (var j = 0; j < methods.length; j++) + { + send("[AUXILIARY] \t[-] Method: " + methods[j]); + try + { + send("[AUXILIARY] \t\t[-] Arguments Type: " + ObjC.classes[className][methods[j]].argumentTypes); + send("[AUXILIARY] \t\t[-] Return Type: " + ObjC.classes[className][methods[j]].returnType); + } + catch(err) {} + } + } + free(pClasses) + send("App Classes found: " + count); + send("Completed: Find App's Classes") +} + +function show_app_classes_methods_only() +{ + try { + setImmediate(run_show_app_classes_methods_only) + } catch(err) {} +} + +show_app_classes_methods_only() diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/find-app-classes.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/find-app-classes.js new file mode 100644 index 0000000000..921a6d9a0a --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/find-app-classes.js @@ -0,0 +1,39 @@ +/* Description: Dump classes owned by the app only + * Mode: S+A + * Version: 1.0 + * Credit: PassionFruit (https://github.com/chaitin/passionfruit/blob/master/agent/app/classdump.js) & https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +function run_show_app_classes_only() +{ + send("Started: Find App's Classes") + var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer']) + var copyClassNamesForImage = new NativeFunction(Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer']) + var p = Memory.alloc(Process.pointerSize) + Memory.writeUInt(p, 0) + var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String() + var pPath = Memory.allocUtf8String(path) + var pClasses = copyClassNamesForImage(pPath, p) + var count = Memory.readUInt(p) + var classesArray = new Array(count) + for (var i = 0; i < count; i++) + { + var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize)) + classesArray[i] = Memory.readUtf8String(pClassName) + send("[AUXILIARY] " + classesArray[i]) + } + free(pClasses) + send("App Classes found: " + count); + send("Completed: Find App's Classes") +} + +function show_app_classes_only() +{ + try { + setImmediate(run_show_app_classes_only) + } catch(err) {} +} + +show_app_classes_only() diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/find-specific-method.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/find-specific-method.js new file mode 100644 index 0000000000..3b1dc67968 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/find-specific-method.js @@ -0,0 +1,53 @@ +/* Description: Find a specific method in all classes in the app + * Modified for MobSF + * Mode: S+A + * Version: 1.0 + * Credit: PassionFruit (https://github.com/chaitin/passionfruit/blob/master/agent/app/classdump.js) & https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +function find_specific_method_in_all_classes(func_name) +{ + send("Searching for method [" + func_name + "] in all Classes"); + var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer']) + var copyClassNamesForImage = new NativeFunction(Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer']) + var p = Memory.alloc(Process.pointerSize) + Memory.writeUInt(p, 0) + var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String() + var pPath = Memory.allocUtf8String(path) + var pClasses = copyClassNamesForImage(pPath, p) + var count = Memory.readUInt(p) + var classesArray = new Array(count) + for (var i = 0; i < count; i++) + { + var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize)) + classesArray[i] = Memory.readUtf8String(pClassName) + var className = classesArray[i] + //var methods = ObjC.classes[className].$methods; + var methods = ObjC.classes[className].$ownMethods; + for (var j = 0; j < methods.length; j++) + { + if(methods[j].includes(func_name)) + { + send("[AUXILIARY] Class: " + className); + send("[AUXILIARY] \t[-] Method: " + methods[j]); + try + { + send("[AUXILIARY] \t\t[-] Arguments Type: " + ObjC.classes[className][methods[j]].argumentTypes); + send("[AUXILIARY] \t\t[-] Return Type: " + ObjC.classes[className][methods[j]].returnType); + } + catch(err) {} + } + } + } + free(pClasses) + send("Completed: Find specific Method in all Classes"); +} + + +//Your function name goes here +var METHOD = '{{METHOD}}' +try { + find_specific_method_in_all_classes(METHOD) +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/get-methods.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/get-methods.js new file mode 100644 index 0000000000..d8bc6950e7 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/get-methods.js @@ -0,0 +1,61 @@ +/* Description: Get all methods of the class + * Modified for MobSF + * Mode: S+A + * Version: 1.0 + * Credit: PassionFruit (https://github.com/chaitin/passionfruit/blob/master/agent/app/classdump.js) & https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +function run_get_app_methods_in_class() +{ + var targetClass = '{{CLASS}}'; + var found = false; + send("Looking for methods in: " + targetClass) + + var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer']) + var copyClassNamesForImage = new NativeFunction(Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer']) + var p = Memory.alloc(Process.pointerSize) + Memory.writeUInt(p, 0) + var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String() + var pPath = Memory.allocUtf8String(path) + var pClasses = copyClassNamesForImage(pPath, p) + var count = Memory.readUInt(p) + var classesArray = new Array(count) + for (var i = 0; i < count; i++) + { + var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize)) + classesArray[i] = Memory.readUtf8String(pClassName) + var className = classesArray[i] + if (className === targetClass) + { + found = true; + send("[AUXILIARY] Class: " + className); + //var methods = ObjC.classes[className].$methods; + var methods = ObjC.classes[className].$ownMethods; + for (var j = 0; j < methods.length; j++) + { + send("[AUXILIARY] \t[-] Method: " + methods[j]); + try + { + send("[AUXILIARY] \t\t[-] Arguments Type: " + ObjC.classes[className][methods[j]].argumentTypes); + send("[AUXILIARY] \t\t[-] Return Type: " + ObjC.classes[className][methods[j]].returnType); + } + catch(err) {} + } + } + } + free(pClasses) + if (!found) + { + send("Class not found: " + targetClass) + } else + { + send("Completed Enumerating Methods in Class: " + targetClass) + } +} + + +try { + run_get_app_methods_in_class() +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/search-class-pattern.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/search-class-pattern.js new file mode 100644 index 0000000000..502415bb0f --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/search-class-pattern.js @@ -0,0 +1,57 @@ +/* Description: Find classes matching a pattern + * Modified for MobSF + * Mode: S+A + * Version: 1.0 + * Credit: PassionFruit (https://github.com/chaitin/passionfruit/blob/master/agent/app/classdump.js) & https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +function findClasses(pattern) +{ + var foundClasses = []; + var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer']) + var copyClassNamesForImage = new NativeFunction(Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer']) + var p = Memory.alloc(Process.pointerSize) + Memory.writeUInt(p, 0) + var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String() + var pPath = Memory.allocUtf8String(path) + var pClasses = copyClassNamesForImage(pPath, p) + var count = Memory.readUInt(p) + var classesArray = new Array(count) + for (var i = 0; i < count; i++) + { + var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize)) + classesArray[i] = Memory.readUtf8String(pClassName) + if (classesArray[i].match(pattern)) { + foundClasses.push( classesArray[i]); + } + } + free(pClasses) + return foundClasses; +} + + +function getMatches(){ + var matches; + try{ + var pattern = /{{PATTERN}}/i; + send('Class search for pattern: ' + pattern) + matches = findClasses(pattern); + }catch (err){ + send('Class pattern match [\"Error\"] => ' + err); + return; + } + if (matches.length>0) + send('Found [' + matches.length + '] matches') + else + send('No matches found') + matches.forEach(function(clz) { + send('[AUXILIARY] ' + clz) + }); +} + + +try { + getMatches(); +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/string-capture.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/string-capture.js new file mode 100644 index 0000000000..6ddb379eae --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/string-capture.js @@ -0,0 +1,14 @@ +function captureString() { + send('Capturing strings') + Interceptor.attach(ObjC.classes.NSString['+ stringWithUTF8String:'].implementation, { + onLeave: function (retval) { + var str = new ObjC.Object(ptr(retval)).toString() + send('[AUXILIARY] [NSString stringWithUTF8String:] -> '+ str); + return retval; + } + }); +} + +try { + captureString(); +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/string-compare.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/string-compare.js new file mode 100644 index 0000000000..6d4997ac55 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/auxiliary/string-compare.js @@ -0,0 +1,25 @@ +function captureStringCompare() { + send('Capturing string comparisons') + Interceptor.attach(ObjC.classes.__NSCFString['- isEqualToString:'].implementation, { + onEnter: function (args) { + var str = new ObjC.Object(ptr(args[2])).toString() + send('[AUXILIARY] [__NSCFString isEqualToString:] -> '+ str); + } + }); +} + +function captureStringCompare2(){ + Interceptor.attach(ObjC.classes.NSTaggedPointerString['- isEqualToString:'].implementation, { + onEnter: function (args) { + var str = new ObjC.Object(ptr(args[2])).toString() + send('[AUXILIARY] NSTaggedPointerString[- isEqualToString:] -> '+ str); + } + }); +} +try { + captureStringCompare(); +} catch(err) {} + +try { + captureStringCompare2(); +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/default/jailbreak_bypass.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/default/jailbreak_bypass.js new file mode 100644 index 0000000000..53ac31ec4f --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/default/jailbreak_bypass.js @@ -0,0 +1,143 @@ +function bypassJailbreakDetection(){ + + var paths = [ + "/Applications/blackra1n.app", + "/Applications/Cydia.app", + "/Applications/FakeCarrier.app", + "/Applications/Icy.app", + "/Applications/IntelliScreen.app", + "/Applications/MxTube.app", + "/Applications/RockApp.app", + "/Applications/SBSetttings.app", + "/Applications/WinterBoard.app", + "/bin/bash", + "/bin/sh", + "/bin/su", + "/etc/apt", + "/etc/ssh/sshd_config", + "/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist", + "/Library/MobileSubstrate/DynamicLibraries/Veency.plist", + "/Library/MobileSubstrate/MobileSubstrate.dylib", + "/pguntether", + "/private/var/lib/cydia", + "/private/var/mobile/Library/SBSettings/Themes", + "/private/var/stash", + "/private/var/tmp/cydia.log", + "/System/Library/LaunchDaemons/com.ikey.bbot.plist", + "/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist", + "/usr/bin/cycript", + "/usr/bin/ssh", + "/usr/bin/sshd", + "/usr/libexec/sftp-server", + "/usr/libexec/ssh-keysign", + "/usr/sbin/frida-server", + "/usr/sbin/sshd", + "/var/cache/apt", + "/var/lib/cydia", + "/var/log/syslog", + "/var/mobile/Media/.evasi0n7_installed", + "/var/tmp/cydia.log", + "/etc/apt", + "/Library/MobileSubstrate/MobileSubstrate.dylib", + "/Applications/Cydia.app", + "/Applications/blackra1n.app", + "/Applications/FakeCarrier.app", + "/Applications/Icy.app", + "/Applications/IntelliScreen.app", + "/Applications/MxTube.app", + "/Applications/RockApp.app", + "/Applications/SBSetttings.app", + "/private/var/lib/apt/", + "/Applications/WinterBoard.app", + "/usr/sbin/sshd", + "/private/var/tmp/cydia.log", + "/usr/binsshd", + "/usr/libexec/sftp-server", + "/Systetem/Library/LaunchDaemons/com.ikey.bbot.plist", + "/System/Library/LaunchDaemons/com.saurik.Cy@dia.Startup.plist", + "/var/log/syslog", + "/bin/bash", + "/bin/sh", + "/etc/ssh/sshd_config", + "/usr/libexec/ssh-keysign", + "/Library/MobileSubstrate/DynamicLibraries/Veency.plist", + "/System/Library/LaunchDaemons/com.ikey.bbot.plist", + "/private/var/stash", + "/usr/bin/cycript", + "/usr/bin/ssh", + "/usr/bin/sshd", + "/var/cache/apt", + "/var/lib/cydia", + "/var/tmp/cydia.log", + "/Applications/SBSettings.app", + "/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist", + "/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist", + "/private/var/lib/apt", + "/private/var/lib/cydia", + "/private/var/mobile/Library/SBSettings/Themes", + "/var/lib/apt", + "/private/jailbreak.txt", + "/bin/su", + "/pguntether", + "/usr/sbin/frida-server", + "/private/Jailbreaktest.txt", + "/var/mobile/Media/.evasi0n7_installed", + "cydia://package/com.example.package" + ]; + + try { + + var f = Module.findExportByName("libSystem.B.dylib", "stat64"); + Interceptor.attach(f, { + onEnter: function(args) { + this.is_common_path = false; + var arg = Memory.readUtf8String(args[0]); + for (var path in paths) { + if (arg.indexOf(paths[path]) > -1) { + send('[Jailbreak Detection Bypass] Hooking native function stat64: ' + arg); + this.is_common_path = true; + return -1; + } + } + }, + onLeave: function(retval) { + if (this.is_common_path) { + // send("stat64 Bypass!!!"); + retval.replace(-1); + } + } + }); + var f = Module.findExportByName("libSystem.B.dylib", "stat"); + Interceptor.attach(f, { + onEnter: function(args) { + this.is_common_path = false; + var arg = Memory.readUtf8String(args[0]); + for (var path in paths) { + if (arg.indexOf(paths[path]) > -1) { + send('[Jailbreak Detection Bypass] Hooking native function stat: ' + arg); + this.is_common_path = true; + return -1; + } + } + }, + onLeave: function(retval) { + if (this.is_common_path) { + // send("stat Bypass!!!"); + retval.replace(-1); + } + } + }); + send('[Jailbreak Detection Bypass] success'); + } + catch(e) { + send('[Jailbreak Detection Bypass] script error: ' + e.toString()); + } +} + +try { + if (ObjC.available) { + bypassJailbreakDetection(); + } else { + send('[Jailbreak Detection Bypass] error: Objective-C Runtime is not available!'); + } +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/default/jailbreak_bypass2.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/default/jailbreak_bypass2.js new file mode 100644 index 0000000000..f8e83dcf9f --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/default/jailbreak_bypass2.js @@ -0,0 +1,161 @@ +var paths = [ + "/Applications/blackra1n.app", + "/Applications/Cydia.app", + "/Applications/FakeCarrier.app", + "/Applications/Icy.app", + "/Applications/IntelliScreen.app", + "/Applications/MxTube.app", + "/Applications/RockApp.app", + "/Applications/SBSetttings.app", + "/Applications/WinterBoard.app", + "/bin/bash", + "/bin/sh", + "/bin/su", + "/etc/apt", + "/etc/ssh/sshd_config", + "/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist", + "/Library/MobileSubstrate/DynamicLibraries/Veency.plist", + "/Library/MobileSubstrate/MobileSubstrate.dylib", + "/pguntether", + "/private/var/lib/cydia", + "/private/var/mobile/Library/SBSettings/Themes", + "/private/var/stash", + "/private/var/tmp/cydia.log", + "/System/Library/LaunchDaemons/com.ikey.bbot.plist", + "/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist", + "/usr/bin/cycript", + "/usr/bin/ssh", + "/usr/bin/sshd", + "/usr/libexec/sftp-server", + "/usr/libexec/ssh-keysign", + "/usr/sbin/frida-server", + "/usr/sbin/sshd", + "/var/cache/apt", + "/var/lib/cydia", + "/var/log/syslog", + "/var/mobile/Media/.evasi0n7_installed", + "/var/tmp/cydia.log", + "/etc/apt", + "/Library/MobileSubstrate/MobileSubstrate.dylib", + "/Applications/Cydia.app", + "/Applications/blackra1n.app", + "/Applications/FakeCarrier.app", + "/Applications/Icy.app", + "/Applications/IntelliScreen.app", + "/Applications/MxTube.app", + "/Applications/RockApp.app", + "/Applications/SBSetttings.app", + "/private/var/lib/apt/", + "/Applications/WinterBoard.app", + "/usr/sbin/sshd", + "/private/var/tmp/cydia.log", + "/usr/binsshd", + "/usr/libexec/sftp-server", + "/Systetem/Library/LaunchDaemons/com.ikey.bbot.plist", + "/System/Library/LaunchDaemons/com.saurik.Cy@dia.Startup.plist", + "/var/log/syslog", + "/bin/bash", + "/bin/sh", + "/etc/ssh/sshd_config", + "/usr/libexec/ssh-keysign", + "/Library/MobileSubstrate/DynamicLibraries/Veency.plist", + "/System/Library/LaunchDaemons/com.ikey.bbot.plist", + "/private/var/stash", + "/usr/bin/cycript", + "/usr/bin/ssh", + "/usr/bin/sshd", + "/var/cache/apt", + "/var/lib/cydia", + "/var/tmp/cydia.log", + "/Applications/SBSettings.app", + "/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist", + "/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist", + "/private/var/lib/apt", + "/private/var/lib/cydia", + "/private/var/mobile/Library/SBSettings/Themes", + "/var/lib/apt", + "/private/jailbreak.txt", + "/bin/su", + "/pguntether", + "/usr/sbin/frida-server", + "/private/Jailbreaktest.txt", + "/var/mobile/Media/.evasi0n7_installed", + "cydia://package/com.example.package" +]; + +function bypassJailbreakDetection2() { +try { + + var resolver = new ApiResolver('objc'); + + resolver.enumerateMatches('*[* *jail**]', { + onMatch: function(match) { + var ptr = match["address"]; + Interceptor.attach(ptr, { + onEnter: function() {}, + onLeave: function(retval) { + retval.replace(0x0); + } + }); + }, + onComplete: function() {} + }); + + resolver.enumerateMatches('*[* fileExistsAtPath*]', { + onMatch: function(match) { + var ptr = match["address"]; + Interceptor.attach(ptr, { + onEnter: function(args) { + var path = ObjC.Object(args[2]).toString(); + this.jailbreakCall = false; + for (var i = 0; i < paths.length; i++) { + if (paths[i] == path) { + this.jailbreakCall = true; + } + } + }, + onLeave: function(retval) { + if (this.jailbreakCall) { + retval.replace(0x0); + } + } + }); + }, + onComplete: function() {} + }); + + resolver.enumerateMatches('*[* canOpenURL*]', { + onMatch: function(match) { + var ptr = match["address"]; + Interceptor.attach(ptr, { + onEnter: function(args) { + var url = ObjC.Object(args[2]).toString(); + this.jailbreakCall = false; + if (url.indexOf("cydia") >= 0) { + this.jailbreakCall = true; + } + }, + onLeave: function(retval) { + if (this.jailbreakCall) { + retval.replace(0x0); + } + } + }); + }, + onComplete: function() {} + }); + send("[Jailbreak Detection Bypass 2] success"); + } + catch(e) { + send('[Jailbreak Detection Bypass 2] script error:' + e.toString()); + } +} + + +try { + if (ObjC.available) { + bypassJailbreakDetection2(); + } else { + send('[Jailbreak Detection Bypass 2] error: Objective-C Runtime is not available!'); + } +} catch(err) {} diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/cookies.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/cookies.js new file mode 100644 index 0000000000..c8e6284412 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/cookies.js @@ -0,0 +1,23 @@ +function dumpCookies(){ + send('Dumping Cookies'); + var cookieArr = []; + var cookies = ObjC.classes.NSHTTPCookieStorage.sharedHTTPCookieStorage().cookies(); + for (var i = 0, l = cookies.count(); i < l; i++) { + var cookie = cookies['- objectAtIndex:'](i); + var expiry = cookie.expiresDate() ? cookie.expiresDate().toString() : 'null'; + cookieArr.push({ + name: cookie.Name().toString(), + value: cookie.Value().toString(), + domain: cookie.domain().toString(), + path: cookie.path().toString(), + expiry: expiry, + httponly: cookie.isHTTPOnly().toString(), + secure: cookie.isSecure().toString(), + version: cookie.version().toString(), + }); + send(JSON.stringify({'[MBSFDUMP] cookies': cookieArr})); + } +} +try { + dumpCookies(); +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/crypto.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/crypto.js new file mode 100755 index 0000000000..03603fb481 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/crypto.js @@ -0,0 +1,282 @@ +/* Description: iOS Intercepts Crypto Operations + * Mode: S+A + * Version: 1.0 + * Credit: https://github.com/federicodotta/Brida + * Author: @federicodotta + */ + +function CCCrypt(){ + Interceptor.attach(Module.findExportByName("libSystem.B.dylib","CCCrypt"), + { + onEnter: function(args) { + + var cccrypt = { + 'CCOperation': parseInt(args[0]), + 'CCAlgorithm': parseInt(args[1]), + 'CCOptions': parseInt(args[2]), + } + + if(ptr(args[3]) != 0 ) { + cccrypt['Key'] = base64ArrayBuffer(Memory.readByteArray(ptr(args[3]),parseInt(args[4]))); + } else { + cccrypt['Key'] = 0; + } + + if(ptr(args[5]) != 0 ) { + cccrypt['IV'] = base64ArrayBuffer(Memory.readByteArray(ptr(args[5]),16)); + } else { + cccrypt['IV'] = 0; + } + + this.dataInLength = parseInt(args[7]); + + if(ptr(args[6]) != 0 ) { + + cccrypt['dataInput'] = base64ArrayBuffer(Memory.readByteArray(ptr(args[6]),this.dataInLength)) + + } else { + cccrypt['dataInput'] = null; + } + + this.dataOut = args[8]; + this.dataOutLength = args[10]; + send(JSON.stringify({'[MBSFDUMP] crypto': cccrypt})); + + }, + + onLeave: function(retval) { + var cccrypt_re = {}; + if(ptr(this.dataOut) != 0 ) { + cccrypt_re['dataOutput'] = base64ArrayBuffer(Memory.readByteArray(this.dataOut,parseInt(ptr(Memory.readU32(ptr(this.dataOutLength),4))))); + + } else { + cccrypt_re['dataOutput'] = null; + } + send(JSON.stringify({'[MBSFDUMP] crypto': cccrypt_re})); + + } + +}); +} +function CCCryptorCreate(){ + Interceptor.attach(Module.findExportByName("libSystem.B.dylib","CCCryptorCreate"), + { + onEnter: function(args) { + + var cccryptorcreate = { + 'CCOperation': parseInt(args[0]), + 'CCAlgorithm': parseInt(args[1]), + 'CCOptions': parseInt(args[2]), + } + + if(ptr(args[3]) != 0 ) { + cccryptorcreate['Key'] = base64ArrayBuffer(Memory.readByteArray(ptr(args[3]),parseInt(args[4]))); + + } else { + cccryptorcreate['Key'] = 0; + } + + if(ptr(args[5]) != 0 ) { + cccryptorcreate['IV'] = base64ArrayBuffer(Memory.readByteArray(ptr(args[5]),16)); + } else { + cccryptorcreate['IV'] = 0; + } + send(JSON.stringify({'[MBSFDUMP] crypto': cccryptorcreate})); + + }, + onLeave: function(retval) { + } + + }); +} +function CCCryptorUpdate(){ + Interceptor.attach(Module.findExportByName("libSystem.B.dylib","CCCryptorUpdate"), + { + onEnter: function(args) { + var cryptorupdate = {} + if(ptr(args[1]) != 0) { + cryptorupdate['dataInput'] = base64ArrayBuffer(Memory.readByteArray(ptr(args[1]),parseInt(args[2]))); + + } else { + cryptorupdate['dataInput'] = null; + } + + //this.len = args[4]; + this.len = args[5]; + this.out = args[3]; + send(JSON.stringify({'[MBSFDUMP] crypto': cryptorupdate})); + + }, + + onLeave: function(retval) { + var cryptorupdate_re = {} + if(ptr(this.out) != 0) { + cryptorupdate_re['dataOutput'] = base64ArrayBuffer(Memory.readByteArray(this.out,parseInt(ptr(Memory.readU32(ptr(this.len),4))))) + } else { + cryptorupdate_re['dataOutput'] = null; + } + send(JSON.stringify({'[MBSFDUMP] crypto': cryptorupdate_re})); + } + +}); +} + +function CCCryptorFinal(){ + Interceptor.attach(Module.findExportByName("libSystem.B.dylib","CCCryptorFinal"), + { + onEnter: function(args) { + //this.len2 = args[2]; + this.len2 = args[3]; + this.out2 = args[1]; + }, + onLeave: function(retval) { + var cccryptorfinal_re = {} + if(ptr(this.out2) != 0) { + cccryptorfinal_re['dataOutput'] = base64ArrayBuffer(Memory.readByteArray(this.out2,parseInt(ptr(Memory.readU32(ptr(this.len2),4))))) + } else { + cccryptorfinal_re['dataOutput'] = null; + } + send(JSON.stringify({'[MBSFDUMP] crypto': cccryptorfinal_re})); + } + +}); +} + +function CC_SHA1_Init(){ + //CC_SHA1_Init(CC_SHA1_CTX *c); + Interceptor.attach(Module.findExportByName("libSystem.B.dylib","CC_SHA1_Init"), + { + onEnter: function(args) { + send(JSON.stringify({'[MBSFDUMP] crypto': { + 'operation': 'CC_SHA1_Init', + 'contextAddress': args[0], + }})); + } + }); +} + +function CC_SHA1_Update(){ + //CC_SHA1_Update(CC_SHA1_CTX *c, const void *data, CC_LONG len); + Interceptor.attach(Module.findExportByName("libSystem.B.dylib","CC_SHA1_Update"), + { + onEnter: function(args) { + var ccsha1update = { + 'operation': 'CC_SHA1_Update', + 'contextAddress': args[0], + } + if(ptr(args[1]) != 0) { + ccsha1update['data'] = base64ArrayBuffer(Memory.readByteArray(ptr(args[1]),parseInt(args[2]))); + } else { + ccsha1update['data'] = null; + } + send(JSON.stringify({'[MBSFDUMP] crypto': ccsha1update})); + } + }); +} + +function CC_SHA1_Final(){ + //CC_SHA1_Final(unsigned char *md, CC_SHA1_CTX *c); + Interceptor.attach(Module.findExportByName("libSystem.B.dylib","CC_SHA1_Final"), + { + onEnter: function(args) { + this.mdSha = args[0]; + this.ctxSha = args[1]; + }, + onLeave: function(retval) { + var ccsha1final_ret = { + 'operation': 'CC_SHA1_Final', + 'contextAddress': this.ctxSha, + } + if(ptr(this.mdSha) != 0) { + ccsha1final_ret['hash'] = base64ArrayBuffer(Memory.readByteArray(ptr(this.mdSha),20)); + + } else { + ccsha1final_ret['hash'] = null; + } + send(JSON.stringify({'[MBSFDUMP] crypto': ccsha1final_ret})); + } + }); +} + + +try { + send("Tracing Crypto Operations"); + CCCrypt(); +} catch(err) {} +try { + CCCryptorCreate(); +} catch(err) {} + +try { + CCCryptorUpdate(); +} catch(err) {} + +try { + CCCryptorFinal(); +} catch(err) {} + +try { + CC_SHA1_Init(); +} catch(err) {} + +try { + CC_SHA1_Update(); +} catch(err) {} + +try { + CC_SHA1_Final(); +} catch(err) {} + +// Native ArrayBuffer to Base64 +// https://gist.github.com/jonleighton/958841 +function base64ArrayBuffer(arrayBuffer) { + var base64 = '' + var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + + var bytes = new Uint8Array(arrayBuffer) + var byteLength = bytes.byteLength + var byteRemainder = byteLength % 3 + var mainLength = byteLength - byteRemainder + + var a, b, c, d + var chunk + + // Main loop deals with bytes in chunks of 3 + for (var i = 0; i < mainLength; i = i + 3) { + // Combine the three bytes into a single integer + chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2] + + // Use bitmasks to extract 6-bit segments from the triplet + a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18 + b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12 + c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6 + d = chunk & 63 // 63 = 2^6 - 1 + + // Convert the raw binary segments to the appropriate ASCII encoding + base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d] + } + + // Deal with the remaining bytes and padding + if (byteRemainder == 1) { + chunk = bytes[mainLength] + + a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2 + + // Set the 4 least significant bits to zero + b = (chunk & 3) << 4 // 3 = 2^2 - 1 + + base64 += encodings[a] + encodings[b] + '==' + } else if (byteRemainder == 2) { + chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1] + + a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10 + b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4 + + // Set the 2 least significant bits to zero + c = (chunk & 15) << 2 // 15 = 2^4 - 1 + + base64 += encodings[a] + encodings[b] + encodings[c] + '=' + } + + return base64 +} diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/data-dir.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/data-dir.js new file mode 100644 index 0000000000..6e99404148 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/data-dir.js @@ -0,0 +1,136 @@ +/* + * iOS Data Protection + * + * getDataProtectionKeysForAllPaths() - List iOS file data protection classes (NSFileProtectionKey) of an app + * + */ +function listDirectoryContentsAtPath(path) { + var fileManager = ObjC.classes.NSFileManager.defaultManager(); + var enumerator = fileManager.enumeratorAtPath_(path); + var file; + var paths = []; + + while ((file = enumerator.nextObject()) !== null) { + paths.push(path + '/' + file); + } + + return paths; +} + +function listHomeDirectoryContents() { + var homePath = ObjC.classes.NSProcessInfo.processInfo().environment().objectForKey_("HOME").toString(); + var paths = listDirectoryContentsAtPath(homePath); + return paths; +} + +function getDataProtectionKeyForPath(path) { + var fileManager = ObjC.classes.NSFileManager.defaultManager(); + var urlPath = ObjC.classes.NSURL.fileURLWithPath_(path); + var fileProtectionKey = ObjC.Object(ptr(fileManager.attributesOfItemAtPath_error_(urlPath.path(), NULL))); + var protString = fileProtectionKey.valueForKey_("NSFileProtectionKey") + if (protString) + return protString.UTF8String(); + else{ + return ''; + } +} + +function getDataProtectionKeysForAllPaths() { + var fileManager = ObjC.classes.NSFileManager.defaultManager(); + var dict = []; + var paths = listHomeDirectoryContents(); + + var isDir = Memory.alloc(Process.pointerSize); + Memory.writePointer(isDir, NULL); + + for (var i = 0; i < paths.length; i++) { + + fileManager.fileExistsAtPath_isDirectory_(paths[i], isDir); + + if (Memory.readPointer(isDir) == 0) { + dict.push({ + path: paths[i], + fileProtectionKey: getDataProtectionKeyForPath(paths[i]) + }); + } + } + return dict; +} + +send('Dumping Application Directory file information'); +try { + send(JSON.stringify({'[MBSFDUMP] datadir': getDataProtectionKeysForAllPaths()})); +} catch(err) {} + +// /******************************************************************************** +// * Name: Dump iOS Data Protection Keys +// * OS: iOS +// * Author: @ay-kay +// * Source: https://codeshare.frida.re/@ay-kay/ios-dataprotection/ +// * Info: List iOS file data protection classes (NSFileProtectionKey) of an app +// *********************************************************************************/ + +// function listDirectoryContentsAtPath(path) { +// var fileManager = ObjC.classes.NSFileManager.defaultManager(); +// var enumerator = fileManager.enumeratorAtPath_(path); +// var file; +// var paths = []; + +// while ((file = enumerator.nextObject()) !== null) { +// paths.push(path + '/' + file); +// } + +// return paths; +// } + +// function listHomeDirectoryContents() { +// var homePath = ObjC.classes.NSProcessInfo.processInfo().environment().objectForKey_("HOME").toString(); +// var paths = listDirectoryContentsAtPath(homePath); +// return paths; +// } + +// function getDataProtectionKeyForPath(path) { +// var fileManager = ObjC.classes.NSFileManager.defaultManager(); +// var urlPath = ObjC.classes.NSURL.fileURLWithPath_(path); +// var attributeDict = dictFromNSDictionary(fileManager.attributesOfItemAtPath_error_(urlPath.path(), NULL)); +// return attributeDict.NSFileProtectionKey; +// } + +// // helper function available at https://codeshare.frida.re/@dki/ios-app-info/ +// function dictFromNSDictionary(nsDict) { +// var jsDict = {}; +// var keys = nsDict.allKeys(); +// var count = keys.count(); + +// for (var i = 0; i < count; i++) { +// var key = keys.objectAtIndex_(i); +// var value = nsDict.objectForKey_(key); +// jsDict[key.toString()] = value.toString(); +// } + +// return jsDict; +// } + +// function getDataProtectionKeysForAllPaths(){ +// var fileManager = ObjC.classes.NSFileManager.defaultManager(); +// var dict = []; +// var paths = listHomeDirectoryContents(); + +// var isDir = Memory.alloc(Process.pointerSize); +// Memory.writePointer(isDir, NULL); + +// for (var i = 0; i < paths.length; i++) { +// fileManager.fileExistsAtPath_isDirectory_(paths[i], isDir); + +// if (Memory.readPointer(isDir) == 0) { +// dict.push({ +// path: paths[i], +// fileProtectionKey: getDataProtectionKeyForPath(paths[i]) +// }); +// } +// } +// return dict; +// } + +// send('Getting files in app data directory'); +// send(JSON.stringify({'[MBSFDUMP] datadir': getDataProtectionKeysForAllPaths()})); diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/file-access.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/file-access.js new file mode 100644 index 0000000000..497e9d8faf --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/file-access.js @@ -0,0 +1,13 @@ +// From: https://node-security.com/posts/frida-for-ios/ +function traceFileAccess(){ + send('Tracing File Access Calls'); + Interceptor.attach(ObjC.classes.NSFileManager['- fileExistsAtPath:'].implementation, { + onEnter: function (args) { + var filename = ObjC.Object(args[2]).toString(); + send(JSON.stringify({'[MBSFDUMP] filename': filename})); + } + }); +} +try { + traceFileAccess(); +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/json.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/json.js new file mode 100644 index 0000000000..91d92646f3 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/json.js @@ -0,0 +1,15 @@ +function traceJSON(){ + send('Tracing JSON Data'); + var jsonHook = ObjC.classes.NSJSONSerialization["+ JSONObjectWithData:options:error:"]; + Interceptor.attach(jsonHook.implementation, + { + onEnter: function(args) { + var jsonData = ObjC.Object(args[2]); + var jsonStr = Memory.readUtf8String(jsonData.bytes(), jsonData.length()); + send(JSON.stringify({'[MBSFDUMP] json': jsonStr})); + } + }); +} +try { + traceJSON(); +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/keychain.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/keychain.js new file mode 100644 index 0000000000..76d4f00475 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/keychain.js @@ -0,0 +1,312 @@ +// From: https://github.com/sensepost/objection/blob/f8e78d8a29574c6dadd2b953a63207b45a19b1cf/objection/hooks/ios/keychain/dump.js +function dumpKeyChain(){ + var NSMutableDictionary = ObjC.classes.NSMutableDictionary; + var NSString = ObjC.classes.NSString; + + // Ref: http://nshipster.com/bool/ + var kCFBooleanTrue = ObjC.classes.__NSCFBoolean.numberWithBool_(true); + var SecItemCopyMatching = new NativeFunction( + ptr(Module.findExportByName('Security', 'SecItemCopyMatching')), 'pointer', ['pointer', 'pointer']); + var SecAccessControlGetConstraints = new NativeFunction( + ptr(Module.findExportByName('Security', 'SecAccessControlGetConstraints')), + 'pointer', ['pointer']); + + // constants + var kSecReturnAttributes = 'r_Attributes', + kSecReturnData = 'r_Data', + kSecReturnRef = 'r_Ref', + kSecMatchLimit = 'm_Limit', + kSecMatchLimitAll = 'm_LimitAll', + kSecClass = 'class', + kSecClassKey = 'keys', + kSecClassIdentity = 'idnt', + kSecClassCertificate = 'cert', + kSecClassGenericPassword = 'genp', + kSecClassInternetPassword = 'inet', + kSecAttrService = 'svce', + kSecAttrAccount = 'acct', + kSecAttrAccessGroup = 'agrp', + kSecAttrLabel = 'labl', + kSecAttrCreationDate = 'cdat', + kSecAttrAccessControl = 'accc', + kSecAttrGeneric = 'gena', + kSecAttrSynchronizable = 'sync', + kSecAttrModificationDate = 'mdat', + kSecAttrServer = 'srvr', + kSecAttrDescription = 'desc', + kSecAttrComment = 'icmt', + kSecAttrCreator = 'crtr', + kSecAttrType = 'type', + kSecAttrScriptCode = 'scrp', + kSecAttrAlias = 'alis', + kSecAttrIsInvisible = 'invi', + kSecAttrIsNegative = 'nega', + kSecAttrHasCustomIcon = 'cusi', + kSecProtectedDataItemAttr = 'prot', + kSecAttrAccessible = 'pdmn', + kSecAttrAccessibleWhenUnlocked = 'ak', + kSecAttrAccessibleAfterFirstUnlock = 'ck', + kSecAttrAccessibleAlways = 'dk', + kSecAttrAccessibleWhenUnlockedThisDeviceOnly = 'aku', + kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly = 'akpu', + kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly = 'cku', + kSecAttrAccessibleAlwaysThisDeviceOnly = 'dku', + kSecValueData = 'v_Data'; + + // dict for reverse constants lookups + var kSecConstantReverse = { + 'r_Attributes': 'kSecReturnAttributes', + 'r_Data': 'kSecReturnData', + 'r_Ref': 'kSecReturnRef', + 'm_Limit': 'kSecMatchLimit', + 'm_LimitAll': 'kSecMatchLimitAll', + 'class': 'kSecClass', + 'keys': 'kSecClassKey', + 'idnt': 'kSecClassIdentity', + 'cert': 'kSecClassCertificate', + 'genp': 'kSecClassGenericPassword', + 'inet': 'kSecClassInternetPassword', + 'svce': 'kSecAttrService', + 'acct': 'kSecAttrAccount', + 'agrp': 'kSecAttrAccessGroup', + 'labl': 'kSecAttrLabel', + 'srvr': 'kSecAttrServer', + 'cdat': 'kSecAttrCreationDate', + 'accc': 'kSecAttrAccessControl', + 'gena': 'kSecAttrGeneric', + 'sync': 'kSecAttrSynchronizable', + 'mdat': 'kSecAttrModificationDate', + 'desc': 'kSecAttrDescription', + 'icmt': 'kSecAttrComment', + 'crtr': 'kSecAttrCreator', + 'type': 'kSecAttrType', + 'scrp': 'kSecAttrScriptCode', + 'alis': 'kSecAttrAlias', + 'invi': 'kSecAttrIsInvisible', + 'nega': 'kSecAttrIsNegative', + 'cusi': 'kSecAttrHasCustomIcon', + 'prot': 'kSecProtectedDataItemAttr', + 'pdmn': 'kSecAttrAccessible', + 'ak': 'kSecAttrAccessibleWhenUnlocked', + 'ck': 'kSecAttrAccessibleAfterFirstUnlock', + 'dk': 'kSecAttrAccessibleAlways', + 'aku': 'kSecAttrAccessibleWhenUnlockedThisDeviceOnly', + 'akpu': 'kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly', + 'cku': 'kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly', + 'dku': 'kSecAttrAccessibleAlwaysThisDeviceOnly', + 'v_Data': 'kSecValueData', + }; + + // the base query dictionary to use for the keychain lookups + var search_dictionary = NSMutableDictionary.alloc().init(); + search_dictionary.setObject_forKey_(kCFBooleanTrue, kSecReturnAttributes); + search_dictionary.setObject_forKey_(kCFBooleanTrue, kSecReturnData); + search_dictionary.setObject_forKey_(kCFBooleanTrue, kSecReturnRef); + search_dictionary.setObject_forKey_(kSecMatchLimitAll, kSecMatchLimit); + + // keychain item times to query for + var item_classes = [ + kSecClassKey, + kSecClassIdentity, + kSecClassCertificate, + kSecClassGenericPassword, + kSecClassInternetPassword + ]; + + // get the string representation of some data + // ref: https://www.frida.re/docs/examples/ios/ + function odas(raw_data) { + + // "objective-c data as string" + + // // TODO: check if this is something we need NSKeyedUnarchiver for + // if (raw_data.toString().toLowerCase() + // .indexOf('62706c69 73743030 d4010203 04050609 0a582476 65727369 6f6e5824 6f626a65 63747359 24617263 68697665 72542474')) { + + // var new_value = NSKeyedUnarchiver.unarchiveObjectWithData_(raw_data); + // console.log(new_value); + // console.log(new_value.$ownMethods); + // } + + // try and get a string representation of the data + try { + + var data_instance = new ObjC.Object(raw_data); + return Memory.readUtf8String(data_instance.bytes(), data_instance.length()); + + } catch (_) { + + try { + + return raw_data.toString(); + + } catch (_) { + + return ''; + } + } + } + + // Decode the access control attributes on a keychain + // entry into a human readable string. Getting an idea of what the + // constriants actually are is done using an undocumented method, + // SecAccessControlGetConstraints. + function decode_acl(entry) { + + // No access control? Move along. + if (!entry.containsKey_(kSecAttrAccessControl)) { + return ''; + } + + var access_controls = ObjC.Object( + SecAccessControlGetConstraints(entry.objectForKey_(kSecAttrAccessControl))); + + // Ensure we were able to get the SecAccessControlRef + if (access_controls.handle == 0x00) { + return ''; + } + + var flags = []; + var access_control_enumerator = access_controls.keyEnumerator(); + var access_control_item_key; + + while ((access_control_item_key = access_control_enumerator.nextObject()) !== null) { + + var access_control_item = access_controls.objectForKey_(access_control_item_key); + + switch (odas(access_control_item_key)) { + + // Defaults? + case 'dacl': + break; + + case 'osgn': + flags.push('PrivateKeyUsage'); + + case 'od': + var constraints = access_control_item; + var constraint_enumerator = constraints.keyEnumerator(); + var constraint_item_key; + + while ((constraint_item_key = constraint_enumerator.nextObject()) !== null) { + + switch (odas(constraint_item_key)) { + case 'cpo': + flags.push('kSecAccessControlUserPresence'); + break; + + case 'cup': + flags.push('kSecAccessControlDevicePasscode'); + break; + + case 'pkofn': + constraints.objectForKey_('pkofn') == 1 ? + flags.push('Or') : + flags.push('And'); + break; + + case 'cbio': + constraints.objectForKey_('cbio').count() == 1 ? + flags.push('kSecAccessControlTouchIDAny') : + flags.push('kSecAccessControlTouchIDCurrentSet'); + break; + + default: + break; + } + } + + break; + + case 'prp': + flags.push('ApplicationPassword'); + break; + + default: + break; + } + } + + return flags.join(' '); + } + + // helper to lookup the constant name of a constant value + function get_constant_for_value(v) { + + for (var k in kSecConstantReverse) { + if (k == v) { + return kSecConstantReverse[v]; + } + } + + return v; + } + + // a list of keychain items that will return + var keychain_items = []; + + for (var item_class_index in item_classes) { + + var item_class = item_classes[item_class_index]; + + // set the class-type we are querying for now + search_dictionary.setObject_forKey_(item_class, kSecClass); + + // get a pointer to write results to. no type? guess that goes as id* then + var results_pointer = Memory.alloc(Process.pointerSize); + + // get the keychain items + var copy_results = SecItemCopyMatching(search_dictionary, results_pointer); + + // if we have no results, move to the next + if (copy_results != 0x00) { + continue; + } + + // read the resultant dict of the lookup from memory + var search_results = new ObjC.Object(Memory.readPointer(results_pointer)); + + // if there are search results, loop them each and populate the return + // array with the data we got + if (search_results.count() > 0) { + + for (var i = 0; i < search_results.count(); i++) { + + // the *actual* keychain item is here! + var search_result = search_results.objectAtIndex_(i); + + var keychain_entry = { + 'item_class': get_constant_for_value(item_class), + 'create_date': odas(search_result.objectForKey_(kSecAttrCreationDate)), + 'modification_date': odas(search_result.objectForKey_(kSecAttrModificationDate)), + 'description': odas(search_result.objectForKey_(kSecAttrDescription)), + 'comment': odas(search_result.objectForKey_(kSecAttrComment)), + 'creator': odas(search_result.objectForKey_(kSecAttrCreator)), + 'type': odas(search_result.objectForKey_(kSecAttrType)), + 'script_code': odas(search_result.objectForKey_(kSecAttrScriptCode)), + 'alias': odas(search_result.objectForKey_(kSecAttrAlias)), + 'invisible': odas(search_result.objectForKey_(kSecAttrIsInvisible)), + 'negative': odas(search_result.objectForKey_(kSecAttrIsNegative)), + 'custom_icon': odas(search_result.objectForKey_(kSecAttrHasCustomIcon)), + 'protected': odas(search_result.objectForKey_(kSecProtectedDataItemAttr)), + 'access_control': decode_acl(search_result), + 'accessible_attribute': get_constant_for_value(odas(search_result.objectForKey_(kSecAttrAccessible))), + 'entitlement_group': odas(search_result.objectForKey_(kSecAttrAccessGroup)), + 'generic': odas(search_result.objectForKey_(kSecAttrGeneric)), + 'service': odas(search_result.objectForKey_(kSecAttrService)), + 'account': odas(search_result.objectForKey_(kSecAttrAccount)), + 'label': odas(search_result.objectForKey_(kSecAttrLabel)), + 'data': odas(search_result.objectForKey_(kSecValueData)), + }; + + keychain_items.push(keychain_entry); + } + } + } + send('Dumping Application Keychain') + send(JSON.stringify({'[MBSFDUMP] keychain': keychain_items})); + } + +try { + dumpKeyChain(); +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/network.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/network.js new file mode 100755 index 0000000000..9030278b6b --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/network.js @@ -0,0 +1,44 @@ +send('Tracing Network Calls'); +// NSURLSession +try { + var NSURLSession = ObjC.classes.NSURLSession["- dataTaskWithRequest:completionHandler:"]; + Interceptor.attach(NSURLSession.implementation, { + onEnter: function(args) { + send(JSON.stringify({'[MBSFDUMP] network': {'source': 'NSURLSession', 'url': ObjC.Object(args[2]).URL().absoluteString().toString()}})); + } + }); + +} +catch(error){} +// NSURLRequest +try { + var NSURLRequest = ObjC.classes.NSURLRequest["- initWithURL:"]; + Interceptor.attach(NSURLRequest.implementation, { + onEnter: function(args) { + send(JSON.stringify({'[MBSFDUMP] network': {'source': 'NSURLRequest', 'url': ObjC.Object(args[2]).toString()}})); + }, + }); + +} catch (error) {} +// LGSRWebSocket +try { + var SRWebSocket = ObjC.classes.SRWebSocket["- send:"]; + Interceptor.attach(SRWebSocket.implementation, { + onEnter: function(args) { + var socketURL = ObjC.Object(args[0]).url().absoluteString().toString(); + send(JSON.stringify({'[MBSFDUMP] network': {'source': 'SRWebSocket', 'url': socketURL}})); + }, + + }); + +} catch (error) {} +// Cordova +try { + var CDVInvokedUrlCommand = ObjC.classes.CDVInvokedUrlCommand["+ commandFromJson:"]; + Interceptor.attach(CDVInvokedUrlCommand.implementation, { + onEnter: function(args) { + send(JSON.stringify({'[MBSFDUMP] network': {'source': 'Cordova', 'url': ObjC.Object(args[2]).toString()}})); + }, + }); + +} catch (error) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nslog.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nslog.js new file mode 100644 index 0000000000..60ef1c5325 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nslog.js @@ -0,0 +1,33 @@ +/* Description: Intercept calls to Apple's NSLog logging function + * Mode: S+A + * Version: 1.0 + * Credit: https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +// Modified for MobSF +function NSlog(){ + send('Tracing NSLog Calls'); + Interceptor.attach(Module.findExportByName("Foundation", "NSLog"), { + onEnter: function(args) { + send(JSON.stringify({'[MBSFDUMP] nslog': 'NSLog -> ' + ObjC.Object(ptr(args[0])).toString() + ', ' + Memory.readCString(ptr(args[1]))})); + } + }); +} + +function NSLogv(){ + //As per the Apple documentation NSLog calls NSLogv in the background but for some reason it is not working. Still working on a fix. + Interceptor.attach(Module.findExportByName("Foundation", "NSLogv"), { + onEnter: function(args) { + send(JSON.stringify({'[MBSFDUMP] nslog': 'NSLogv -> ' + ObjC.Object(ptr(args[0])).toString()+ ', ' + Memory.readCString(ptr(args[1]))})); + } + }); +} + +try { + NSlog(); +} catch(err) {} +try { + NSLogv(); +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nsurlcredentialstorage.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nsurlcredentialstorage.js new file mode 100644 index 0000000000..9c8a1f16f9 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nsurlcredentialstorage.js @@ -0,0 +1,39 @@ +// Dumps contents of NSURLCredentialStorage for all protection spaces +// Based on https://github.com/sensepost/objection/blob/f8e78d8a29574c6dadd2b953a63207b45a19b1cf/objection/hooks/ios/keychain/dump.js#L3 +function dumpNSURLCredentialStorage () { + send('Dumping Credentials from NSURLCredentialStorage') + var data = []; + var credentialstorage = []; + var credentialsDict = ObjC.classes.NSURLCredentialStorage.sharedCredentialStorage().allCredentials(); + + if (credentialsDict.count() <= 0) { + return data; + } + + const protectionSpaceEnumerator = credentialsDict.keyEnumerator(); + let urlProtectionSpace; + + while ((urlProtectionSpace = protectionSpaceEnumerator.nextObject()) !== null) { + + const userNameEnumerator = credentialsDict.objectForKey_(urlProtectionSpace).keyEnumerator(); + let userName; + while ((userName = userNameEnumerator.nextObject()) !== null) { + + var creds = credentialsDict.objectForKey_(urlProtectionSpace).objectForKey_(userName); + credentialstorage.push({ + host: urlProtectionSpace.host().toString(), + authenticationMethod: urlProtectionSpace.authenticationMethod().toString(), + protocol: urlProtectionSpace.protocol().toString(), + port: urlProtectionSpace.port(), + user: creds.user().toString(), + password: creds.password().toString() + + }) + } + } + send(JSON.stringify({'[MBSFDUMP] credentialstorage': credentialstorage})); +} + +try { + dumpNSURLCredentialStorage(); +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nsuserdefaults.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nsuserdefaults.js new file mode 100644 index 0000000000..41f799db45 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/nsuserdefaults.js @@ -0,0 +1,32 @@ +/* Description: Show contents of NSUserDefaults + * Mode: S+A + * Version: 1.0 + * Credit: Objection (https://github.com/sensepost/objection/blob/master/objection/commands/ios/nsuserdefaults.py) & https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Credit: Objection (https://github.com/sensepost/objection/blob/master/objection/commands/ios/nsuserdefaults.py) +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security + +function convertNsDictionaryToJson(nsDict) { + let jsDict = {}; + let keys = nsDict.allKeys(); + let keyCount = keys.count(); + for (var i = 0; i < keyCount; i++) { + let key = keys.objectAtIndex_(i); + let value = new ObjC.Object(nsDict.objectForKey_(key)); + jsDict[key] = String(value); // convert everything to a JavaScript String representation + } + return jsDict; +} + +function ns_userdefaults() { + send("Dumping NSUserDefaults Data"); + var NSUserDefaults = ObjC.classes.NSUserDefaults; + var NSDictionary = NSUserDefaults.alloc().init().dictionaryRepresentation(); + send(JSON.stringify({'[MBSFDUMP] nsuserdefaults': convertNsDictionaryToJson(NSDictionary)})); +} + +try{ + ns_userdefaults(); +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/pasteboard.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/pasteboard.js new file mode 100644 index 0000000000..014fff3086 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/pasteboard.js @@ -0,0 +1,31 @@ +/* Description: Monitor usage of pasteboard. Useful to show lack of secure attribute on sensitive fields allowing data copying. + * Mode: S+A + * Version: 1.0 + * Credit: https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +function start_pasteboard_monitoring(interval_value) +{ + send("Tracing iOS Pasteboard Entries"); + var pasteboard = (ObjC.classes.UIPasteboard).generalPasteboard(); + var latest_word = ""; + setInterval(function(){ + try + { + var on_pasteboard = pasteboard.string().toString() + if(on_pasteboard != latest_word) + { + send(JSON.stringify({'[MBSFDUMP] pasteboard':on_pasteboard})); + latest_word = on_pasteboard; + } + } + catch(err){} + }, interval_value); + +} +//start_pasteboard_monitoring(INTERVAL_VALUE_HERE_MILLISECONDS) +try { + start_pasteboard_monitoring(2000); +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/sqlite.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/sqlite.js new file mode 100644 index 0000000000..3d7f37a616 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/sqlite.js @@ -0,0 +1,23 @@ +send("Tracing SQLite Queries"); + +function hookSql(func, position, pretext) { + var to_hook = Module.findExportByName('libsqlite3.dylib', func); + Interceptor.attach(to_hook, { + onEnter: function(args) { + send(JSON.stringify({'[MBSFDUMP] sql': pretext + args[position].readCString()})); + }, + onLeave: function(retval) {} + }); +} +try { + hookSql('sqlite3_open', 0, 'OPEN: '); +} catch(err) {} +try { + hookSql('sqlite3_prepare_v2', 1, 'PREPARED: '); +} catch(err) {} +try { + hookSql('sqlite3_bind_text', 2, 'BIND TEXT: '); +} catch(err) {} +try { + hookSql('sqlite3_bind_text16', 2, 'BIND TEXT: '); +} catch(err) {} diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/text-inputs.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/text-inputs.js new file mode 100644 index 0000000000..1424228a59 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/dump/text-inputs.js @@ -0,0 +1,16 @@ +// Based on https://codeshare.frida.re/@lichao890427/ios-utils/ +function dump_inputs() { + send("Tracing all Text inputs to the device"); + var UIApplication = ObjC.classes.UIApplication; + Interceptor.attach(UIApplication["- sendAction:to:from:forEvent:"].implementation, { + onEnter:function(args) { + var fromObj = ObjC.Object(args[4]); + try{ + send(JSON.stringify({'[MBSFDUMP] textinput':fromObj.text().toString()})); + }catch(e){} + } + }); +} +try { + dump_inputs(); +} catch(err) {} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/app-environment.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/app-environment.js new file mode 100644 index 0000000000..f37876c721 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/app-environment.js @@ -0,0 +1,22 @@ +function getPath(nspath){ + return ObjC.classes.NSFileManager.defaultManager().URLsForDirectory_inDomains_(nspath, 1).lastObject().path().toString(); +} + +String.prototype.rsplit = function(sep, maxsplit) { + var split = this.split(sep); + return maxsplit ? [ split.slice(0, -maxsplit).join(sep) ].concat(split.slice(-maxsplit)) : split; +} + +function app_env_info() { + send('App Executable Path: ' + ObjC.classes.NSBundle.mainBundle().executablePath().toString()); + var mainBundlePath = String(ObjC.classes.NSBundle.mainBundle()) + mainBundlePath = mainBundlePath.substring(0, mainBundlePath.indexOf(">")) + mainBundlePath = mainBundlePath.substring(mainBundlePath.indexOf("<") + 1) + send('App Bundle Path: ' + mainBundlePath); + var libPath = getPath(5) + send('App Container Path: ' + libPath.rsplit('Library', 1)[0]); + send('App Document Path: ' + getPath(9)); + send('App Library Path: ' + libPath); + send('App Cache Path: ' + getPath(13)); +} +app_env_info(); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-flutter-ssl.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-flutter-ssl.js new file mode 100644 index 0000000000..33275fbf19 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-flutter-ssl.js @@ -0,0 +1,43 @@ +/* Description: Flutter bypass ssl pinning + * Mode: S + * Version: 1.0 + * Credit: https://bhattsameer.github.io/2021/06/23/Intercepting-flutter-iOS-application.html + * Author: @noobpk + */ +var colors = { + "resetColor": "\x1b[0m", + "green": "\x1b[32m", + "yellow": "\x1b[33m", + "red": "\x1b[31m" +} + +function hook_ssl_verify_result(address) +{ + Interceptor.attach(address, { + onEnter: function(args) { + send("Disabling SSL validation") + }, + onLeave: function(retval) + { + retval.replace(0x1); + } + }); + } +function disablePinning() +{ + var pattern = "FF 03 05 D1 FC 6F 0F A9 F8 5F 10 A9 F6 57 11 A9 F4 4F 12 A9 FD 7B 13 A9 FD C3 04 91 08 0A 80 52" + Process.enumerateRangesSync('r-x').filter(function (m) + { + if (m.file) return m.file.path.indexOf('Flutter') > -1; + return false; + }).forEach(function (r) + { + Memory.scanSync(r.base, r.size, pattern).forEach(function (match) { + send('[+] ssl_verify_result found at: ' + match.address.toString()); + hook_ssl_verify_result(match.address); + send('[*] Started: Bypass Flutter SSL-Pinning'); + + }); + }); + } +setTimeout(disablePinning, 1000) diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios10.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios10.js new file mode 100644 index 0000000000..011f059bca --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios10.js @@ -0,0 +1,18 @@ +/************************************************************************ + * Name: SSL Pinning Bypass for iOS 10 + * OS: iOS + * Author: @dki + * Source: https://codeshare.frida.re/@dki/ios10-ssl-bypass/ +*************************************************************************/ + +var tls_helper_create_peer_trust = new NativeFunction( + Module.findExportByName(null, "tls_helper_create_peer_trust"), + 'int', ['pointer', 'bool', 'pointer'] + ); + +var errSecSuccess = 0; + +Interceptor.replace(tls_helper_create_peer_trust, new NativeCallback(function(hdsk, server, trustRef) { + return errSecSuccess; +}, 'int', ['pointer', 'bool', 'pointer'])); +send("SSL certificate validation bypass active"); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios11.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios11.js new file mode 100644 index 0000000000..d7169b9718 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios11.js @@ -0,0 +1,19 @@ +/************************************************************************ + * Name: SSL Pinning Bypass for iOS 11 + * OS: iOS + * Author: @dki + * Source: https://codeshare.frida.re/@dki/ios10-ssl-bypass/ +*************************************************************************/ + +/* OSStatus nw_tls_create_peer_trust(tls_handshake_t hdsk, bool server, SecTrustRef *trustRef); */ +var tls_helper_create_peer_trust = new NativeFunction( + Module.findExportByName(null, "nw_tls_create_peer_trust"), + 'int', ['pointer', 'bool', 'pointer'] + ); + +var errSecSuccess = 0; + +Interceptor.replace(tls_helper_create_peer_trust, new NativeCallback(function(hdsk, server, trustRef) { + return errSecSuccess; +}, 'int', ['pointer', 'bool', 'pointer'])); +send("SSL certificate validation bypass active"); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios12.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios12.js new file mode 100644 index 0000000000..92f03d8e9d --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios12.js @@ -0,0 +1,51 @@ +/************************************************************************ + * Name: SSL Pinning Bypass for iOS 12 + * OS: iOS + * Author: Github @machoreverser / twitter @macho_reverser + * Source: https://github.com/machoreverser/Frida-Scripts + * Info: + iOS 12 SSL Bypass based on blog post + https://nabla-c0d3.github.io/blog/2019/05/18/ssl-kill-switch-for-ios12/ +*************************************************************************/ + +// Variables +var SSL_VERIFY_NONE = 0; +var ssl_ctx_set_custom_verify; +var ssl_get_psk_identity; + +/* Create SSL_CTX_set_custom_verify NativeFunction +* Function signature https://github.com/google/boringssl/blob/7540cc2ec0a5c29306ed852483f833c61eddf133/include/openssl/ssl.h#L2294 +*/ +ssl_ctx_set_custom_verify = new NativeFunction( + Module.findExportByName("libboringssl.dylib", "SSL_CTX_set_custom_verify"), + 'void', ['pointer', 'int', 'pointer'] +); + +/* Create SSL_get_psk_identity NativeFunction +* Function signature https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_get_psk_identity +*/ +ssl_get_psk_identity = new NativeFunction( + Module.findExportByName("libboringssl.dylib", "SSL_get_psk_identity"), + 'pointer', ['pointer'] +); + +/** Custom callback passed to SSL_CTX_set_custom_verify */ +function custom_verify_callback_that_does_not_validate(ssl, out_alert){ + return SSL_VERIFY_NONE; +} + +/** Wrap callback in NativeCallback for frida */ +var ssl_verify_result_t = new NativeCallback(function (ssl, out_alert){ + custom_verify_callback_that_does_not_validate(ssl, out_alert); +},'int',['pointer','pointer']); + +Interceptor.replace(ssl_ctx_set_custom_verify, new NativeCallback(function(ssl, mode, callback) { + // |callback| performs the certificate verification. Replace this with our custom callback + ssl_ctx_set_custom_verify(ssl, mode, ssl_verify_result_t); +}, 'void', ['pointer', 'int', 'pointer'])); + +Interceptor.replace(ssl_get_psk_identity, new NativeCallback(function(ssl) { + return "notarealPSKidentity"; +}, 'pointer', ['pointer'])); + +send("[+] iOS 12 SSL Pinning Bypass - successfully loaded"); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios13.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios13.js new file mode 100755 index 0000000000..ec973e877e --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/bypass-ssl-ios13.js @@ -0,0 +1,48 @@ +/* Description: iOS 13 SSL Bypass based on https://codeshare.frida.re/@machoreverser/ios12-ssl-bypass/ and https://github.com/nabla-c0d3/ssl-kill-switch2 + * Author: @apps3c + */ + +try { + Module.ensureInitialized("libboringssl.dylib"); +} catch(err) { + send("libboringssl.dylib module not loaded. Trying to manually load it.") + Module.load("libboringssl.dylib"); +} + +var SSL_VERIFY_NONE = 0; +var ssl_set_custom_verify; +var ssl_get_psk_identity; + +ssl_set_custom_verify = new NativeFunction( + Module.findExportByName("libboringssl.dylib", "SSL_set_custom_verify"), + 'void', ['pointer', 'int', 'pointer'] +); + +/* Create SSL_get_psk_identity NativeFunction +* Function signature https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_get_psk_identity +*/ +ssl_get_psk_identity = new NativeFunction( + Module.findExportByName("libboringssl.dylib", "SSL_get_psk_identity"), + 'pointer', ['pointer'] +); + +/** Custom callback passed to SSL_CTX_set_custom_verify */ +function custom_verify_callback_that_does_not_validate(ssl, out_alert){ + return SSL_VERIFY_NONE; +} + +/** Wrap callback in NativeCallback for frida */ +var ssl_verify_result_t = new NativeCallback(function (ssl, out_alert){ + custom_verify_callback_that_does_not_validate(ssl, out_alert); +},'int',['pointer','pointer']); + +Interceptor.replace(ssl_set_custom_verify, new NativeCallback(function(ssl, mode, callback) { + // |callback| performs the certificate verification. Replace this with our custom callback + ssl_set_custom_verify(ssl, mode, ssl_verify_result_t); +}, 'void', ['pointer', 'int', 'pointer'])); + +Interceptor.replace(ssl_get_psk_identity, new NativeCallback(function(ssl) { + return "notarealPSKidentity"; +}, 'pointer', ['pointer'])); + +send("[+] Bypass successfully loaded "); diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/check-keyboard-cache.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/check-keyboard-cache.js new file mode 100644 index 0000000000..2bfe3dc948 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/check-keyboard-cache.js @@ -0,0 +1,38 @@ +/* iOS Keyboard Cache + * + * Author: https://codeshare.frida.re/@ay-kay/ios-keyboard-cache/ + * iterateInputTraits() - Iterate over all UITextView, UITextField (including UISearchBar) elements in the current view and check if keyboard caching is disabled on these text inputs + * +*/ + +function resolveAutocorrectionType(typeNr) { + switch (parseInt(typeNr, 10)) { + case 1: + return "UITextAutocorrectionTypeNo" + case 2: + return "UITextAutocorrectionTypeYes" + default: + return "UITextAutocorrectionTypeDefault" + } +} + +function iterateInputTraits() { + var inputTraits = [ObjC.classes.UITextView, ObjC.classes.UITextField]; + inputTraits.forEach(function(inputTrait) { + ObjC.choose(inputTrait, { + onMatch: function(ui) { + send("-".repeat(100)); + send(ui.toString()); + send("is Editable: " + ui.isEditable()); + send("secureTextEntry: " + ui.isSecureTextEntry()); + send("autocorrectionType: " + ui.autocorrectionType() + " (" + resolveAutocorrectionType(ui.autocorrectionType()) + ")") + }, + onComplete: function() { + send("-".repeat(100)); + send("Finished searching for " + inputTrait.toString() + " elements."); + } + }); + }); +} + +iterateInputTraits(); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/check-keyboard-thridparty.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/check-keyboard-thridparty.js new file mode 100644 index 0000000000..35a00204a8 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/check-keyboard-thridparty.js @@ -0,0 +1,31 @@ + // Use send() for logging +/******************************************************************************** + * Name: iOS Custom Keyboard Support Check + * OS: iOS + * Author: @ay-kay + * Source: https://codeshare.frida.re/@ay-kay/ios-custom-keyboard-support/ + *********************************************************************************/ + +var UIApplication = ObjC.classes.UIApplication.sharedApplication(); +var shouldAllowKeyboardExtension = true; +var isDelegateImplemented = false; +try { + shouldAllowKeyboardExtension = UIApplication.delegate().application_shouldAllowExtensionPointIdentifier_(UIApplication, "com.apple.keyboard-service"); + isDelegateImplemented = true; + send("App delegate implements application:shouldAllowExtensionPointIdentifier:"); +} catch (e) { + if (e instanceof TypeError) { + send("App delegate has no application:shouldAllowExtensionPointIdentifier:, default behaviour applies:"); + } +} + +if (shouldAllowKeyboardExtension) { + send("-> Third-party keyboards are allowed.") +} else { + send("-> Third-party keyboards are NOT allowed.") +} + +if (shouldAllowKeyboardExtension && isDelegateImplemented) { + send("Note: App delegate is implemented but is configured to allow third-party keyboards."); + send("Review the implementation to check if third-party keyboard support is configurable."); +} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/device-environment.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/device-environment.js new file mode 100644 index 0000000000..3bc2a96ac6 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/device-environment.js @@ -0,0 +1,11 @@ +// Based on https://github.com/iddoeldor/frida-snippets#device-properties + +var UIDevice = ObjC.classes.UIDevice.currentDevice(); +UIDevice.$ownMethods + .filter(function(method) { + return method.indexOf(':') == -1 /* filter out methods with parameters */ + && method.indexOf('+') == -1 /* filter out public methods */ + }) + .forEach(function(method) { + send(method + ' : ' + UIDevice[method]()) + }) \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/dump-ios-url-scheme.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/dump-ios-url-scheme.js new file mode 100644 index 0000000000..b61d56590e --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/dump-ios-url-scheme.js @@ -0,0 +1,26 @@ +/* Description: Dump iOS url scheme when "openURL" is called + * Mode: S + * Version: 1.0 + * Credit: https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +// Get a reference to the openURL selector +var openURL = ObjC.classes.UIApplication["- openURL:"]; + +// Intercept the method +Interceptor.attach(openURL.implementation, { + onEnter: function(args) { + // As this is an ObjectiveC method, the arguments are as follows: + // 0. 'self' + // 1. The selector (openURL:) + // 2. The first argument to the openURL selector + var myNSURL = new ObjC.Object(args[2]); + // Convert it to a JS string + var myJSURL = myNSURL.absoluteString().toString(); + // Log it + send("Launching URL: " + myJSURL); + //send(myJSURL); + } +}); diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/find-all-classes-methods.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/find-all-classes-methods.js new file mode 100644 index 0000000000..332c15f745 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/find-all-classes-methods.js @@ -0,0 +1,38 @@ +/* Description: Dump all methods inside all classes + * Mode: S+A + * Version: 1.0 + * Credit: https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +function run_show_classes_methods_of_app() +{ + send("Enumerating Classes and Methods") + for (var className in ObjC.classes) + { + if (ObjC.classes.hasOwnProperty(className)) + { + send("[AUXILIARY] Class: " + className); + //var methods = ObjC.classes[className].$methods; + var methods = ObjC.classes[className].$ownMethods; + for (var i = 0; i < methods.length; i++) + { + send("[AUXILIARY] \t Method: " + methods[i]); + try + { + send("[AUXILIARY] \t\tArguments Type: " + ObjC.classes[className][methods[i]].argumentTypes); + send("[AUXILIARY] \t\tReturn Type: " + ObjC.classes[className][methods[i]].returnType); + } + catch(err) {} + } + } + } + send("Completed Enumerating Methods of All Classes") +} + +function show_classes_methods_of_app() +{ + setImmediate(run_show_classes_methods_of_app) +} +show_classes_methods_of_app() \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/find-all-classes.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/find-all-classes.js new file mode 100644 index 0000000000..7c4dfb9a99 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/find-all-classes.js @@ -0,0 +1,30 @@ +/* Description: Dump all classes used by the app + * Mode: S+A + * Version: 1.0 + * Credit: https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +function run_show_classes_of_app() +{ + send("Enumerating Classes") + var count = 0 + for (var className in ObjC.classes) + { + if (ObjC.classes.hasOwnProperty(className)) + { + send("[AUXILIARY] " + className); + count = count + 1 + } + } + send("[AUXILIARY] \n Classes found: " + count); + send("Completed Enumerating Classes") +} + +function show_classes_of_app() +{ + setImmediate(run_show_classes_of_app) +} + +show_classes_of_app() \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/flutter-trace-function.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/flutter-trace-function.js new file mode 100755 index 0000000000..64d1ac067b --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/flutter-trace-function.js @@ -0,0 +1,244 @@ +/* Description: iOS flutter trace function + * Mode: S+A + * Version: 1.0 + * Credit: https://gist.github.com/AICDEV/630feed7583561ec9f9421976e836f90 + * Author: @AICDEV + */ +/** + * run the script to a running app: frida -U "appName" -l flutter_ios.js --no-pause + * start app direct with the script: frida -Uf bundleIdentifier -l flutter_ios.js --no-pause + */ +// ############################################# +// HELPER SECTION START +var colors = { + "resetColor": "\x1b[0m", + "green": "\x1b[32m", + "yellow": "\x1b[33m", + "red": "\x1b[31m" +} + +function logSection(message) { + send("#################################################"); + send(message); + send("#################################################"); +} + +function logMessage(message) { + send("---> " + message); +} + +function logError(message) { + send("---> ERRROR: " + message); +} + +function getAllClasses() { + var classes = []; + for (var cl in ObjC.classes) { + classes.push(cl); + } + return classes; +} + +function filterFlutterClass() { + var matchClasses = []; + var classes = getAllClasses(); + + for (var i = 0; i < classes.length; i++) { + if (classes[i].toString().toLowerCase().includes('flu')) { + matchClasses.push(classes[i]); + } + } + + return matchClasses; +} + + +function getAllMethodsFromClass(cl) { + return ObjC.classes[cl].$ownMethods; +} + +function listAllMethodsFromClasses(classes) { + for (var i = 0; i < classes.length; i++) { + var methods = getAllMethodsFromClass(classes[i]); + for (var a = 0; a < methods.length; a++) { + logMessage("class: " + classes[i] + " --> method: " + methods[a]); + } + } +} + +function blindCallDetection(classes) { + for (var i = 0; i < classes.length; i++) { + var methods = getAllMethodsFromClass(classes[i]); + for (var a = 0; a < methods.length; a++) { + var hook = ObjC.classes[classes[i]][methods[a]]; + try { + Interceptor.attach(hook.implementation, { + onEnter: function (args) { + this.className = ObjC.Object(args[0]).toString(); + this.methodName = ObjC.selectorAsString(args[1]); + logMessage("detect call to: " + this.className + ":" + this.methodName); + } + }) + } catch (err) { + logError("error in trace blindCallDetection"); + logError(err); + } + } + } +} + +function singleBlindTracer(className, methodName) { + try { + var hook = ObjC.classes[className][methodName]; + Interceptor.attach(hook.implementation, { + onEnter: function (args) { + this.className = ObjC.Object(args[0]).toString(); + this.methodName = ObjC.selectorAsString(args[1]); + logMessage("detect call to: " + this.className + ":" + this.methodName); + } + }) + } catch (err) { + logError("error in trace singleBlindTracer"); + logError(err); + } +} + +// ############################################# +//HELPER SECTION END +// ############################################# +// BEGIN FLUTTER SECTION +// ############################################# +function listAllFlutterClassesAndMethods() { + var flutterClasses = filterFlutterClass(); + for (var i = 0; i < flutterClasses.length; i++) { + var methods = getAllMethodsFromClass(flutterClasses[i]); + for (var a = 0; a < methods.length; a++) { + logMessage("class: " + flutterClasses[i] + " --> method: " + methods[a]); + } + } +} +// https://api.flutter.dev/objcdoc/Classes/FlutterMethodCall.html#/c:objc(cs)FlutterMethodCall(cm)methodCallWithMethodName:arguments: +function traceFlutterMethodCall() { + var className = "FlutterMethodCall" + var methodName = "+ methodCallWithMethodName:arguments:" + var hook = ObjC.classes[className][methodName]; + + try { + Interceptor.attach(hook.implementation, { + onEnter: function (args) { + + this.className = ObjC.Object(args[0]).toString(); + this.methodName = ObjC.selectorAsString(args[1]); + logMessage(this.className + ":" + this.methodName); + logMessage("method: " + ObjC.Object(args[2]).toString()); + logMessage("args: " + ObjC.Object(args[3]).toString()); + } + }) + } catch (err) { + logError("error in trace FlutterMethodCall"); + logError(err); + } +} + +// https://api.flutter.dev/objcdoc/Classes/FlutterMethodChannel.html#/c:objc(cs)FlutterMethodChannel(im)invokeMethod:arguments: +function traceFlutterMethodChannel() { + var className = "FlutterMethodChannel" + var methodName = "- setMethodCallHandler:" + var hook = ObjC.classes[className][methodName]; + + try { + Interceptor.attach(hook.implementation, { + onEnter: function (args) { + this.className = ObjC.Object(args[0]).toString(); + this.methodName = ObjC.selectorAsString(args[1]); + logMessage(this.className + ":" + this.methodName); + logMessage("method: " + ObjC.Object(args[2]).toString()); + } + }) + } catch (err) { + logError("error in trace FlutterMethodChannel"); + logError(err); + } +} + +// enum function from defined classes +function inspectInteresingFlutterClasses(classes) { + logSection("START BLIND TRACE FOR SPECIFIED METHODS"); + for (var i = 0; i < classes.length; i++) { + logMessage("inspect all methods from: " + classes[i]); + var methods = getAllMethodsFromClass(classes[i]); + for (var a = 0; a < methods.length; a++) { + logMessage("method --> " + methods[a]); + blindTraceWithPayload(classes[i], methods[a]); + } + } +} + +function blindTraceWithPayload(className, methodName) { + try { + var hook = ObjC.classes[className][methodName]; + Interceptor.attach(hook.implementation, { + onEnter: function (args) { + this.className = ObjC.Object(args[0]).toString(); + this.methodName = ObjC.selectorAsString(args[1]); + logMessage(this.className + ":" + this.methodName); + logMessage("payload: " + ObjC.Object(args[2]).toString()); + }, + }) + } catch (err) { + logError("error in blind trace"); + logError(err); + } +} + +// ############################################# +// END FLUTTER SECTION +// ############################################# +/** + * check if a method in the specified class get called + */ +logSection("BLIND TRACE NATIVE FUNCTION"); +var blindCallClasses = [ + "FlutterStringCodec", +] +blindCallDetection(blindCallClasses); + +/** + * List found flutter classes and there methods + */ +logSection("SEARCH ALL FLUTTER CLASSES AND METHODS"); +listAllFlutterClassesAndMethods(); + + +/** + * define custom class for further investigation. be careful: it calls blindTraceWithPayload logMessage("payload: " + ObjC.Object(args[2]).toString()); + * If you are not sure if the arg[2] is present read the function docs or do some try catch + */ +var interestingFlutterClasses = [ + //https://api.flutter.dev/objcdoc/Protocols/FlutterMessageCodec.html#/c:objc(pl)FlutterMessageCodec(im)encode: + "FlutterJSONMessageCodec", + //https://api.flutter.dev/objcdoc/Protocols/FlutterMethodCodec.html + "FlutterJSONMethodCodec", + "FlutterStandardReader", + //https://api.flutter.dev/objcdoc/Classes/FlutterEventChannel.html + "FlutterEventChannel", + //https://api.flutter.dev/objcdoc/Classes/FlutterViewController.html + //"FlutterViewController", + //https://api.flutter.dev/objcdoc/Classes/FlutterBasicMessageChannel.html + "FlutterBasicMessageChannel", +] + +inspectInteresingFlutterClasses(interestingFlutterClasses) + +/** + * trace implementation for + * https://api.flutter.dev/objcdoc/Classes/FlutterMethodCall.html + * https://api.flutter.dev/objcdoc/Classes/FlutterMethodChannel.html + */ +logSection("TRACING FLUTTER BEHAVIOUR"); +traceFlutterMethodCall(); +traceFlutterMethodChannel(); + + +logSection("SINGLE BLIND TRACING"); +singleBlindTracer("FlutterObservatoryPublisher","- url") diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/get-exports.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/get-exports.js new file mode 100644 index 0000000000..a6d91d4ceb --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/get-exports.js @@ -0,0 +1,7 @@ +// Get Modules and Exports +// Based on: https://github.com/iddoeldor/frida-snippets +var x = {}; +Process.enumerateModulesSync().forEach(function(m){ + x[m.name] = Module.enumerateExportsSync(m.name) +}); +console.log(JSON.stringify(x, null, ' ')) \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/get-modules.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/get-modules.js new file mode 100644 index 0000000000..3e3b982a45 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/get-modules.js @@ -0,0 +1,6 @@ +// Based on https://github.com/iddoeldor/frida-snippets#list-modules +Process.enumerateModulesSync() + .filter(function(m){ return m['path'].toLowerCase().indexOf('app') !=-1 ; }) + .forEach(function(m) { + send(JSON.stringify(m, null, ' ')); +}); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-get-memory-address-of-class.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-get-memory-address-of-class.js new file mode 100644 index 0000000000..07da2f3421 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-get-memory-address-of-class.js @@ -0,0 +1,7 @@ +// From: https://node-security.com/posts/frida-for-ios/ +let className = `FFJpegFrame`; +let methodName = `- setData:`; + +let address = ObjC.classes[className][methodName].implementation; + +send(`Address: ${address}`); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-memory-hex-dump.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-memory-hex-dump.js new file mode 100644 index 0000000000..0e3da0b762 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-memory-hex-dump.js @@ -0,0 +1,11 @@ +// From: https://node-security.com/posts/frida-for-ios/ +const className = `FFJpegFrame`; +const methodToHook = `- setData:`; +let address = ObjC.classes[className][methodToHook].implementation; + +let hexData = hexdump(address, { + offset: 0, + length: 64, +}); + +send(hexData); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-method-hooking.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-method-hooking.js new file mode 100644 index 0000000000..0db0205fa5 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-method-hooking.js @@ -0,0 +1,14 @@ +// From: https://node-security.com/posts/frida-for-ios/ +let className = `SCDiscoverFeedRanker`; +let methodName = `- addListener:`; + +let address = ObjC.classes[className][methodName].implementation; + +Interceptor.attach(address, { + onEnter: function(args) { + send(`Function Called`); + }, + onLeave: function(returnValue) { + send(`\nReturn Value: ${returnValue}`); + } +}); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-read-plist-file.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-read-plist-file.js new file mode 100644 index 0000000000..38482adb25 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-read-plist-file.js @@ -0,0 +1,16 @@ +/* Description: Show contents of a Plist file + * Mode: S+A + * Version: 1.0 + * Credit: https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +function read_plist_file(file_location) +{ + var dict = ObjC.classes.NSMutableDictionary + send("[*] Read Plist File: " + file_location) + send("[*] File Contents:") + send(dict.alloc().initWithContentsOfFile_(file_location).toString()) +} +read_plist_file("/path/to/file/filename.plist")//file location and path here \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-replace-exported-method.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-replace-exported-method.js new file mode 100644 index 0000000000..841aec4e54 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-replace-exported-method.js @@ -0,0 +1,24 @@ +/* Description: Replace a module's exported function + * Mode: S + * Version: 1.0 + * Credit: https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security + +//How to identify exports +//Get a list of all modules: Process.enumerateModules() +//Get a list of export for a module: Module.enumerateExports() + +//replace a function. In this example we are replacing ptrace +var ptracePtr = Module.findExportByName(null, "ptrace"); //null can be replaced with libsystem_kernel.dylib which exports ptrace +Interceptor.replace(ptracePtr, new NativeCallback(function () { + send("[*] Ptrace called and replaced") +}, "int", [])); + +//replace a function. In this example we are replacing sysctl +var sysctlPtr = Module.findExportByName(null, "__sysctl"); //null can be replaced with libsystem_kernel.dylib which exports sysctl +Interceptor.replace(sysctlPtr, new NativeCallback(function () { + send("[*] Sysctl called and replaced") +}, "int", [])); diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-show-modify-function-arguments.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-show-modify-function-arguments.js new file mode 100644 index 0000000000..672b984232 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-show-modify-function-arguments.js @@ -0,0 +1,30 @@ +/* Description: Show and modify arguments of a function inside a class + * Mode: S+A + * Version: 1.0 + * Credit: https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security + +function show_modify_function_args(className, funcName) +{ + var hook = ObjC.classes[className][funcName]; + Interceptor.attach(hook.implementation, { + onEnter: function(args) { + // args[0] is self + // args[1] is selector (SEL "sendMessageWithText:") + // args[2] holds the first function argument, an NSString + console.log("\n[*] Detected call to: " + className + " -> " + funcName); + console.log("\t[-] Argument Value: "+args[2]); + //your new argument value here + var newargval = ptr("0x0") + args[2] = newargval + console.log("\t[-] New Argument Value: " + args[2]) + } + }); +} + + +//Your class name and function name here +show_modify_function_args("className", "funcName") \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-show-modify-method-return-value.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-show-modify-method-return-value.js new file mode 100644 index 0000000000..6309a81a58 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-show-modify-method-return-value.js @@ -0,0 +1,31 @@ +/* Description: Show and modify return value of a particular method inside a class + * Mode: S+A + * Version: 1.0 + * Credit: https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +//Twitter: https://twitter.com/xploresec +//GitHub: https://github.com/interference-security +function show_modify_function_return_value(className_arg, funcName_arg) +{ + var className = className_arg; + var funcName = funcName_arg; + var hook = ObjC.classes[className][funcName]; + Interceptor.attach(hook.implementation, { + onLeave: function(retval) { + console.log("\n[*] Class Name: " + className); + console.log("[*] Method Name: " + funcName); + console.log("\t[-] Type of return value: " + typeof retval); + //console.log(retval.toString()); + console.log("\t[-] Return Value: " + retval); + //For modifying the return value + var newretval = ptr("0x0") //your new return value here + retval.replace(newretval) + console.log("\t[-] New Return Value: " + newretval) + } + }); +} + + +//YOUR_CLASS_NAME_HERE and YOUR_EXACT_FUNC_NAME_HERE +show_modify_function_return_value("YOUR_CLASS_NAME_HERE" ,"YOUR_EXACT_FUNC_NAME_HERE") \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-ssh-commands.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-ssh-commands.js new file mode 100644 index 0000000000..802809973c --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-ssh-commands.js @@ -0,0 +1,15 @@ +// From: https://github.com/iddoeldor/frida-snippets#log-ssh-commands +var NMSSHChannel = ObjC.classes.NMSSHChannel; +if (!NMSSHChannel){ + send('Class NMSSHChannel not found') + return; +} +Interceptor.attach(NMSSHChannel['- execute:error:timeout:'].implementation, { + onEnter: function(args) { + this.cmd = ObjC.Object(args[2]).toString(); + this.timeout = args[4]; + }, + onLeave: function(retv) { + send('CMD: ' + ObjC.Object(args[2]).toString() + 'Timeout: ' + args[4] + 'Ret: ' + retv); + } +}); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-view-and-modify-method-registers.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-view-and-modify-method-registers.js new file mode 100644 index 0000000000..0c4a6418cd --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-view-and-modify-method-registers.js @@ -0,0 +1,24 @@ +// From: https://node-security.com/posts/frida-for-ios/#view-and-modify-registers +let className = `SCDiscoverFeedRanker`; +let methodName = `- addListener:`; + +let address = ObjC.classes[className][methodName].implementation; + +Interceptor.attach(address, { + onEnter: function(args) { + // Print ALL Registers + send(JSON.stringify(this.context, null, 4), '\n'); + + // View Register Value + send(`Register (x14): ${this.context.x14}`); + send(`Register (x14): ${this.context.x14.toInt32()}\n`); + + // Update Register Value + this.context.x14 = 64; + this.context.x14 = 0x44; // Same as the previous line + + // View Register Value + send(`Register (x14): ${this.context.x14}`); + send(`Register (x14): ${this.context.x14.toInt32()}`); + }, +}); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-view-method-arguments.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-view-method-arguments.js new file mode 100644 index 0000000000..4c041af157 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/helper-view-method-arguments.js @@ -0,0 +1,13 @@ +// From: https://node-security.com/posts/frida-for-ios/ +let className = `SCDiscoverFeedRanker`; +let methodName = `- addListener:`; + +let address = ObjC.classes[className][methodName].implementation; + +Interceptor.attach(address, { + onEnter: function(args) { + send('arg[0]:', args[0]); + send('arg[1]:', args[1]); + send('arg[2]:', args[2]); + } +}); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-ios-alert.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-ios-alert.js new file mode 100644 index 0000000000..2dd52057c5 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-ios-alert.js @@ -0,0 +1,12 @@ +var UIAlertController = ObjC.classes.UIAlertController; +var UIAlertAction = ObjC.classes.UIAlertAction; +var UIApplication = ObjC.classes.UIApplication; +var handler = new ObjC.Block({ retType: 'void', argTypes: ['object'], implementation: function () {} }); + +ObjC.schedule(ObjC.mainQueue, function () { + var alert = UIAlertController.alertControllerWithTitle_message_preferredStyle_('Frida', 'Hello from Frida', 1); + var defaultAction = UIAlertAction.actionWithTitle_style_handler_('OK', 0, handler); + alert.addAction_(defaultAction); + // Instead of using `ObjC.choose()` and looking for UIViewController instances on the heap, we have direct access through UIApplication: + UIApplication.sharedApplication().keyWindow().rootViewController().presentViewController_animated_completion_(alert, true, NULL); +}) \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-ios-biometric-bypass.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-ios-biometric-bypass.js new file mode 100644 index 0000000000..369d3b415f --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-ios-biometric-bypass.js @@ -0,0 +1,22 @@ +/* Description: iOS Biometric Bypass + * Mode: S + * Version: 1.0 + * Credit: https://github.com/interference-security/frida-scripts/blob/master/iOS + * Author: @interference-security + */ +var func_name = "LAContext [- evaluatePolicy:localizedReason:reply:] method"; +send("\n[*] Hooking: " + func_name); +send("[*] Press CANCEL on biometric authentication prompt to bypass authentication"); +var hook = ObjC.classes.LAContext["- evaluatePolicy:localizedReason:reply:"]; +Interceptor.attach(hook.implementation, { + onEnter: function(args) { + send("[*] Detected call to method: " + func_name); + var block = new ObjC.Block(args[4]); + const callback = block.implementation; + block.implementation = function (error, value) { + send("[*] Changing return value to TRUE to bypass iOS biometric authentication"); + const result = callback(1, null); + return result; + }; + }, +}); diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-send-url-scheme.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-send-url-scheme.js new file mode 100644 index 0000000000..85e89f7de1 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-send-url-scheme.js @@ -0,0 +1,58 @@ +// Based on: https://github.com/ChiChou/grapefruit/blob/f4e9fd3e4411a04d8a8e90453acb8e9ea270c19a/agent/src/modules/url.ts +function openUrlScheme(urlStr) { + const app = ObjC.classes.UIApplication.sharedApplication() + // iOS 13 UISceneDelegate + if (ObjC.classes.UIScene) { + const scenes = app.connectedScenes().allObjects() + for (let i = 0; i < scenes.count(); i++) { + const scene = scenes.objectAtIndex_(i) + const delegate = scene.delegate() + if (!delegate) + continue; + ObjC.schedule(ObjC.mainQueue, () => { + // prevent UAF!! + const url = ObjC.classes.NSURL.URLWithString_(urlStr) + const opt = ObjC.classes.UISceneOpenURLOptions.new() + const ctx = ObjC.classes.UIOpenURLContext.new().initWithURL_options_( + url, + opt + ) + const imp = scene.delegate().scene_openURLContexts_.implementation + scene + .delegate() + .scene_openURLContexts_(scene, ObjC.classes.NSSet.setWithObject_(ctx)) + + send("Request sent to url handler:" + delegate + " scene:openURLContexts: @" +imp) + }) + return + } + } + + const candidates = [ + "application:handleOpenURL:", // iOS 2.0-9.0 + "application:openURL:sourceApplication:annotation:", // iOS 4.2–9.0 + "application:openURL:options:" // 9.0+ + ] + + const delegate = app.delegate() + + for (let sel of candidates) { + const method = delegate[sel] + if (typeof method === "function") { + ObjC.schedule(ObjC.mainQueue, () => { + const rest = [...new Array(method.length - 2)].map(e => NULL) + const url = ObjC.classes.NSURL.URLWithString_(urlStr) + delegate[sel](app, url, ...rest) + send("Request sent to url handler:" + delegate + " " + sel + " @" + method.implementation) + }) + return + } + } + + throw Error( + `delegate not found. Please file a bug (bundle id: ${ObjC.classes.NSBundle.mainBundle()})` + ) + } + +// Example: openUrlScheme('someapp://foobar'); +openUrlScheme(''); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-trace.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-trace.js new file mode 100644 index 0000000000..f6a2bf4bf3 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-trace.js @@ -0,0 +1,13 @@ +send("[*] Started: Dumping UI") +if (ObjC.available) +{ + var keyWin = ObjC.classes.UIWindow.keyWindow() + if (keyWin) + { + console.log(keyWin.recursiveDescription().toString()); + } +} +else +{ + send("Objective-C Runtime is not available!"); +} diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-view-trace.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-view-trace.js new file mode 100644 index 0000000000..e268ea3603 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/others/ui-view-trace.js @@ -0,0 +1,15 @@ +// From https://codeshare.frida.re/@lichao890427/ios-utils/ +function trace_view() { + var UIApplication = ObjC.classes.UIApplication; + Interceptor.attach(UIApplication["- sendAction:to:from:forEvent:"].implementation, { + onEnter:function(args) { + var action = Memory.readUtf8String(args[2]); + var toobj = ObjC.Object(args[3]); + var fromobj = ObjC.Object(args[4]); + var event = ObjC.Object(args[5]); + send('SendAction:' + action + ' to:' + toobj.toString() + + ' from:' + fromobj.toString() + ' forEvent:' + event.toString() + ']'); + } + }); +} +trace_view(); \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/rpc/get-app-container.js b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/rpc/get-app-container.js new file mode 100644 index 0000000000..7c8fb289b7 --- /dev/null +++ b/mobsf/DynamicAnalyzer/tools/frida_scripts/ios/rpc/get-app-container.js @@ -0,0 +1,11 @@ +getContainer: function () { + try{ + var libPath = ObjC.classes.NSFileManager.defaultManager().URLsForDirectory_inDomains_(5, 1).lastObject().path().toString(); + const sep = 'Library' + var split = libPath.split(sep); + var rsplit = 1 ? [ split.slice(0, -1).join(sep) ].concat(split.slice(-1)) : split; + return rsplit[0]; + } catch(err) { + return false; + } +} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/tools/onDevice/xposed/hooks.json b/mobsf/DynamicAnalyzer/tools/onDevice/xposed/hooks.json deleted file mode 100755 index 820d152ca7..0000000000 --- a/mobsf/DynamicAnalyzer/tools/onDevice/xposed/hooks.json +++ /dev/null @@ -1,389 +0,0 @@ -{ - "hookConfigs": [ - { - "class_name": "android.telephony.TelephonyManager", - "method": "getDeviceId", - "thisObject": false, - "type": "fingerprint" - }, - { - "class_name": "android.telephony.TelephonyManager", - "method": "getSubscriberId", - "thisObject": false, - "type": "fingerprint" - }, - { - "class_name": "android.telephony.TelephonyManager", - "method": "getLine1Number", - "thisObject": false, - "type": "fingerprint" - }, - { - "class_name": "android.telephony.TelephonyManager", - "method": "getNetworkOperator", - "thisObject": false, - "type": "fingerprint" - }, - { - "class_name": "android.telephony.TelephonyManager", - "method": "getNetworkOperatorName", - "thisObject": false, - "type": "fingerprint" - }, - { - "class_name": "android.telephony.TelephonyManager", - "method": "getSimOperatorName", - "thisObject": false, - "type": "fingerprint" - }, - { - "class_name": "android.net.wifi.WifiInfo", - "method": "getMacAddress", - "thisObject": false, - "type": "fingerprint" - }, - { - "class_name": "android.telephony.TelephonyManager", - "method": "getSimCountryIso", - "thisObject": false, - "type": "fingerprint" - }, - { - "class_name": "android.telephony.TelephonyManager", - "method": "getSimSerialNumber", - "thisObject": false, - "type": "fingerprint" - }, - { - "class_name": "android.telephony.TelephonyManager", - "method": "getNetworkCountryIso", - "thisObject": false, - "type": "fingerprint" - }, - { - "class_name": "android.telephony.TelephonyManager", - "method": "getDeviceSoftwareVersion", - "thisObject": false, - "type": "fingerprint" - }, - { - "class_name": "android.os.Debug", - "method": "isDebuggerConnected", - "thisObject": false, - "type": "fingerprint" - }, - { - "class_name": "android.app.SharedPreferencesImpl$EditorImpl", - "method": "putString", - "thisObject": false, - "type": "globals" - }, - { - "class_name": "android.app.SharedPreferencesImpl$EditorImpl", - "method": "putBoolean", - "thisObject": false, - "type": "globals" - }, - { - "class_name": "android.app.SharedPreferencesImpl$EditorImpl", - "method": "putInt", - "thisObject": false, - "type": "globals" - }, - { - "class_name": "android.app.SharedPreferencesImpl$EditorImpl", - "method": "putLong", - "thisObject": false, - "type": "globals" - }, - { - "class_name": "android.app.SharedPreferencesImpl$EditorImpl", - "method": "putFloat", - "thisObject": false, - "type": "globals" - }, - { - "class_name": "android.content.ContentValues", - "method": "put", - "thisObject": false, - "type": "globals" - }, - { - "class_name": "java.net.URL", - "method": "openConnection", - "thisObject": true, - "type": "network" - }, - { - "class_name": "org.apache.http.impl.client.AbstractHttpClient", - "method": "execute", - "thisObject": false, - "type": "network" - }, - { - "class_name": "android.app.ContextImpl", - "method": "registerReceiver", - "thisObject": false, - "type": "binder" - }, - { - "class_name": "android.app.ActivityThread", - "method": "handleReceiver", - "thisObject": false, - "type": "binder" - }, - { - "class_name": "android.app.Activity", - "method": "startActivity", - "thisObject": false, - "type": "binder" - }, - { - "class_name": "dalvik.system.BaseDexClassLoader", - "method": "findResource", - "thisObject": false, - "type": "dex" - }, - { - "class_name": "dalvik.system.BaseDexClassLoader", - "method": "findLibrary", - "thisObject": false, - "type": "dex" - }, - { - "class_name": "dalvik.system.DexFile", - "method": "loadDex", - "thisObject": false, - "type": "dex" - }, - { - "class_name": "dalvik.system.DexClassLoader", - "method": null, - "thisObject": false, - "type": "dex" - }, - { - "class_name": "dalvik.system.BaseDexClassLoader", - "method": "findResources", - "thisObject": false, - "type": "dex" - }, - { - "class_name": "dalvik.system.DexFile", - "method": "loadClass", - "thisObject": false, - "type": "dex" - }, - { - "class_name": "dalvik.system.DexFile", - "method": null, - "thisObject": false, - "type": "dex" - }, - { - "class_name": "dalvik.system.PathClassLoader", - "method": null, - "thisObject": false, - "type": "dex" - }, - { - "class_name": "java.lang.reflect.Method", - "method": "invoke", - "thisObject": false, - "type": "reflection" - }, - { - "class_name": "javax.crypto.spec.SecretKeySpec", - "method": null, - "thisObject": false, - "type": "crypto" - }, - { - "class_name": "javax.crypto.Cipher", - "method": "doFinal", - "thisObject": true, - "type": "crypto" - }, - { - "class_name": "javax.crypto.Mac", - "method": "doFinal", - "thisObject": false, - "type": "crypto" - }, - { - "class_name": "android.app.ApplicationPackageManager", - "method": "setComponentEnabledSetting", - "thisObject": false, - "type": "generic" - }, - { - "class_name": "android.app.NotificationManager", - "method": "notify", - "thisObject": false, - "type": "generic" - }, - { - "class_name": "android.util.Base64", - "method": "decode", - "thisObject": false, - "type": "generic" - }, - { - "class_name": "android.telephony.TelephonyManager", - "method": "listen", - "thisObject": false, - "type": "generic" - }, - { - "class_name": "android.util.Base64", - "method": "encode", - "thisObject": false, - "type": "generic" - }, - { - "class_name": "android.util.Base64", - "method": "encodeToString", - "thisObject": false, - "type": "generic" - }, - { - "class_name": "android.net.ConnectivityManager", - "method": "setMobileDataEnabled", - "thisObject": false, - "type": "generic" - }, - { - "class_name": "android.content.BroadcastReceiver", - "method": "abortBroadcast", - "thisObject": false, - "type": "generic" - }, - { - "class_name": "android.telephony.SmsManager", - "method": "sendTextMessage", - "thisObject": false, - "type": "sms" - }, - { - "class_name": "android.telephony.SmsManager", - "method": "sendMultipartTextMessage", - "thisObject": false, - "type": "sms" - }, - { - "class_name": "java.lang.Runtime", - "method": "exec", - "thisObject": false, - "type": "runtime" - }, - { - "class_name": "java.lang.ProcessBuilder", - "method": "start", - "thisObject": true, - "type": "runtime" - }, - { - "class_name": "java.io.FileOutputStream", - "method": "write", - "thisObject": false, - "type": "runtime" - }, - { - "class_name": "java.io.FileInputStream", - "method": "read", - "thisObject": false, - "type": "runtime" - }, - { - "class_name": "android.app.ActivityManager", - "method": "killBackgroundProcesses", - "thisObject": false, - "type": "runtime" - }, - { - "class_name": "android.os.Process", - "method": "killProcess", - "thisObject": false, - "type": "runtime" - }, - { - "class_name": "android.content.ContentResolver", - "method": "query", - "thisObject": false, - "type": "content" - }, - { - "class_name": "android.content.ContentResolver", - "method": "registerContentObserver", - "thisObject": false, - "type": "content" - }, - { - "class_name": "android.content.ContentResolver", - "method": "insert", - "thisObject": false, - "type": "content" - }, - { - "class_name": "android.accounts.AccountManager", - "method": "getAccountsByType", - "thisObject": false, - "type": "content" - }, - { - "class_name": "android.accounts.AccountManager", - "method": "getAccounts", - "thisObject": false, - "type": "content" - }, - { - "class_name": "android.location.Location", - "method": "getLatitude", - "thisObject": false, - "type": "content" - }, - { - "class_name": "android.location.Location", - "method": "getLongitude", - "thisObject": false, - "type": "content" - }, - { - "class_name": "android.content.ContentResolver", - "method": "delete", - "thisObject": false, - "type": "content" - }, - { - "class_name": "android.media.AudioRecord", - "method": "startRecording", - "thisObject": false, - "type": "content" - }, - { - "class_name": "android.media.MediaRecorder", - "method": "start", - "thisObject": false, - "type": "content" - }, - { - "class_name": "android.os.SystemProperties", - "method": "get", - "thisObject": false, - "type": "content" - }, - { - "class_name": "android.app.ApplicationPackageManager", - "method": "getInstalledPackages", - "thisObject": false, - "type": "content" - }, - { - "class_name": "libcore.io.IoBridge", - "method": "open", - "thisObject": false, - "type": "file" - } - ], - "trace": false -} \ No newline at end of file diff --git a/mobsf/DynamicAnalyzer/views/android/analysis.py b/mobsf/DynamicAnalyzer/views/android/analysis.py index 34208b1ccb..727b43b594 100644 --- a/mobsf/DynamicAnalyzer/views/android/analysis.py +++ b/mobsf/DynamicAnalyzer/views/android/analysis.py @@ -2,22 +2,20 @@ import io import logging import os -import re import shutil -import tarfile from json import load from pathlib import Path from mobsf.MobSF.utils import ( - clean_filename, is_file_exists, - is_pipe_or_link, python_list, ) -from mobsf.StaticAnalyzer.models import StaticAnalyzerAndroid -from mobsf.MalwareAnalyzer.views.MalwareDomainCheck import ( - MalwareDomainCheck, +from mobsf.DynamicAnalyzer.views.common.shared import ( + extract_urls_domains_emails, + get_app_files, ) +from mobsf.StaticAnalyzer.models import StaticAnalyzerAndroid + logger = logging.getLogger(__name__) @@ -39,31 +37,13 @@ def run_analysis(apk_dir, md5_hash, package): if clip_tag2 in log_line: log_line = log_line.split(clip_tag2)[1] clipboard.append(log_line) - # URLs My Custom regex - url_pattern = re.compile( - r'((?:https?://|s?ftps?://|file://|' - r'javascript:|data:|www\d{0,3}' - r'[.])[\w().=/;,#:@?&~*+!$%\'{}-]+)', re.UNICODE) - urls = re.findall(url_pattern, data['traffic'].lower()) - if urls: - urls = list(set(urls)) - else: - urls = [] - # Domain Extraction and Malware Check - logger.info('Performing Malware Check on extracted Domains') - domains = MalwareDomainCheck().scan(urls) - - # Email Etraction Regex - emails = [] - regex = re.compile(r'[\w.-]{1,20}@[\w-]{1,20}\.[\w]{2,10}') - for email in regex.findall(data['traffic'].lower()): - if (email not in emails) and (not email.startswith('//')): - emails.append(email) + urls, domains, emails = extract_urls_domains_emails( + data['traffic'].lower()) # Tar dump and fetch files - all_files = get_app_files(apk_dir, md5_hash, package) + all_files = get_app_files(apk_dir, package) analysis_result['urls'] = urls analysis_result['domains'] = domains - analysis_result['emails'] = emails + analysis_result['emails'] = list(emails) analysis_result['clipboard'] = clipboard analysis_result['xml'] = all_files['xml'] analysis_result['sqlite'] = all_files['sqlite'] @@ -174,81 +154,6 @@ def get_log_data(apk_dir, package): 'traffic': traffic} -def safe_paths(tar_meta): - """Safe filenames in windows.""" - for fh in tar_meta: - fh.name = clean_filename(fh.name) - yield fh - - -def get_app_files(apk_dir, md5_hash, package): - """Get files from device.""" - logger.info('Getting app files') - all_files = {'xml': [], 'sqlite': [], 'others': []} - # Extract Device Data - tar_loc = os.path.join(apk_dir, package + '.tar') - untar_dir = os.path.join(apk_dir, 'DYNAMIC_DeviceData/') - if not is_file_exists(tar_loc): - return all_files - if os.path.exists(untar_dir): - # fix for permission errors - shutil.rmtree(untar_dir) - try: - with tarfile.open(tar_loc, errorlevel=1) as tar: - - def is_within_directory(directory, target): - abs_directory = os.path.abspath(directory) - abs_target = os.path.abspath(target) - prefix = os.path.commonprefix([abs_directory, abs_target]) - return prefix == abs_directory - - def safe_extract(tar, path='.', - members=None, - *, - numeric_owner=False): - for member in tar.getmembers(): - member_path = os.path.join(path, member.name) - if not is_within_directory(path, member_path): - raise Exception('Attempted Path Traversal in Tar File') - tar.extractall(path, members, numeric_owner=numeric_owner) - - safe_extract(tar, untar_dir, members=safe_paths(tar)) - except FileExistsError: - pass - except Exception: - logger.exception('Tar extraction failed') - # Do Static Analysis on Data from Device - try: - if not os.path.exists(untar_dir): - os.makedirs(untar_dir) - for dir_name, _, files in os.walk(untar_dir): - for jfile in files: - file_path = os.path.join(untar_dir, dir_name, jfile) - fileparam = file_path.replace(untar_dir, '') - if is_pipe_or_link(file_path): - continue - if jfile == 'lib': - pass - else: - if jfile.endswith('.xml'): - all_files['xml'].append( - {'type': 'xml', 'file': fileparam}) - else: - with open(file_path, # lgtm [py/path-injection] - 'r', - encoding='ISO-8859-1') as flip: - file_cnt_sig = flip.read(6) - if file_cnt_sig == 'SQLite': - all_files['sqlite'].append( - {'type': 'db', 'file': fileparam}) - elif not jfile.endswith('.DS_Store'): - all_files['others'].append( - {'type': 'others', 'file': fileparam}) - except Exception: - logger.exception('Getting app files') - return all_files - - def generate_download(apk_dir, md5_hash, download_dir, package): """Generating Downloads.""" logger.info('Generating Downloads') diff --git a/mobsf/DynamicAnalyzer/views/android/dynamic_analyzer.py b/mobsf/DynamicAnalyzer/views/android/dynamic_analyzer.py index 8ecc46ef9e..d80a75298c 100755 --- a/mobsf/DynamicAnalyzer/views/android/dynamic_analyzer.py +++ b/mobsf/DynamicAnalyzer/views/android/dynamic_analyzer.py @@ -43,7 +43,7 @@ logger = logging.getLogger(__name__) -def dynamic_analysis(request, api=False): +def android_dynamic_analysis(request, api=False): """Android Dynamic Analysis Entry point.""" try: scan_apps = [] @@ -97,7 +97,7 @@ def dynamic_analysis(request, api=False): 'version': settings.MOBSF_VER} if api: return context - template = 'dynamic_analysis/dynamic_analysis.html' + template = 'dynamic_analysis/android/dynamic_analysis.html' return render(request, template, context) except Exception as exp: logger.exception('Dynamic Analysis') @@ -121,7 +121,7 @@ def dynamic_analyzer(request, checksum, api=False): # in REST API return print_n_send_error_response( request, - 'Invalid Parameters', + 'Invalid Hash', api) package = get_package_name(checksum) if not package: diff --git a/mobsf/DynamicAnalyzer/views/android/environment.py b/mobsf/DynamicAnalyzer/views/android/environment.py index c48b85a1ea..1840a3ca13 100644 --- a/mobsf/DynamicAnalyzer/views/android/environment.py +++ b/mobsf/DynamicAnalyzer/views/android/environment.py @@ -191,9 +191,8 @@ def configure_proxy(self, project, request): """HTTPS Proxy.""" self.install_mobsf_ca('install') proxy_port = settings.PROXY_PORT - logger.info('Starting HTTPs Proxy on %s', proxy_port) - httptools_url = get_http_tools_url(request) - stop_httptools(httptools_url) + logger.info('Starting HTTPS Proxy on %s', proxy_port) + stop_httptools(get_http_tools_url(request)) start_proxy(proxy_port, project) def install_mobsf_ca(self, action): diff --git a/mobsf/DynamicAnalyzer/views/android/frida_core.py b/mobsf/DynamicAnalyzer/views/android/frida_core.py index fb40ee78fe..1161144af0 100644 --- a/mobsf/DynamicAnalyzer/views/android/frida_core.py +++ b/mobsf/DynamicAnalyzer/views/android/frida_core.py @@ -37,7 +37,7 @@ def __init__(self, app_hash, package, defaults, auxiliary, extras, code): self.extras = extras self.code = code self.frida_dir = os.path.join(settings.TOOLS_DIR, - 'frida_scripts') + 'frida_scripts', 'android') self.apk_dir = os.path.join(settings.UPLD_DIR, self.hash + '/') self.api_mon = os.path.join(self.apk_dir, 'mobsf_api_monitor.txt') self.frida_log = os.path.join(self.apk_dir, 'mobsf_frida_out.txt') @@ -90,8 +90,8 @@ def get_script(self): scripts = [self.code] scripts.extend(self.get_default_scripts()) scripts.extend(self.get_auxiliary()) - final = 'setTimeout(function() {{ {} }}, 0)'.format( - '\n'.join(scripts)) + combined = '\n'.join(scripts) + final = f'setTimeout(function() {{ \n{combined}\n}}, 1000)' return final def frida_response(self, message, data): diff --git a/mobsf/DynamicAnalyzer/views/android/frida_scripts.py b/mobsf/DynamicAnalyzer/views/android/frida_scripts.py index 59e369ac57..1626be7ac1 100644 --- a/mobsf/DynamicAnalyzer/views/android/frida_scripts.py +++ b/mobsf/DynamicAnalyzer/views/android/frida_scripts.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path from django.conf import settings @@ -6,17 +6,13 @@ def get_content(file_name): - content = '' - script = os.path.join(settings.TOOLS_DIR, - 'frida_scripts', - 'auxiliary', - file_name) - - with open(script, 'r', - encoding='utf8', - errors='ignore') as scp: - content = scp.read() - return content + tools_dir = Path(settings.TOOLS_DIR) + aux_dir = tools_dir / 'frida_scripts' / 'ios' / 'auxiliary' + script = aux_dir / file_name + + if script.exists(): + return script.read_text('utf-8', 'ignore') + return '' def get_loaded_classes(): diff --git a/mobsf/DynamicAnalyzer/views/android/operations.py b/mobsf/DynamicAnalyzer/views/android/operations.py index a49d1416a9..2dde3739a4 100644 --- a/mobsf/DynamicAnalyzer/views/android/operations.py +++ b/mobsf/DynamicAnalyzer/views/android/operations.py @@ -4,15 +4,18 @@ import logging import os import random -import re import subprocess import threading from pathlib import Path from django.conf import settings -from django.http import HttpResponse from django.views.decorators.http import require_http_methods +from mobsf.DynamicAnalyzer.views.common.shared import ( + invalid_params, + is_attack_pattern, + send_response, +) from mobsf.DynamicAnalyzer.views.android.environment import ( Environment, ) @@ -46,35 +49,6 @@ def get_package_name(checksum): if packages.get(checksum): return packages[checksum][0] return None - - -def send_response(data, api=False): - """Return JSON Response.""" - if api: - return data - return HttpResponse( - json.dumps(data), # lgtm [py/stack-trace-exposure] - content_type='application/json') - - -def is_attack_pattern(user_input): - """Check for attacks.""" - atk_pattern = re.compile(r';|\$\(|\|\||&&') - stat = re.findall(atk_pattern, user_input) - if stat: - logger.error('Possible RCE attack detected') - return stat - - -def invalid_params(api=False): - """Standard response for invalid params.""" - msg = 'Invalid Parameters' - logger.error(msg) - data = {'status': 'failed', 'message': msg} - if api: - return data - return send_response(data) - # AJAX diff --git a/mobsf/DynamicAnalyzer/views/android/report.py b/mobsf/DynamicAnalyzer/views/android/report.py index 9b663c5a6f..dd841f7b04 100644 --- a/mobsf/DynamicAnalyzer/views/android/report.py +++ b/mobsf/DynamicAnalyzer/views/android/report.py @@ -1,14 +1,11 @@ # -*- coding: utf_8 -*- """Dynamic Analyzer Reporting.""" import logging -import ntpath import os -import io from django.conf import settings from django.shortcuts import render from django.template.defaulttags import register -from django.utils.html import escape import mobsf.MalwareAnalyzer.views.Trackers as Trackers from mobsf.DynamicAnalyzer.views.android.analysis import ( @@ -29,11 +26,8 @@ from mobsf.MobSF.utils import ( is_file_exists, is_md5, - is_path_traversal, - is_safe_path, key, print_n_send_error_response, - read_sqlite, ) @@ -53,7 +47,7 @@ def view_report(request, checksum, api=False): # in REST API return print_n_send_error_response( request, - 'Invalid Parameters', + 'Invalid Hash', api) package = get_package_name(checksum) if not package: @@ -108,68 +102,3 @@ def view_report(request, checksum, api=False): logger.exception('Dynamic Analysis Report Generation') err = 'Error Generating Dynamic Analysis Report. ' + str(exp) return print_n_send_error_response(request, err, api) - - -def view_file(request, api=False): - """View File.""" - logger.info('Viewing File') - try: - typ = '' - rtyp = '' - dat = '' - sql_dump = {} - if api: - fil = request.POST['file'] - md5_hash = request.POST['hash'] - typ = request.POST['type'] - else: - fil = request.GET['file'] - md5_hash = request.GET['hash'] - typ = request.GET['type'] - if not is_md5(md5_hash): - return print_n_send_error_response(request, - 'Invalid Parameters', - api) - src = os.path.join( - settings.UPLD_DIR, - md5_hash, - 'DYNAMIC_DeviceData/') - sfile = os.path.join(src, fil) - if not is_safe_path(src, sfile) or is_path_traversal(fil): - err = 'Path Traversal Attack Detected' - return print_n_send_error_response(request, err, api) - with io.open( - sfile, # lgtm [py/path-injection] - mode='r', - encoding='ISO-8859-1') as flip: - dat = flip.read() - if fil.endswith('.xml') and typ == 'xml': - rtyp = 'xml' - elif typ == 'db': - dat = None - sql_dump = read_sqlite(sfile) - rtyp = 'asciidoc' - elif typ == 'others': - rtyp = 'asciidoc' - else: - err = 'File type not supported' - return print_n_send_error_response(request, err, api) - fil = escape(ntpath.basename(fil)) - context = { - 'title': fil, - 'file': fil, - 'data': dat, - 'sqlite': sql_dump, - 'type': rtyp, - 'version': settings.MOBSF_VER, - } - template = 'general/view.html' - if api: - return context - return render(request, template, context) - except Exception: - logger.exception('Viewing File') - return print_n_send_error_response( - request, - 'Error Viewing File', - api) diff --git a/mobsf/DynamicAnalyzer/views/android/tests_common.py b/mobsf/DynamicAnalyzer/views/android/tests_common.py index f23ff4f079..2925de356f 100644 --- a/mobsf/DynamicAnalyzer/views/android/tests_common.py +++ b/mobsf/DynamicAnalyzer/views/android/tests_common.py @@ -7,11 +7,13 @@ from django.conf import settings from django.views.decorators.http import require_http_methods -from mobsf.DynamicAnalyzer.views.android.operations import ( - get_package_name, +from mobsf.DynamicAnalyzer.views.common.shared import ( invalid_params, send_response, ) +from mobsf.DynamicAnalyzer.views.android.operations import ( + get_package_name, +) from mobsf.DynamicAnalyzer.views.android.environment import ( Environment, ) @@ -143,8 +145,7 @@ def download_data(request, api=False): 'message': 'App details not found in database'} return send_response(data, api) apk_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/') - httptools_url = get_http_tools_url(request) - stop_httptools(httptools_url) + stop_httptools(get_http_tools_url(request)) files_loc = '/data/local/' logger.info('Archiving files created by app') env.adb_command(['tar', '-cvf', files_loc + package + '.tar', diff --git a/mobsf/DynamicAnalyzer/views/android/tests_frida.py b/mobsf/DynamicAnalyzer/views/android/tests_frida.py index 5762f77f11..b4cf8f4a1a 100644 --- a/mobsf/DynamicAnalyzer/views/android/tests_frida.py +++ b/mobsf/DynamicAnalyzer/views/android/tests_frida.py @@ -1,7 +1,6 @@ # -*- coding: utf_8 -*- """Frida tests.""" import base64 -import glob import os import re import json @@ -14,16 +13,17 @@ from django.views.decorators.http import require_http_methods from mobsf.DynamicAnalyzer.views.android.frida_core import Frida -from mobsf.DynamicAnalyzer.views.android.operations import ( - get_package_name, +from mobsf.DynamicAnalyzer.views.common.shared import ( invalid_params, is_attack_pattern, send_response, ) +from mobsf.DynamicAnalyzer.views.android.operations import ( + get_package_name, +) from mobsf.MobSF.utils import ( is_file_exists, is_md5, - is_safe_path, print_n_send_error_response, ) @@ -32,22 +32,6 @@ # AJAX -@require_http_methods(['GET']) -def list_frida_scripts(request, api=False): - """Get frida scripts from others.""" - scripts = [] - others = os.path.join(settings.TOOLS_DIR, - 'frida_scripts', - 'others') - files = glob.glob(others + '**/*.js', recursive=True) - for item in files: - scripts.append(Path(item).stem) - return send_response({'status': 'ok', - 'files': scripts}, - api) -# AJAX - - @require_http_methods(['POST']) def get_runtime_dependencies(request, api=False): """Get App runtime dependencies.""" @@ -71,32 +55,6 @@ def get_runtime_dependencies(request, api=False): # AJAX -@require_http_methods(['POST']) -def get_script(request, api=False): - """Get frida scripts from others.""" - data = {'status': 'ok', 'content': ''} - try: - scripts = request.POST.getlist('scripts[]') - others = os.path.join(settings.TOOLS_DIR, - 'frida_scripts', - 'others') - script_ct = [] - for script in scripts: - script_file = os.path.join(others, script + '.js') - if not is_safe_path(others, script_file): - data = { - 'status': 'failed', - 'message': 'Path traversal detected.'} - return send_response(data, api) - if is_file_exists(script_file): - script_ct.append(Path(script_file).read_text()) - data['content'] = '\n'.join(script_ct) - except Exception: - pass - return send_response(data, api) -# AJAX - - @require_http_methods(['POST']) def instrument(request, api=False): """Instrument app with frida.""" @@ -119,6 +77,7 @@ def instrument(request, api=False): if cls_trace: extras['class_trace'] = cls_trace.strip() if (is_attack_pattern(default_hooks) + or is_attack_pattern(auxiliary_hooks) or not is_md5(md5_hash)): return invalid_params(api) package = get_package_name(md5_hash) diff --git a/mobsf/DynamicAnalyzer/views/common/device.py b/mobsf/DynamicAnalyzer/views/common/device.py new file mode 100644 index 0000000000..f674b4d281 --- /dev/null +++ b/mobsf/DynamicAnalyzer/views/common/device.py @@ -0,0 +1,86 @@ +# -*- coding: utf_8 -*- +"""Dynamic Analyzer Reporting.""" +import logging +import ntpath +from pathlib import Path + +from django.conf import settings +from django.shortcuts import render +from django.utils.html import escape + +from mobsf.MobSF.utils import ( + is_md5, + is_path_traversal, + is_safe_path, + print_n_send_error_response, + read_sqlite, +) + +from biplist import ( + writePlistToString, +) + + +logger = logging.getLogger(__name__) + + +def view_file(request, api=False): + """View File in app data directory.""" + logger.info('Viewing File') + try: + typ = '' + rtyp = '' + dat = '' + sql_dump = {} + if api: + fil = request.POST['file'] + md5_hash = request.POST['hash'] + typ = request.POST['type'] + else: + fil = request.GET['file'] + md5_hash = request.GET['hash'] + typ = request.GET['type'] + if not is_md5(md5_hash): + return print_n_send_error_response( + request, + 'Invalid Parameters', + api) + src = Path(settings.UPLD_DIR) / md5_hash / 'DYNAMIC_DeviceData' + sfile = src / fil + src = src.as_posix() + if not is_safe_path(src, sfile.as_posix()) or is_path_traversal(fil): + err = 'Path Traversal Attack Detected' + return print_n_send_error_response(request, err, api) + dat = sfile.read_text('ISO-8859-1') + if fil.endswith('.plist') and dat.startswith('bplist0'): + dat = writePlistToString(dat).decode('utf-8', 'ignore') + if fil.endswith(('.xml', '.plist')) and typ in ['xml', 'plist']: + rtyp = 'xml' + elif typ == 'db': + dat = None + sql_dump = read_sqlite(sfile.as_posix()) + rtyp = 'asciidoc' + elif typ == 'others': + rtyp = 'asciidoc' + else: + err = 'File type not supported' + return print_n_send_error_response(request, err, api) + fil = escape(ntpath.basename(fil)) + context = { + 'title': fil, + 'file': fil, + 'data': dat, + 'sqlite': sql_dump, + 'type': rtyp, + 'version': settings.MOBSF_VER, + } + template = 'general/view.html' + if api: + return context + return render(request, template, context) + except Exception: + logger.exception('Viewing File') + return print_n_send_error_response( + request, + 'Error Viewing File', + api) diff --git a/mobsf/DynamicAnalyzer/views/common/frida.py b/mobsf/DynamicAnalyzer/views/common/frida.py new file mode 100644 index 0000000000..f6adc5fafa --- /dev/null +++ b/mobsf/DynamicAnalyzer/views/common/frida.py @@ -0,0 +1,67 @@ +"""Shared Frida Views.""" +import glob +import os +from pathlib import Path + +from django.conf import settings +from django.views.decorators.http import require_http_methods + +from mobsf.DynamicAnalyzer.views.common.shared import ( + send_response, +) +from mobsf.MobSF.utils import ( + is_file_exists, + is_safe_path, +) +# AJAX + + +@require_http_methods(['POST']) +def list_frida_scripts(request, api=False): + """List frida scripts from others.""" + scripts = [] + device = request.POST.get('device', 'android') + if device != 'android': + device = 'ios' + others = os.path.join(settings.TOOLS_DIR, + 'frida_scripts', + device, + 'others') + files = glob.glob(others + '**/*.js', recursive=True) + for item in files: + scripts.append(Path(item).stem) + scripts.sort() + return send_response( + {'status': 'ok', + 'files': scripts}, + api) +# AJAX + + +@require_http_methods(['POST']) +def get_script(request, api=False): + """Get frida scripts from others.""" + data = {'status': 'ok', 'content': ''} + try: + device = request.POST.get('device', 'android') + if device != 'android': + device = 'ios' + scripts = request.POST.getlist('scripts[]') + others = os.path.join(settings.TOOLS_DIR, + 'frida_scripts', + device, + 'others') + script_ct = [] + for script in scripts: + script_file = os.path.join(others, script + '.js') + if not is_safe_path(others, script_file): + data = { + 'status': 'failed', + 'message': 'Path traversal detected.'} + return send_response(data, api) + if is_file_exists(script_file): + script_ct.append(Path(script_file).read_text()) + data['content'] = '\n'.join(script_ct) + except Exception: + pass + return send_response(data, api) diff --git a/mobsf/DynamicAnalyzer/views/common/shared.py b/mobsf/DynamicAnalyzer/views/common/shared.py new file mode 100644 index 0000000000..6bd3f26fdd --- /dev/null +++ b/mobsf/DynamicAnalyzer/views/common/shared.py @@ -0,0 +1,162 @@ +# -*- coding: utf_8 -*- +"""Common helpers for Android and iOS Dynamic Analysis.""" +import logging +import os +import re +import json +import tarfile +import shutil +from pathlib import Path + +from django.http import HttpResponse + +from mobsf.MalwareAnalyzer.views.MalwareDomainCheck import ( + MalwareDomainCheck, +) +from mobsf.MobSF.utils import ( + EMAIL_REGEX, + URL_REGEX, + clean_filename, + is_pipe_or_link, +) + +logger = logging.getLogger(__name__) + + +def extract_urls_domains_emails(data): + """Extract URLs, Domains and Emails.""" + # URL Extraction + urls = re.findall(URL_REGEX, data.lower()) + if urls: + urls = list(set(urls)) + else: + urls = [] + # Domain Extraction and Malware Check + logger.info('Performing Malware Check on extracted Domains') + domains = MalwareDomainCheck().scan(urls) + # Email Etraction Regex + emails = set() + for email in EMAIL_REGEX.findall(data.lower()): + if email.startswith('//'): + continue + if email.endswith('.png'): + continue + emails.add(email) + return urls, domains, emails + + +def safe_paths(tar_meta): + """Safe filenames in windows.""" + for fh in tar_meta: + fh.name = clean_filename(fh.name) + yield fh + + +def untar_files(tar_loc, untar_dir): + """Untar files.""" + logger.info('Extracting Tar files') + # Extract Device Data + if not tar_loc.exists(): + return False + if untar_dir.exists(): + # fix for permission errors + shutil.rmtree(untar_dir) + else: + os.makedirs(untar_dir) + try: + with tarfile.open(tar_loc.as_posix(), errorlevel=1) as tar: + + def is_within_directory(directory, target): + abs_directory = os.path.abspath(directory) + abs_target = os.path.abspath(target) + prefix = os.path.commonprefix([abs_directory, abs_target]) + return prefix == abs_directory + + def safe_extract(tar, path='.', + members=None, + *, + numeric_owner=False): + for member in tar.getmembers(): + member_path = os.path.join(path, member.name) + if not is_within_directory(path, member_path): + raise Exception('Attempted Path Traversal in Tar File') + tar.extractall(path, members, numeric_owner=numeric_owner) + + safe_extract(tar, untar_dir, members=safe_paths(tar)) + except (FileExistsError, tarfile.ReadError): + logger.warning('Failed to extract tar file') + except Exception: + logger.exception('Tar extraction failed') + return True + + +def get_app_files(app_dir, tarname): + """Get files from device.""" + logger.info('Getting app files') + all_files = {'xml': [], 'sqlite': [], 'others': [], 'plist': []} + appdir = Path(app_dir) + tar_loc = appdir / f'{tarname}.tar' + untar_dir = appdir / 'DYNAMIC_DeviceData' + success = untar_files(tar_loc, untar_dir) + if not success: + return all_files + # Do Static Analysis on Data from Device + try: + untar_dir = untar_dir.as_posix() + for dir_name, _, files in os.walk(untar_dir): + for jfile in files: + file_path = os.path.join(untar_dir, dir_name, jfile) + fileparam = file_path.replace(f'{untar_dir}/', '') + if is_pipe_or_link(file_path): + continue + if jfile == 'lib': + pass + else: + if jfile.endswith('.xml'): + all_files['xml'].append( + {'type': 'xml', 'file': fileparam}) + elif jfile.endswith('.plist'): + all_files['plist'].append( + {'type': 'plist', 'file': fileparam}) + else: + with open(file_path, + 'r', + encoding='ISO-8859-1') as flip: + file_cnt_sig = flip.read(6) + if file_cnt_sig == 'SQLite': + all_files['sqlite'].append( + {'type': 'db', 'file': fileparam}) + elif not jfile.endswith('.DS_Store'): + all_files['others'].append( + {'type': 'others', 'file': fileparam}) + except Exception: + logger.exception('Getting app files') + return all_files + + +def send_response(data, api=False): + """Return JSON Response.""" + if api: + return data + return HttpResponse( + json.dumps(data), + content_type='application/json') + + +def invalid_params(api=False): + """Standard response for invalid params.""" + msg = 'Invalid Parameters' + logger.error(msg) + data = {'status': 'failed', 'message': msg} + if api: + return data + return send_response(data) + + +def is_attack_pattern(user_input): + """Check for attacks.""" + atk_pattern = re.compile(r';|\$\(|\|\||&&') + stat = re.findall(atk_pattern, user_input) + if stat: + logger.error('Possible RCE attack detected') + return stat diff --git a/mobsf/DynamicAnalyzer/views/ios/analysis.py b/mobsf/DynamicAnalyzer/views/ios/analysis.py new file mode 100644 index 0000000000..0537349e9f --- /dev/null +++ b/mobsf/DynamicAnalyzer/views/ios/analysis.py @@ -0,0 +1,119 @@ +# -*- coding: utf_8 -*- +"""iOS Dynamic Analysis.""" +import logging +import json +from pathlib import Path + +from mobsf.DynamicAnalyzer.views.common.shared import ( + extract_urls_domains_emails, + get_app_files, +) + +logger = logging.getLogger(__name__) + + +def get_screenshots(checksum, download_dir): + """Get Screenshots.""" + screenshots = [] + screen_dir = Path(download_dir) + for img in screen_dir.glob('*.png'): + if (img.name.startswith(checksum) + and 'sshot' in img.name): + screenshots.append(img.name) + return screenshots + + +def get_logs_data(app_dir, bundle_id): + """Get Data for analysis.""" + data = [] + dump_file = Path(app_dir) / 'mobsf_dump_file.txt' + fd_log_file = Path(app_dir) / 'mobsf_frida_out.txt' + flows = Path.home() / '.httptools' / 'flows' + web_file = flows / f'{bundle_id}.flow.txt' + if dump_file.exists(): + data.append(dump_file.read_text('utf-8', 'ignore')) + if fd_log_file.exists(): + data.append(fd_log_file.read_text('utf-8', 'ignore')) + if web_file.exists(): + data.append(web_file.read_text('utf-8', 'ignore')) + return '\n'.join(data) + + +def run_analysis(app_dir, bundle_id, checksum): + """Run Dynamic File Analysis.""" + analysis_result = {} + logger.info('Dynamic File Analysis') + domains = {} + # Collect Log data + data = get_logs_data(app_dir, bundle_id) + urls, domains, emails = extract_urls_domains_emails(data) + # App data files analysis + pfiles = get_app_files(app_dir, f'{checksum}-app-container') + analysis_result['sqlite'] = pfiles['sqlite'] + analysis_result['plist'] = pfiles['plist'] + analysis_result['others'] = pfiles['others'] + analysis_result['urls'] = urls + analysis_result['domains'] = domains + analysis_result['emails'] = list(emails) + return analysis_result + + +def ios_api_analysis(app_dir): + """The iOS API Analysis.""" + dump = { + 'cookies': [], + 'crypto': [], + 'network': [], + 'files': set(), + 'keychain': [], + 'logs': set(), + 'credentials': [], + 'userdefaults': {}, + 'pasteboard': set(), + 'textinputs': [], + 'datadir': [], + 'sql': [], + 'json': [], + } + try: + dump_file = app_dir / 'mobsf_dump_file.txt' + if not dump_file.exists(): + return dump + logger.info('Analyzing Frida Data Dump') + data = dump_file.read_text( + encoding='utf-8', + errors='ignore').splitlines() + for line in data: + parsed = json.loads(line) + if parsed.get('cookies'): + dump['cookies'] = parsed['cookies'] + elif parsed.get('crypto'): + dump['crypto'].append(parsed['crypto']) + elif parsed.get('filename'): + dump['files'].add(parsed['filename']) + elif parsed.get('keychain'): + dump['keychain'] = parsed['keychain'] + elif parsed.get('nslog'): + dump['logs'].add(parsed['nslog']) + elif parsed.get('credentialstorage'): + dump['credentials'] = parsed['credentialstorage'] + elif parsed.get('nsuserdefaults'): + dump['userdefaults'] = parsed['nsuserdefaults'] + elif parsed.get('pasteboard'): + dump['pasteboard'].add(parsed['pasteboard']) + elif parsed.get('textinput'): + dump['textinputs'].append(parsed['textinput']) + elif parsed.get('network'): + dump['network'].append(parsed['network']) + elif parsed.get('datadir'): + dump['datadir'] = parsed['datadir'] + elif parsed.get('sql'): + dump['sql'].append(parsed['sql']) + elif parsed.get('json'): + dump['json'].append(parsed['json']) + if len(dump['network']) > 0: + dump['network'] = list( + {v['url']: v for v in dump['network']}.values()) + except Exception: + logger.exception('Analyzing dump data failed') + return dump diff --git a/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py b/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py new file mode 100644 index 0000000000..6cb30eeb5d --- /dev/null +++ b/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py @@ -0,0 +1,528 @@ +# -*- coding: utf_8 -*- +"""Corellium APIs.""" +import logging +import os +from copy import deepcopy +from socket import gethostname + +from django.conf import settings + +import requests + +SUCCESS_RESP = (200, 204) +ERROR_RESP = (400, 403, 404, 409) +OK = 'ok' +logger = logging.getLogger(__name__) + + +class CorelliumAPI: + + def __init__(self, project_id) -> None: + self.api = 'https://app.corellium.com/api/v1' + self.api_key = getattr(settings, 'CORELLIUM_API_KEY', '') + self.headers = { + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {self.api_key}', + } + self.project_id = project_id + + def api_ready(self): + """Check API Availability.""" + r = requests.get(f'{self.api}/ready') + if r.status_code in SUCCESS_RESP: + return True + else: + logger.error('Corellium API is not ready.' + ' Status code: %s', r.status_code) + return False + + def api_auth(self): + """Check Corellium API Auth.""" + if not self.api_key: + logger.error('Corellium API key is not set') + return False + r = requests.get( + f'{self.api}/projects', + headers=self.headers) + if r.status_code in ERROR_RESP: + return False + return True + + def get_projects(self): + """Get Projects.""" + logger.info('Getting Corellium project id') + if self.project_id: + return True + else: + ids = [] + r = requests.get( + f'{self.api}/projects?ids_only=true', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + for i in r.json(): + ids.append(i['id']) + if ids: + self.project_id = ids[0] + return True + return False + + def get_authorized_keys(self): + """Get SSH public keys associated with a project.""" + r = requests.get( + f'{self.api}/projects/{self.project_id}/keys', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return r.json() + return False + + def add_authorized_key(self, key): + """Add SSH public key to the Project.""" + logger.info('Adding SSH public key to Corellium project') + extras = '' + if os.getenv('MOBSF_PLATFORM') == 'docker': + extras = ' - (docker)' + data = { + 'kind': 'ssh', + 'label': f'MobSF SSH Key - {gethostname()}{extras}', + 'key': key, + } + r = requests.post( + f'{self.api}/projects/{self.project_id}/keys', + headers=self.headers, + json=data) + if r.status_code in SUCCESS_RESP: + return r.json()['identifier'] + return False + + def delete_authorized_key(self, key_id): + """Delete SSH public key from the Project.""" + r = requests.delete( + f'{self.api}/projects/{self.project_id}/keys/{key_id}', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return OK + return False + + def get_instances(self): + """Get Instances.""" + logger.info('Getting iOS instances') + instances = [] + r = requests.get( + f'{self.api}/instances', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + for i in r.json(): + if i['type'] == 'ios' and 'jailbroken' in i['patches']: + instances.append(i) + return instances + + def create_ios_instance(self, flavor, version): + """Create a Jailbroken iOS instance.""" + data = { + 'project': self.project_id, + 'name': f'MobSF iOS - {flavor.upper()}', + 'flavor': flavor, + 'os': version, + } + r = requests.post( + f'{self.api}/instances', + headers=self.headers, + json=data) + if r.status_code in SUCCESS_RESP: + return r.json()['id'] + return False + + +class CorelliumModelsAPI: + + def __init__(self) -> None: + self.api = 'https://app.corellium.com/api/v1' + self.api_key = getattr(settings, 'CORELLIUM_API_KEY', '') + self.headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {self.api_key}', + } + + def get_models(self): + r = requests.get( + f'{self.api}/models', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return r.json() + return False + + def get_supported_os(self, model): + models = self.get_models() + if not models: + return False + allowed = False + for i in models: + if i['type'] == 'ios' and model == i['model']: + allowed = True + break + if not allowed: + return False + r = requests.get( + f'{self.api}/models/{model}/software', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return r.json() + elif r.status_code in ERROR_RESP: + return r.json()['error'] + return False + + +class CorelliumInstanceAPI: + + def __init__(self, instance_id) -> None: + self.api = 'https://app.corellium.com/api/v1' + self.api_key = getattr(settings, 'CORELLIUM_API_KEY', '') + self.headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {self.api_key}', + } + self.instance_id = instance_id + + def start_instance(self): + """Start instance.""" + data = {'paused': False} + r = requests.post( + f'{self.api}/instances/{self.instance_id}/start', + headers=self.headers, + json=data) + if r.status_code in SUCCESS_RESP: + return OK + elif r.status_code in ERROR_RESP: + return r.json()['error'] + return False + + def stop_instance(self): + """Stop instance.""" + data = {'soft': True} + r = requests.post( + f'{self.api}/instances/{self.instance_id}/stop', + headers=self.headers, + json=data) + if r.status_code in SUCCESS_RESP: + return OK + elif r.status_code in ERROR_RESP: + return r.json()['error'] + return False + + def unpause_instance(self): + """Unpause instance.""" + r = requests.post( + f'{self.api}/instances/{self.instance_id}/unpause', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return OK + elif r.status_code in ERROR_RESP: + return r.json()['error'] + return False + + def reboot_instance(self): + """Reboot instance.""" + r = requests.post( + f'{self.api}/instances/{self.instance_id}/reboot', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return OK + elif r.status_code in ERROR_RESP: + return r.json()['error'] + return False + + def remove_instance(self): + """Remove instance.""" + r = requests.delete( + f'{self.api}/instances/{self.instance_id}', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return OK + elif r.status_code in ERROR_RESP: + return r.json()['error'] + return False + + def poll_instance(self): + """Check instance status.""" + r = requests.get( + f'{self.api}/instances/{self.instance_id}', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return r.json() + return False + + def screenshot(self): + """Take screenshot inside VM.""" + r = requests.get( + f'{self.api}/instances/{self.instance_id}/screenshot.png?scale=1', + headers=self.headers, + stream=True) + if r.status_code == 200: + return r.content + logger.error('Failed to take a screenshot. %s', r.json()['error']) + return False + + def start_network_capture(self): + """Start network capture.""" + r = requests.post( + f'{self.api}/instances/{self.instance_id}/sslsplit/enable', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return OK + err = r.json()['error'] + if 'network monitoring enabled' in err: + return OK + logger.error( + 'Failed to enable network monitoring. %s', err) + return r.json()['error'] + + def stop_network_capture(self): + """Stop network capture.""" + r = requests.post( + f'{self.api}/instances/{self.instance_id}/sslsplit/disable', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return OK + logger.error( + 'Failed to disable network monitoring. %s', r.json()['error']) + return r.json()['error'] + + def download_network_capture(self): + """Download network capture.""" + r = requests.get( + f'{self.api}/instances/{self.instance_id}/networkMonitor.pcap', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return r.content + logger.error( + 'Failed to download pcap. %s', r.json()['error']) + return None + + def console_log(self): + """Get Console Log.""" + r = requests.get( + f'{self.api}/instances/{self.instance_id}/consoleLog', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return r.content.decode('utf-8', 'ignore') + logger.error( + 'Failed to disable network monitoring. %s', r.json()['error']) + return r.json()['error'] + + def get_ssh_connection_string(self): + """Get SSH connection string.""" + r = requests.get( + f'{self.api}/instances/{self.instance_id}/quickConnectCommand', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return r.text + logger.error( + 'Failed to get SSH connection string %s', r.json()['error']) + return r.json()['error'] + + def device_input(self, event, x, y): + """Provide touch/button event to VM.""" + if event == 'home': + data = [{ + 'buttons': ['holdButton'], + }] + elif event == 'text': + data = [{ + 'text': x, + }] + elif event == 'enter': + data = [{ + 'buttons': ['enter'], + }] + elif event == 'backspace': + data = [{ + 'buttons': ['backspace'], + }] + elif event == 'left': + data = [{ + 'buttons': ['left'], + }] + elif event == 'right': + data = [{ + 'buttons': ['right'], + }] + elif event == 'swipe_up': + data = [{ + 'startButtons': ['finger'], + 'start': [[300, 600]], + 'bezierPoints': [[[350, 700]], [[375, 850]]], + 'end': [[400, 950]], + 'endButtons': [], + 'duration': 200, + }] + elif event == 'swipe_down': + data = [{ + 'startButtons': ['finger'], + 'start': [[300, 600]], + 'bezierPoints': [[[700, 350]], [[850, 375]]], + 'end': [[950, 400]], + 'endButtons': [], + 'duration': 200, + }] + elif event == 'swipe_left': + data = [{ + 'startButtons': ['finger'], + 'start': [[200, 200]], + 'bezierPoints': [[[700, 350]], [[850, 375]]], + 'end': [[950, 400]], + 'endButtons': [], + 'duration': 200, + }] + elif event == 'swipe_right': + data = [{ + 'startButtons': ['finger'], + 'start': [[700, 100]], + 'bezierPoints': [[[350, 750]], [[375, 875]]], + 'end': [[300, 600]], + 'endButtons': [], + 'duration': 200, + }] + else: + data = [ + {'buttons': ['finger'], + 'position': [[x, y]], + 'wait': 0}, + {'buttons': [], 'wait': 100}] + r = requests.post( + f'{self.api}/instances/{self.instance_id}/input', + headers=self.headers, + json=data) + if r.status_code in SUCCESS_RESP: + return OK + logger.error( + 'Failed to send touch event. %s', r.json()['error']) + return r.json()['error'] + + +class CorelliumAgentAPI: + + def __init__(self, instance_id) -> None: + self.api = 'https://app.corellium.com/api/v1' + self.api_key = getattr(settings, 'CORELLIUM_API_KEY', '') + self.headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {self.api_key}', + } + self.instance_id = instance_id + + def agent_ready(self): + """Agent ready.""" + r = requests.get( + f'{self.api}/instances/{self.instance_id}/agent/v1/app/ready', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + logger.info('Corellium Agent is Ready!') + return r.json()['ready'] + elif r.status_code in ERROR_RESP: + logger.error('Corellium Agent is not Ready, Please Wait!') + return r.json()['error'] + return False + + def unlock_device(self): + """Unlock iOS device.""" + r = requests.post( + f'{self.api}/instances/{self.instance_id}/agent/v1/system/unlock', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + logger.info('Device unlocked') + return OK + elif r.status_code in ERROR_RESP: + logger.error('Failed to unlock device') + return r.json()['error'] + return False + + def upload_ipa(self, ipa_file): + """Upload IPA.""" + logger.info('Uploading IPA to iOS instance...') + headers = deepcopy(self.headers) + headers['Content-Type'] = 'application/octet-stream' + r = requests.put( + (f'{self.api}/instances/{self.instance_id}' + f'/agent/v1/file/device/%2Ftmp%2Fapp.ipa'), + data=open(ipa_file, 'rb').read(), + headers=headers) + if r.status_code in SUCCESS_RESP: + logger.info('IPA uploaded to instance') + return OK + logger.error('Failed to upload IPA %s', r.json()['error']) + return r.json()['error'] + + def install_ipa(self): + """Install IPA.""" + data = {'path': '/tmp/app.ipa'} + r = requests.post( + f'{self.api}/instances/{self.instance_id}/agent/v1/app/install', + headers=self.headers, + json=data) + if r.status_code in SUCCESS_RESP: + logger.info('App installed') + return OK + logger.error('Failed to install the IPA. %s', r.json()['error']) + return r.json()['error'] + + def run_app(self, bundle_id): + """Run an App.""" + r = requests.post( + (f'{self.api}/instances/{self.instance_id}' + f'/agent/v1/app/apps/{bundle_id}/run'), + headers=self.headers) + if r.status_code in SUCCESS_RESP: + logger.info('App Started') + return OK + logger.error('Failed to start the app. %s', r.json()['error']) + return r.json()['error'] + + def stop_app(self, bundle_id): + """Stop an App.""" + r = requests.post( + (f'{self.api}/instances/{self.instance_id}' + f'/agent/v1/app/apps/{bundle_id}/kill'), + headers=self.headers) + if r.status_code in SUCCESS_RESP: + logger.info('App Killed') + return OK + logger.error('Failed to stop the app. %s', r.json()['error']) + return r.json()['error'] + + def remove_app(self, bundle_id): + """Remove an app from VM.""" + r = requests.post( + (f'{self.api}/instances/{self.instance_id}' + f'/agent/v1/app/apps/{bundle_id}/uninstall'), + headers=self.headers) + if r.status_code in SUCCESS_RESP: + logger.info('App Removed') + return OK + logger.error('Failed to remove the app. %s', r.json()['error']) + return r.json()['error'] + + def list_apps(self): + """List all apps installed.""" + r = requests.get( + f'{self.api}/instances/{self.instance_id}/agent/v1/app/apps', + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return r.json() + elif r.status_code in ERROR_RESP: + return r.json() + return False + + def get_icons(self, bundleids): + """Get app icons by bundleId.""" + r = requests.get( + (f'{self.api}/instances/{self.instance_id}' + f'/agent/v1/app/icons?{bundleids}'), + headers=self.headers) + if r.status_code in SUCCESS_RESP: + return r.json() + elif r.status_code in ERROR_RESP: + return r.json() + return False diff --git a/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py b/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py new file mode 100644 index 0000000000..254f87fe5a --- /dev/null +++ b/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py @@ -0,0 +1,839 @@ +# -*- coding: utf_8 -*- +"""Instance Operation APIs.""" +import logging +import re +import shutil +import time +from base64 import b64encode +from pathlib import Path + +from paramiko.ssh_exception import SSHException + +from django.conf import settings +from django.views.decorators.http import require_http_methods +from django.http import ( + HttpResponse, +) +from django.shortcuts import ( + render, +) + +from mobsf.MobSF.utils import ( + common_check, + get_md5, + id_generator, + is_md5, + is_number, + print_n_send_error_response, + strict_package_check, +) +from mobsf.DynamicAnalyzer.forms import UploadFileForm +from mobsf.DynamicAnalyzer.tools.webproxy import ( + get_http_tools_url, + stop_httptools, +) +from mobsf.DynamicAnalyzer.views.common.shared import ( + invalid_params, + send_response, +) +from mobsf.DynamicAnalyzer.views.ios.corellium_ssh import ( + ssh_execute_cmd, + ssh_file_download, + ssh_file_upload, + ssh_jump_host, +) +from mobsf.DynamicAnalyzer.views.ios.corellium_apis import ( + CorelliumAPI, + CorelliumAgentAPI, + CorelliumInstanceAPI, + CorelliumModelsAPI, + OK, +) + + +logger = logging.getLogger(__name__) + + +# AJAX + + +@require_http_methods(['POST']) +def start_instance(request, api=False): + """Start iOS VM instance.""" + logger.info('Starting iOS VM instance') + data = { + 'status': 'failed', + 'message': 'Failed to start instance'} + try: + instance_id = request.POST['instance_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ci = CorelliumInstanceAPI(instance_id) + r = ci.start_instance() + if r == OK: + data = { + 'status': OK, + 'message': 'Starting VM Instance'} + elif r: + data['message'] = r + except Exception as exp: + logger.exception('Start iOS VM instance') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def stop_instance(request, api=False): + """Stop iOS VM instance.""" + logger.info('Stopping iOS VM instance') + data = { + 'status': 'failed', + 'message': 'Failed to stop instance'} + try: + instance_id = request.POST['instance_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ci = CorelliumInstanceAPI(instance_id) + r = ci.stop_instance() + if r == OK: + data = { + 'status': OK, + 'message': 'Instance stopped'} + elif r: + data['message'] = r + except Exception as exp: + logger.exception('Stopping iOS VM instance') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def unpause_instance(request, api=False): + """Unpause iOS VM instance.""" + logger.info('Unpausing iOS VM instance') + data = { + 'status': 'failed', + 'message': 'Failed to unpause instance'} + try: + instance_id = request.POST['instance_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ci = CorelliumInstanceAPI(instance_id) + r = ci.unpause_instance() + if r == OK: + data = { + 'status': OK, + 'message': 'Instance unpaused'} + elif r: + data['message'] = r + except Exception as exp: + logger.exception('Unpausing iOS VM instance') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def reboot_instance(request, api=False): + """Reboot iOS VM instance.""" + logger.info('Rebooting iOS VM instance') + data = { + 'status': 'failed', + 'message': 'Failed to reboot instance'} + try: + instance_id = request.POST['instance_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ci = CorelliumInstanceAPI(instance_id) + r = ci.reboot_instance() + if r == OK: + data = { + 'status': OK, + 'message': 'Rebooting instance'} + elif r: + data['message'] = r + except Exception as exp: + logger.exception('Rebooting iOS VM instance') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def destroy_instance(request, api=False): + """Destroy iOS VM instance.""" + logger.info('Destroying iOS VM instance') + data = { + 'status': 'failed', + 'message': 'Failed to destroy instance'} + try: + instance_id = request.POST['instance_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ci = CorelliumInstanceAPI(instance_id) + r = ci.remove_instance() + if r == OK: + data = { + 'status': OK, + 'message': 'Destroying instance'} + elif r: + data['message'] = r + except Exception as exp: + logger.exception('Destroying iOS VM instance') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def list_apps(request, api=False): + """List installed apps.""" + logger.info('Listing installed applications') + data = { + 'status': 'failed', + 'message': 'Failed to list installed apps'} + try: + instance_id = request.POST['instance_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ca = CorelliumAgentAPI(instance_id) + # Get apps in device + r = ca.list_apps() + app_list = [] + bundle_ids = [] + if r and r.get('apps'): + for i in r.get('apps'): + bundle = i['bundleID'] + bundle_ids.append(f'bundleID={bundle}') + elif r and r.get('error'): + data['message'] = r.get('error') + return send_response(failed, api) + else: + data['message'] = 'Failed to list apps' + return send_response(failed, api) + # Get app icons + logger.info('Getting all application icons') + ic = ca.get_icons('&'.join(bundle_ids)) + for i in r.get('apps'): + bundleid = i['bundleID'] + checksum = get_md5(bundleid.encode('utf-8')) + dump_file = Path( + settings.UPLD_DIR) / checksum / 'mobsf_dump_file.txt' + if ic and ic.get('icons'): + icon_url = ic['icons'].get(bundleid) + else: + icon_url = '' + app_list.append({ + 'applicationType': i['applicationType'], + 'name': i['name'], + 'bundleID': bundleid, + 'icon': icon_url, + 'checksum': checksum, + 'reportExists': dump_file.exists(), + }) + data = { + 'status': OK, + 'message': app_list} + + except Exception as exp: + logger.exception('Listing installed apps') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def get_supported_models(request, api=False): + """Get Supported iOS VM models.""" + data = { + 'status': 'failed', + 'message': 'Failed to obtain iOS models'} + try: + cm = CorelliumModelsAPI() + r = cm.get_models() + if r: + data = {'status': OK, 'message': r} + except Exception as exp: + logger.exception('Obtaining iOS models') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def get_supported_os(request, api=False): + """Get Supported iOS OS versions.""" + data = { + 'status': 'failed', + 'message': 'Failed to obtain iOS versions'} + try: + model = request.POST['model'] + cm = CorelliumModelsAPI() + r = cm.get_supported_os(model) + if r: + data = {'status': OK, 'message': r} + except Exception as exp: + logger.exception('Obtaining iOS versions') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def create_vm_instance(request, api=False): + """Create and iOS VM in Corellium.""" + logger.info('Creating Corellium iOS VM instance') + data = { + 'status': 'failed', + 'message': 'Failed to create an iOS VM'} + try: + project_id = request.POST['project_id'] + flavor = request.POST['flavor'] + version = request.POST['version'] + if not re.match(r'^iphone\d*\w+', flavor): + data['message'] = 'Invalid iOS flavor' + return send_response(data, api) + if not re.match(r'^\d+\.\d+\.*\d*', version): + data['message'] = 'Invalid iOS version' + return send_response(data, api) + failed = common_check(project_id) + if failed: + return send_response(failed, api) + c = CorelliumAPI(project_id) + r = c.create_ios_instance(flavor, version) + if r: + data = { + 'status': OK, + 'message': f'Created a new instance with id: {r}'} + except Exception as exp: + logger.exception('Creating Corellium iOS VM') + data['message'] = str(exp) + return send_response(data, api) +# Helpers for AppSync Install Check & IPA Install + + +def check_appsync(target): + """Check and install AppSync Unified.""" + check_install = 'apt list --installed | grep \'ai.akemi.appinst\'' + # Check if AppSync Unified is installed + out = ssh_execute_cmd(target, check_install) + if 'ai.akemi.appinst' not in out: + # Install AppSync Unified + logger.info('AppSync Unified is not installed. ' + 'Attempting to install...') + src_file = '/etc/apt/sources.list.d/cydia.list' + src = 'deb https://cydia.akemi.ai/ ./' + install_cmds = [ + f'grep -qxF \'{src}\' {src_file} || echo \'{src}\' >> {src_file}', + 'apt update', + 'apt install -y --allow-unauthenticated ai.akemi.appinst', + 'launchctl reboot userspace', + ] + for i in install_cmds: + out = ssh_execute_cmd(target, i) + logger.info(out) + logger.info('Please wait for 15 seconds for the userspace to reboot.') + time.sleep(15) + + +def appsync_ipa_install(ssh_string): + """Install app using AppSync Unified.""" + target, jumpbox = ssh_jump_host(ssh_string) + # AppSync Unified install check + check_appsync(target) # This will terminate SSH session + if target: + target.close() + if jumpbox: + jumpbox.close() + # Install IPA with AppSync United + logger.info('Attempting to install the IPA ' + 'using AppSync Unified.') + target, jumpbox = ssh_jump_host(ssh_string) + out = ssh_execute_cmd(target, 'appinst /tmp/app.ipa') + target.close() + jumpbox.close() + if 'Failed' in out: + logger.error('AppSync IPA Install Failed.\n%s', out) + return out + logger.info(out) + return OK +# AJAX + + +@require_http_methods(['POST']) +def setup_environment(request, checksum, api=False): + """Setup iOS Dynamic Analyzer Environment.""" + data = { + 'status': 'failed', + 'message': 'Failed to Setup Dynamic Analysis Environment'} + try: + if not is_md5(checksum): + # Additional Check for REST API + data['message'] = 'Invalid Hash' + return send_response(data, api) + instance_id = request.POST['instance_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ca = CorelliumAgentAPI(instance_id) + if not ca.agent_ready(): + data['message'] = ( + f'Agent is not ready with {instance_id}' + ', please wait.') + return send_response(data, api) + # Unlock iOS Device + ca.unlock_device() + # Upload IPA + ipa_path = Path(settings.UPLD_DIR) / checksum / f'{checksum}.ipa' + msg = ca.upload_ipa(ipa_path) + if msg != OK: + data['message'] = msg + return send_response(data, api) + # Install IPA + msg = ca.install_ipa() + if msg != OK: + if 'Please re-sign.' in msg: + # Try AppSync IPA Install + ci = CorelliumInstanceAPI(instance_id) + out = appsync_ipa_install(ci.get_ssh_connection_string()) + if out and out != OK: + data['message'] = out + return send_response(data, api) + else: + # Other install errors + data['message'] = msg + return send_response(data, api) + msg = 'Testing Environment is Ready!' + logger.info(msg) + data['status'] = OK + data['message'] = msg + except Exception as exp: + logger.exception('Creating iOS Dynamic Analyzer Environment') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def run_app(request, api=False): + """Run an App.""" + data = { + 'status': 'failed', + 'message': 'Failed to run the app'} + try: + instance_id = request.POST['instance_id'] + bundle_id = request.POST['bundle_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + if not strict_package_check(bundle_id): + data['message'] = 'Invalid iOS Bundle id' + return send_response(data, api) + ca = CorelliumAgentAPI(instance_id) + if (ca.agent_ready() + and ca.unlock_device() + and ca.run_app(bundle_id) == OK): + data['status'] = OK + data['message'] = 'App Started' + except Exception as exp: + logger.exception('Failed to run the app') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def remove_app(request, api=False): + """Remove an app from the device.""" + data = { + 'status': 'failed', + 'message': 'Failed to uninstall the app'} + try: + instance_id = request.POST['instance_id'] + bundle_id = request.POST['bundle_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + if not strict_package_check(bundle_id): + data['message'] = 'Invalid iOS Bundle id' + return send_response(data, api) + ca = CorelliumAgentAPI(instance_id) + if (ca.agent_ready() + and ca.remove_app(bundle_id) == OK): + data['status'] = OK + data['message'] = 'App uninstalled' + except Exception as exp: + logger.exception('Failed to uninstall the app') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def take_screenshot(request, api=False): + """Take a Screenshot.""" + data = { + 'status': 'failed', + 'message': 'Failed to take screenshot'} + try: + instance_id = request.POST['instance_id'] + save = request.POST.get('save') + checksum = request.POST.get('checksum') + dwd = Path(settings.DWD_DIR) + if save and checksum: + if not is_md5(checksum): + data['message'] = 'Invaid MD5 Hash' + return send_response(data, api) + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ci = CorelliumInstanceAPI(instance_id) + r = ci.screenshot() + if r: + data['status'] = OK + if save == '1': + sfile = dwd / f'{checksum}-sshot-{id_generator()}.png' + sfile.write_bytes(r) + data['message'] = 'Screenshot saved!' + else: + b64dat = b64encode(r).decode('utf-8') + data['message'] = f'data:image/png;base64,{b64dat}' + except Exception as exp: + logger.exception('Failed to take screenshot') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def get_container_path(request, api=False): + """Get App Container path.""" + err_msg = 'Failed to get app container path' + data = { + 'status': 'failed', + 'message': err_msg} + try: + bundle_id = request.POST['bundle_id'] + if not strict_package_check(bundle_id): + data['message'] = 'Invalid iOS Bundle id' + return send_response(data, api) + checksum = get_md5(bundle_id.encode('utf-8')) + cfile = 'mobsf_app_container_path.txt' + acfile = Path(settings.UPLD_DIR) / checksum / cfile + if acfile.exists(): + data['status'] = OK + data['message'] = acfile.read_text( + 'utf-8').splitlines()[0].strip() + except Exception as exp: + logger.exception(err_msg) + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def network_capture(request, api=False): + """Enable/Disable Network Capture.""" + data = { + 'status': 'failed', + 'message': 'Failed to enable/disable network capture'} + try: + instance_id = request.POST['instance_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ci = CorelliumInstanceAPI(instance_id) + state = request.POST.get('state') + if state == 'on': + msg = 'Enabled' + logger.info('Enabling Network Capture') + r = ci.start_network_capture() + else: + msg = 'Disabled' + logger.info('Disabling Network Capture') + r = ci.stop_network_capture() + if r != OK: + data['message'] = r + return send_response(data, api) + else: + data = { + 'status': OK, + 'message': f'{msg} network capture'} + except Exception as exp: + logger.exception('Enabling/Disabling network capture') + data['message'] = str(exp) + return send_response(data, api) +# File Download + + +@require_http_methods(['GET']) +def live_pcap_download(request, api=False): + """Download Network Capture.""" + data = { + 'status': 'failed', + 'message': 'Failed to download network capture'} + try: + instance_id = request.GET['instance_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ci = CorelliumInstanceAPI(instance_id) + pcap = ci.download_network_capture() + if pcap: + res = HttpResponse( + pcap, + content_type='application/vnd.tcpdump.pcap') + res['Content-Disposition'] = ( + f'inline; filename={instance_id}-network.pcap') + return res + else: + data['message'] = 'Failed to download pcap' + except Exception as exp: + logger.exception('Download network capture') + data['message'] = str(exp) + return send_response(data, api) + + +# AJAX +SSH_TARGET = None + + +@require_http_methods(['POST']) +def ssh_execute(request, api=False): + """Execute commands in VM over SSH.""" + global SSH_TARGET + res = '' + data = { + 'status': 'failed', + 'message': 'Failed to execute command'} + try: + instance_id = request.POST['instance_id'] + cmd = request.POST.get('cmd') + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ci = CorelliumInstanceAPI(instance_id) + if not SSH_TARGET: + logger.info('Setting up SSH tunnel') + SSH_TARGET, _jmp = ssh_jump_host( + ci.get_ssh_connection_string()) + try: + res = ssh_execute_cmd(SSH_TARGET, cmd) + except SSHException: + logger.info('SSH session not active, setting up again') + SSH_TARGET, _jmp = ssh_jump_host( + ci.get_ssh_connection_string()) + res = ssh_execute_cmd(SSH_TARGET, cmd) + data = {'status': OK, 'message': res} + except Exception as exp: + data['message'] = str(exp) + logger.exception('Executing Commands') + return send_response(data, api) +# Helper Download app data tarfile + + +def download_app_data(ci, checksum): + """Download App data from device.""" + app_dir = Path(settings.UPLD_DIR) / checksum + container_file = app_dir / 'mobsf_app_container_path.txt' + if container_file.exists(): + app_container = container_file.read_text( + 'utf-8').splitlines()[0].strip() + target, jumpbox = ssh_jump_host( + ci.get_ssh_connection_string()) + tarfile = f'/tmp/{checksum}-app-container.tar' + localtar = app_dir / f'{checksum}-app-container.tar' + ssh_execute_cmd( + target, f'tar -C {app_container} -cvf {tarfile} .') + with target.open_sftp() as sftp: + sftp.get(tarfile, localtar) + target.close() + jumpbox.close() + if localtar.exists(): + dst = Path(settings.DWD_DIR) / f'{checksum}-app_data.tar' + shutil.copyfile(localtar, dst) +# AJAX + + +@require_http_methods(['POST']) +def download_data(request, bundle_id, api=False): + """Download Application Data from Device.""" + logger.info('Downloading application data') + data = { + 'status': 'failed', + 'message': 'Failed to Download application data'} + try: + instance_id = request.POST['instance_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + if not strict_package_check(bundle_id): + data['message'] = 'Invalid iOS Bundle id' + return send_response(data, api) + ci = CorelliumInstanceAPI(instance_id) + checksum = get_md5(bundle_id.encode('utf-8')) + # App Container download + logger.info('Downloading app container data') + download_app_data(ci, checksum) + # Stop HTTPS Proxy + stop_httptools(get_http_tools_url(request)) + # Move HTTP raw logs to download directory + flows = Path.home() / '.httptools' / 'flows' + webf = flows / f'{bundle_id}.flow.txt' + dwd = Path(settings.DWD_DIR) + dweb = dwd / f'{checksum}-web_traffic.txt' + if webf.exists(): + shutil.copyfile(webf, dweb) + # Pcap download + logger.info('Downloading network capture') + pcap = ci.download_network_capture() + if pcap: + dwd = Path(settings.DWD_DIR) + pcap_file = dwd / f'{checksum}-network.pcap' + pcap_file.write_bytes(pcap) + data = { + 'status': OK, + 'message': 'Downloaded application data', + } + else: + data['message'] = 'Failed to download pcap' + return send_response(data, api) + except Exception as exp: + logger.exception('Downloading application data') + data['message'] = str(exp) + return send_response(data, api) +# AJAX + + +@require_http_methods(['POST']) +def touch(request, api=False): + """Sending Touch Events.""" + data = { + 'status': 'failed', + 'message': '', + } + try: + x_axis = request.POST['x'] + y_axis = request.POST['y'] + event = request.POST['event'] + instance_id = request.POST['instance_id'] + if not is_number(x_axis) and not is_number(y_axis): + logger.error('Axis parameters must be numbers') + return invalid_params() + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ci = CorelliumInstanceAPI(instance_id) + ci.device_input(event, x_axis, y_axis) + data = {'status': 'ok'} + except Exception as exp: + logger.exception('Sending Touch Events') + data['message'] = str(exp) + return send_response(data) +# AJAX + HTML + + +@require_http_methods(['POST', 'GET']) +def system_logs(request, api=False): + """Show system logs.""" + data = { + 'status': 'failed', + 'message': 'Failed to get system logs', + } + try: + if request.method == 'POST': + instance_id = request.POST['instance_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ci = CorelliumInstanceAPI(instance_id) + data = {'status': 'ok', 'message': ci.console_log()} + return send_response(data) + logger.info('Getting system logs') + instance_id = request.GET['instance_id'] + failed = common_check(instance_id) + if failed: + return print_n_send_error_response( + request, failed['message'], api) + template = 'dynamic_analysis/ios/system_logs.html' + return render(request, + template, + {'instance_id': instance_id, + 'version': settings.MOBSF_VER, + 'title': 'Live System logs'}) + except Exception as exp: + err = 'Getting system logs' + logger.exception(err) + if request.method == 'POST': + data['message'] = str(exp) + return send_response(data) + return print_n_send_error_response(request, err, api) +# AJAX + + +@require_http_methods(['POST']) +def upload_file(request, api=False): + """Upload file to device.""" + err_msg = 'Failed to upload file' + data = { + 'status': 'failed', + 'message': err_msg, + } + try: + instance_id = request.POST['instance_id'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + form = UploadFileForm(request.POST, request.FILES) + if form.is_valid(): + ci = CorelliumInstanceAPI(instance_id) + fobject = request.FILES['file'] + ssh_file_upload( + ci.get_ssh_connection_string(), + fobject, + fobject.name) + data = {'status': 'ok'} + except Exception as exp: + logger.exception(err_msg) + data['message'] = str(exp) + return send_response(data) +# File Download + + +@require_http_methods(['POST']) +def download_file(request, api=False): + """Download file from device.""" + try: + global SSH_TARGET + instance_id = request.POST['instance_id'] + rfile = request.POST['file'] + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + ci = CorelliumInstanceAPI(instance_id) + if not SSH_TARGET: + logger.info('Setting up SSH tunnel') + SSH_TARGET, _jmp = ssh_jump_host( + ci.get_ssh_connection_string()) + try: + fl = ssh_file_download(SSH_TARGET, rfile) + except SSHException: + logger.info('SSH session not active, setting up again') + SSH_TARGET, _jmp = ssh_jump_host( + ci.get_ssh_connection_string()) + fl = ssh_file_download(SSH_TARGET, rfile) + if not fl: + fl = b'File not found' + except Exception: + logger.exception('Failed to download file') + response = HttpResponse(fl, content_type='application/octet-stream') + response['Content-Disposition'] = f'inline; filename={Path(rfile).name}' + return response diff --git a/mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py b/mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py new file mode 100644 index 0000000000..b768fe37ed --- /dev/null +++ b/mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py @@ -0,0 +1,294 @@ +# -*- coding: utf_8 -*- +"""Corellium SSH. + +Corellium SSH Utilities , modified for MobSF. +Supports SSH over Jump Host +Local Port Forward +Remote Port Forward +SSH Shell Exec +SFTP File Upload +SFTP File Download +""" +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# Modified for MobSF. +import io +import logging +import select +import socket +import socketserver +from threading import Thread +from pathlib import Path + +import paramiko + +from django.conf import settings + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + + +logger = logging.getLogger(__name__) + + +def generate_keypair_if_not_exists(location): + """Generate RSA key pair.""" + prv = location / 'ssh_key.private' + pub = location / 'ssh_key.public' + if prv.exists() and pub.exists(): + # Keys Exists + return prv.read_bytes(), pub.read_bytes() + logger.info('Generating RSA key pair for Corellium SSH') + + # Generate private/public key pair + private_key = Ed25519PrivateKey.generate() + public_key = private_key.public_key() + + # OpenSSH friendly + private_bytes = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.OpenSSH, + encryption_algorithm=serialization.NoEncryption()) + + public_bytes = public_key.public_bytes( + encoding=serialization.Encoding.OpenSSH, + format=serialization.PublicFormat.OpenSSH) + prv.write_bytes(private_bytes) + pub.write_bytes(public_bytes) + return private_bytes, public_bytes + + +def parse_ssh_string(ssh): + """Parse SSH connection string.""" + ssh_dict = {} + sp = ssh.split(' ') + bastion = sp[2] + private = sp[3] + ssh_dict['bastion_user'] = bastion.split('@')[0] + ssh_dict['bastion_host'] = bastion.split('@')[1] + ssh_dict['private_user'] = private.split('@')[0] + ssh_dict['private_ip'] = private.split('@')[1] + return ssh_dict + + +def sock_chan_handler(sock, chan): + """Socket and Channel Handler.""" + try: + while True: + r, w, x = select.select([sock, chan], [], []) + if sock in r: + data = sock.recv(1024) + if len(data) == 0: + break + chan.send(data) + if chan in r: + data = chan.recv(1024) + if len(data) == 0: + break + sock.send(data) + except ConnectionResetError: + pass + finally: + if chan: + chan.close() + if sock: + sock.close() + + +# Local Port Forward +class ForwardServer(socketserver.ThreadingTCPServer): + daemon_threads = True + allow_reuse_address = True + + +class Handler(socketserver.BaseRequestHandler): + def handle(self): + chan = None + sock = self.request + try: + chan = self.ssh_transport.open_channel( + 'direct-tcpip', + (self.chain_host, self.chain_port), + sock.getpeername(), + ) + except paramiko.SSHException: + # SSH tunnel closed, try opening again + ssh_jumphost_port_forward(self.ssh_string) + except Exception as e: + logger.info( + 'Incoming request to %s:%d failed: %s', + self.chain_host, self.chain_port, repr(e)) + return + if chan is None: + logger.info( + 'Incoming request to %s:%d was rejected by the SSH server.', + self.chain_host, self.chain_port) + return + + logger.info( + 'Connected! Tunnel open %r -> %r -> %r', + sock.getpeername(), + chan.getpeername(), + (self.chain_host, self.chain_port)) + peername = sock.getpeername() + sock_chan_handler(sock, chan) + logger.info('Tunnel closed from %r', peername) + + +def forward_tunnel(local_port, remote_host, remote_port, transport, ssh): + # this is a little convoluted, but lets me configure things for the Handler + # object. (SocketServer doesn't give Handlers any way to access the outer + # server normally.) + class SubHander(Handler): + chain_host = remote_host + chain_port = remote_port + ssh_transport = transport + ssh_string = ssh + + try: + ForwardServer(('', local_port), SubHander).serve_forever() + except OSError: + logger.info('Port Forwarding Already in place') + + +# Remote Port Forward +def handler(chan, host, port): + sock = socket.socket() + try: + sock.connect((host, port)) + except ConnectionRefusedError: + # Proxy server is stopped + return + except Exception: + logger.info('Forwarding request to %s:%d failed', host, port) + return + sock_chan_handler(sock, chan) + + +def reverse_forward_tunnel(server_port, remote_host, remote_port, transport): + try: + transport.request_port_forward('', server_port) + while True: + chan = transport.accept(1000) + if chan is None: + continue + Thread( + target=handler, + args=(chan, remote_host, remote_port), + daemon=True).start() + except paramiko.SSHException as exp: + if 'forwarding request denied' in str(exp): + # Handle TCP forwarding request denied + # Happens if already forwarding port + pass + else: + logger.exception('SSH Remote Port Forward Exception') + + +def ssh_jump_host(ssh_string): + """Connect to SSH over a bastion.""" + ssh_dict = parse_ssh_string(ssh_string) + bastion_user = ssh_dict['bastion_user'] + bastion_host = ssh_dict['bastion_host'] + user = ssh_dict['private_user'] + private_ip = ssh_dict['private_ip'] + + home = Path(settings.UPLD_DIR).parent + generate_keypair_if_not_exists(home) + keyf = home / 'ssh_key.private' + jumpbox = paramiko.SSHClient() + jumpbox.load_system_host_keys() + jumpbox.set_missing_host_key_policy(paramiko.WarningPolicy()) + jumpbox.connect( + bastion_host, + username=bastion_user, + key_filename=keyf.as_posix()) + + jumpbox_transport = jumpbox.get_transport() + src_addr = (private_ip, 22) + dest_addr = (private_ip, 22) + jumpbox_channel = jumpbox_transport.open_channel( + 'direct-tcpip', dest_addr, src_addr) + + target = paramiko.SSHClient() + target.load_system_host_keys() + target.set_missing_host_key_policy(paramiko.WarningPolicy()) + target.connect( + private_ip, + username=user, + sock=jumpbox_channel, + password='alpine') + return target, jumpbox + + +def ssh_jumphost_port_forward(ssh_string): + """SSH over Jump Host and Local Port Forward.""" + target, _jumpbox = ssh_jump_host(ssh_string) + # Frida port + forward_port = 27042 + remote_host = '127.0.0.1' + forward_tunnel( + forward_port, + remote_host, + forward_port, + target.get_transport(), + ssh_string) + + +def ssh_jumphost_reverse_port_forward(ssh_string): + """SSH over Jump Host and Remote Port Forward.""" + target, _jumpbox = ssh_jump_host(ssh_string) + # HTTPS proxy port + port = settings.PROXY_PORT + remote_host = '127.0.0.1' + reverse_forward_tunnel( + port, + remote_host, + port, + target.get_transport(), + ) + + +def ssh_execute_cmd(target, cmd): + """Execute SSH command.""" + _stdin, _stdout, _stderr = target.exec_command(cmd) + stdout = _stdout.read().decode(encoding='utf-8', errors='ignore') + stderr = _stderr.read().decode(encoding='utf-8', errors='ignore') + return f'{stdout}\n{stderr}' + + +def ssh_file_upload(ssh_conn_string, fobject, fname): + """File Upload over SFTP.""" + target, jumpbox = ssh_jump_host(ssh_conn_string) + with target.open_sftp() as sftp: + rfile = Path(fname.replace('..', '')).name + sftp.putfo(fobject, f'/tmp/{rfile}') + target.close() + jumpbox.close() + + +def ssh_file_download(target, remote_path): + """File Download over SFTP.""" + try: + with io.BytesIO() as fl: + with target.open_sftp() as sftp: + sftp.getfo(remote_path, fl) + fl.seek(0) + return fl.read() + except Exception: + return None diff --git a/mobsf/DynamicAnalyzer/views/ios/dynamic_analyzer.py b/mobsf/DynamicAnalyzer/views/ios/dynamic_analyzer.py new file mode 100644 index 0000000000..9e2a0c94a5 --- /dev/null +++ b/mobsf/DynamicAnalyzer/views/ios/dynamic_analyzer.py @@ -0,0 +1,179 @@ +# -*- coding: utf_8 -*- +"""iOS Dynamic Analysis.""" +import logging +import os +from pathlib import Path +from threading import Thread + +from django.conf import settings +from django.shortcuts import render + +from mobsf.MobSF.utils import ( + common_check, + get_md5, + print_n_send_error_response, + python_dict, + strict_package_check, +) +from mobsf.StaticAnalyzer.models import StaticAnalyzerIOS +from mobsf.DynamicAnalyzer.forms import UploadFileForm +from mobsf.DynamicAnalyzer.views.ios.corellium_ssh import ( + generate_keypair_if_not_exists, +) +from mobsf.DynamicAnalyzer.tools.webproxy import ( + get_http_tools_url, + start_proxy, + stop_httptools, +) +from mobsf.DynamicAnalyzer.views.ios.corellium_apis import ( + CorelliumAPI, + CorelliumInstanceAPI, +) +from mobsf.DynamicAnalyzer.views.ios.corellium_ssh import ( + ssh_jumphost_reverse_port_forward, +) + +logger = logging.getLogger(__name__) + + +def dynamic_analysis(request, api=False): + """The iOS Dynamic Analysis Entry point.""" + try: + scan_apps = [] + ipas = StaticAnalyzerIOS.objects.filter( + FILE_NAME__endswith='.ipa') + for ipa in reversed(ipas): + bundle_hash = get_md5(ipa.BUNDLE_ID.encode('utf-8')) + frida_dump = Path( + settings.UPLD_DIR) / bundle_hash / 'mobsf_dump_file.txt' + macho = python_dict(ipa.MACHO_ANALYSIS) + encrypted = False + if (macho + and macho.get('encrypted') + and macho.get('encrypted').get('is_encrypted')): + encrypted = macho['encrypted']['is_encrypted'] + temp_dict = { + 'MD5': ipa.MD5, + 'APP_NAME': ipa.APP_NAME, + 'APP_VERSION': ipa.APP_VERSION, + 'FILE_NAME': ipa.FILE_NAME, + 'BUNDLE_ID': ipa.BUNDLE_ID, + 'BUNDLE_HASH': bundle_hash, + 'ENCRYPTED': encrypted, + 'DYNAMIC_REPORT_EXISTS': frida_dump.exists(), + } + scan_apps.append(temp_dict) + # Corellium + instances = [] + project_id = None + c = CorelliumAPI(getattr(settings, 'CORELLIUM_PROJECT_ID', '')) + corellium_auth = c.api_ready() and c.api_auth() + if corellium_auth and c.get_projects(): + instances = c.get_instances() + project_id = c.project_id + setup_ssh_keys(c) + context = {'apps': scan_apps, + 'dynamic_analyzer': corellium_auth, + 'project_id': project_id, + 'instances': instances, + 'title': 'MobSF Dynamic Analysis', + 'version': settings.MOBSF_VER} + if api: + return context + template = 'dynamic_analysis/ios/dynamic_analysis.html' + return render(request, template, context) + except Exception as exp: + logger.exception('iOS Dynamic Analysis') + return print_n_send_error_response(request, exp, api) + + +def dynamic_analyzer(request, api=False): + """Dynamic Analyzer for in-device iOS apps.""" + try: + bundleid = request.GET.get('bundleid') + if not bundleid or not strict_package_check(bundleid): + return print_n_send_error_response( + request, + 'Invalid iOS Bundle id', + api) + instance_id = request.GET.get('instance_id') + failed = common_check(instance_id) + if failed: + return print_n_send_error_response( + request, + failed['message'], + api) + bundle_hash = get_md5(bundleid.encode('utf-8')) + app_dir = Path(settings.UPLD_DIR) / bundle_hash + if not app_dir.exists(): + app_dir.mkdir() + ci = CorelliumInstanceAPI(instance_id) + configure_proxy(request, bundleid, ci) + context = { + 'hash': bundle_hash, + 'instance_id': instance_id, + 'bundle_id': bundleid, + 'version': settings.MOBSF_VER, + 'form': UploadFileForm(), + 'title': 'iOS Dynamic Analyzer'} + template = 'dynamic_analysis/ios/dynamic_analyzer.html' + if api: + return context + return render(request, template, context) + except Exception: + logger.exception('iOS Dynamic Analyzer') + return print_n_send_error_response( + request, + 'iOS Dynamic Analysis Failed.', + api) + + +def setup_ssh_keys(c): + # Get Authorized keys for the project + pkeys = c.get_authorized_keys() + location = Path(settings.UPLD_DIR).parent + _prv, pub = generate_keypair_if_not_exists(location) + add_keys = False + is_docker = os.getenv('MOBSF_PLATFORM') == 'docker' + if not pkeys: + # No SSH Keys associated with the project + # let's add one + add_keys = True + else: + # SSH Keys are already associated with the project + # Check if our key is associated + pub_key_exists = False + for pkey in pkeys: + if pkey['project'] == c.project_id: + rkey = get_md5(pkey['key'].encode('utf-8')) + if rkey == get_md5(pub): + pub_key_exists = True + break + if is_docker and pkey['label'].endswith('(docker)'): + # Delete all docker generated keys + # This is done to avoid multiple stale keys being + # added on each run. + logger.info('Removing old stale SSH public key') + c.delete_authorized_key(pkey['identifier']) + # Our key is not asscoiated with the project, let's add it + if not pub_key_exists: + add_keys = True + if add_keys: + iden = c.add_authorized_key(pub) + if not iden: + logger.error('Failed to add SSH Key to Corellium project') + return + logger.info('Added SSH Key to Corellium project') + + +def configure_proxy(request, project, ci): + """Configure HTTPS Proxy.""" + proxy_port = settings.PROXY_PORT + logger.info('Starting HTTPS Proxy on %s', proxy_port) + stop_httptools(get_http_tools_url(request)) + start_proxy(proxy_port, project) + # Remote Port forward for HTTPS Proxy + logger.info('Starting Remote Port Forward over SSH') + Thread(target=ssh_jumphost_reverse_port_forward, + args=(ci.get_ssh_connection_string(),), + daemon=True).start() diff --git a/mobsf/DynamicAnalyzer/views/ios/frida_auxiliary_scripts.py b/mobsf/DynamicAnalyzer/views/ios/frida_auxiliary_scripts.py new file mode 100644 index 0000000000..4030d50a1e --- /dev/null +++ b/mobsf/DynamicAnalyzer/views/ios/frida_auxiliary_scripts.py @@ -0,0 +1,69 @@ +from pathlib import Path + +from django.conf import settings + +from mobsf.MobSF.utils import ( + strict_ios_class, +) + + +def get_content(file_name): + tools_dir = Path(settings.TOOLS_DIR) + aux_dir = tools_dir / 'frida_scripts' / 'ios' / 'auxiliary' + script = aux_dir / file_name + + if script.exists(): + return script.read_text('utf-8', 'ignore') + return '' + + +def get_loaded_classes(): + """Get Loaded classes.""" + return get_content('find-app-classes.js') + + +def get_loaded_classes_methods(): + """Get Loaded classes and methods.""" + return get_content('find-app-classes-methods.js') + + +def string_capture(): + """Capture all runtime strings.""" + return get_content('string-capture.js') + + +def string_compare(): + """Capture all runtime string comparisons.""" + return get_content('string-compare.js') + + +def get_methods(klazz): + """Get Class methods and implementations.""" + if not strict_ios_class(klazz): + return '' + content = get_content('get-methods.js') + return content.replace('{{CLASS}}', klazz) + + +def classes_with_method(method): + """Get all classes containing the method.""" + if not strict_ios_class(method): + return '' + content = get_content('find-specific-method.js') + return content.replace('{{METHOD}}', method) + + +def class_pattern(pattern): + """Search in loaded classes based on pattern.""" + pattern = pattern.replace( + '/', '\\/').replace(';', '') + content = get_content('search-class-pattern.js') + return content.replace('{{PATTERN}}', pattern) + + +def class_trace(class_name): + """Trace all methods of a class.""" + if not strict_ios_class(class_name): + return '' + content = get_content('class-trace.js') + return content.replace('{{CLASS}}', class_name) diff --git a/mobsf/DynamicAnalyzer/views/ios/frida_core.py b/mobsf/DynamicAnalyzer/views/ios/frida_core.py new file mode 100644 index 0000000000..a08b438f3b --- /dev/null +++ b/mobsf/DynamicAnalyzer/views/ios/frida_core.py @@ -0,0 +1,262 @@ +import logging +from threading import Thread +from pathlib import Path +import sys +import time + +from django.conf import settings + +import frida + +from mobsf.DynamicAnalyzer.views.ios.frida_auxiliary_scripts import ( + class_pattern, + class_trace, + classes_with_method, + get_loaded_classes, + get_loaded_classes_methods, + get_methods, + string_capture, + string_compare, +) +from mobsf.DynamicAnalyzer.views.ios.corellium_ssh import ( + ssh_jumphost_port_forward, +) + + +logger = logging.getLogger(__name__) +_PID = None + + +class Frida: + + def __init__( + self, + ssh_string, + app_hash, + bundle_id, + defaults, + dump, + auxiliary, + extras, + code, + action): + self.ssh_connection_string = ssh_string + self.app_container = None + self.hash = app_hash + self.bundle_id = bundle_id + self.defaults = defaults + self.dump = dump + self.auxiliary = auxiliary + self.extras = extras + self.code = code + self.action = action + self.frida_dir = Path(settings.TOOLS_DIR) / 'frida_scripts' / 'ios' + self.ipa_dir = Path(settings.UPLD_DIR) / self.hash + self.frida_log = self.ipa_dir / 'mobsf_frida_out.txt' + self.dump_file = self.ipa_dir / 'mobsf_dump_file.txt' + self.container_file = self.ipa_dir / 'mobsf_app_container_path.txt' + + def get_scripts(self, script_type, selected_scripts): + """Get Frida Scripts.""" + combined_script = [] + header = [] + if not selected_scripts: + return header + all_scripts = self.frida_dir / script_type + for script in all_scripts.rglob('*.js'): + if '*' in selected_scripts: + combined_script.append(script.read_text('utf-8', 'ignore')) + if script.stem in selected_scripts: + header.append(f'send("Loaded Frida Script - {script.stem}");') + combined_script.append(script.read_text('utf-8', 'ignore')) + return header + combined_script + + def get_auxiliary(self): + """Get auxiliary hooks.""" + scripts = [] + if not self.auxiliary: + return scripts + for itm in self.auxiliary: + if itm == 'enum_class': + scripts.append(get_loaded_classes()) + elif itm == 'enum_class_methods': + scripts.append(get_loaded_classes_methods()) + elif itm == 'string_capture': + scripts.append(string_capture()) + elif itm == 'string_compare': + scripts.append(string_compare()) + elif itm == 'enum_methods' and 'class_name' in self.extras: + scripts.append( + get_methods(self.extras['class_name'])) + elif itm == 'search_class' and 'class_search' in self.extras: + scripts.append( + class_pattern(self.extras['class_search'])) + elif itm == 'search_method' and 'method_search' in self.extras: + scripts.append( + classes_with_method(self.extras['method_search'])) + elif itm == 'trace_class' and 'class_trace' in self.extras: + scripts.append( + class_trace(self.extras['class_trace'])) + return scripts + + def get_script(self): + """Get final script.""" + if not self.code: + self.code = '' + rpc_list = [] + scripts = [self.code] + scripts.extend(self.get_scripts('default', self.defaults)) + scripts.extend(self.get_scripts('dump', self.dump)) + rpc_list.extend(self.get_scripts('rpc', ['*'])) + scripts.extend(self.get_auxiliary()) + rpc_script = ','.join(rpc_list) + rpc = f'rpc.exports = {{ \n{rpc_script}\n }};' + combined = '\n'.join(scripts) + final = f'{rpc}\n setTimeout(function() {{ \n{combined}\n }}, 1000)' + return final + + def frida_response(self, message, data): + """Function to handle frida responses.""" + if 'payload' in message: + msg = message['payload'] + aux = '[AUXILIARY] ' + jb = '[Jailbreak Detection Bypass] ' + dump = '[MBSFDUMP] ' + if not isinstance(msg, str): + msg = str(msg) + if dump in msg: + msg = msg.replace(dump, '') + self.write_log(self.dump_file, f'{msg}\n') + elif msg.startswith(jb): + self.write_log(self.frida_log, f'{msg}\n') + elif msg.startswith(aux): + msg = msg.replace(aux, '[*] ') + self.write_log(self.frida_log, f'{msg}\n') + else: + logger.debug('[Frida] %s', msg) + self.write_log(self.frida_log, f'{msg}\n') + else: + logger.error('[Frida] %s', message) + + def frida_ssh_forward(self): + """Setup SSH tunnel and port forwarding for corellium.""" + try: + logger.info('Setting up SSH tunnel and port forwarding') + # Corellium VM provides SSH over bastion host + # Frida server is not reachable, open SSH tunnel + # and port forward + Thread( + target=ssh_jumphost_port_forward, + args=(self.ssh_connection_string,), + daemon=True).start() + time.sleep(3) + except Exception: + logger.exception('Setting up SSH tunnel') + + def spawn(self): + """Connect to Frida Server and spawn the app.""" + global _PID + try: + self.clean_up() + try: + _PID = frida.get_remote_device().spawn([self.bundle_id]) + except frida.NotSupportedError: + logger.exception('Not Supported Error') + return + except frida.ServerNotRunningError: + self.frida_ssh_forward() + if not _PID: + _PID = frida.get_remote_device().spawn([self.bundle_id]) + logger.info('Spawning %s', self.bundle_id) + time.sleep(2) + except frida.TimedOutError: + logger.error('Timed out while waiting for device to appear') + except frida.NotSupportedError: + logger.exception('Not Supported Error') + return + except (frida.ProcessNotFoundError, + frida.TransportError, + frida.InvalidOperationError): + pass + except Exception: + logger.exception('Error Connecting to Frida Server') + + def session(self, pid, bundle_id): + """Use existing session to inject frida scripts.""" + global _PID + try: + try: + device = frida.get_remote_device() + if pid and bundle_id: + _PID = pid + self.bundle_id = bundle_id + front = device.get_frontmost_application() + if not front or front.pid != _PID: + # No front most app, spawn the app or + # pid is not the front most app + _PID = device.spawn([self.bundle_id]) + # pid is the fornt most app + session = device.attach(_PID) + except frida.NotSupportedError: + logger.exception('Not Supported Error') + return + except Exception: + logger.warning('Cannot attach to pid, spawning again') + self.spawn() + session = device.attach(_PID) + if session and device and _PID: + script = session.create_script(self.get_script()) + script.on('message', self.frida_response) + script.load() + api = script.exports_sync + device.resume(_PID) + self.app_container = api.get_container() + self.container_file.write_text(self.app_container) + sys.stdin.read() + script.unload() + session.detach() + except frida.NotSupportedError: + logger.exception('Not Supported Error') + except (frida.ProcessNotFoundError, + frida.TransportError, + frida.InvalidOperationError): + pass + except Exception: + logger.exception('Error Connecting to Frida Server') + + def ps(self): + """Get running process pid.""" + ps_dict = [] + try: + try: + device = frida.get_remote_device() + processes = device.enumerate_applications(scope='minimal') + except frida.ServerNotRunningError: + self.frida_ssh_forward() + device = frida.get_remote_device() + processes = device.enumerate_applications(scope='minimal') + if device and processes: + for process in processes: + if process.pid != 0: + ps_dict.append({ + 'pid': process.pid, + 'name': process.name, + 'identifier': process.identifier, + }) + except Exception: + logger.exception('Failed to enumerate running applications') + return ps_dict + + def clean_up(self): + if self.frida_log.exists(): + self.frida_log.unlink() + if self.dump_file.exists(): + self.dump_file.unlink() + if self.container_file.exists(): + self.container_file.unlink() + + def write_log(self, file_path, data): + with file_path.open('a', + encoding='utf-8', + errors='replace') as flip: + flip.write(data) diff --git a/mobsf/DynamicAnalyzer/views/ios/report.py b/mobsf/DynamicAnalyzer/views/ios/report.py new file mode 100644 index 0000000000..1c22e8f307 --- /dev/null +++ b/mobsf/DynamicAnalyzer/views/ios/report.py @@ -0,0 +1,86 @@ +# -*- coding: utf_8 -*- +"""Dynamic Analyzer Reporting.""" +import logging +from pathlib import Path + +from django.conf import settings +from django.shortcuts import render +from django.template.defaulttags import register + +import mobsf.MalwareAnalyzer.views.Trackers as Trackers +from mobsf.DynamicAnalyzer.views.ios.analysis import ( + get_screenshots, + ios_api_analysis, + run_analysis, +) +from mobsf.MobSF.utils import ( + base64_decode, + common_check, + get_md5, + key, + pretty_json, + print_n_send_error_response, + replace, + strict_package_check, +) + + +logger = logging.getLogger(__name__) +register.filter('key', key) +register.filter('replace', replace) +register.filter('pretty_json', pretty_json) +register.filter('base64_decode', base64_decode) + + +def ios_view_report(request, bundle_id, api=False): + """Dynamic Analysis Report Generation.""" + logger.info('iOS Dynamic Analysis Report Generation') + try: + instance_id = request.GET.get('instance_id') + if instance_id and not common_check(instance_id): + dev = instance_id + else: + dev = '' + if not strict_package_check(bundle_id): + # We need this check since bundleid + # is not validated in REST API + return print_n_send_error_response( + request, + 'Invalid iOS Bundle id', + api) + checksum = get_md5(bundle_id.encode('utf-8')) + app_dir = Path(settings.UPLD_DIR) / checksum + download_dir = settings.DWD_DIR + tools_dir = settings.TOOLS_DIR + frida_log = app_dir / 'mobsf_frida_out.txt' + if not frida_log.exists(): + msg = ('Dynamic Analysis report is not available ' + 'for this app. Perform Dynamic Analysis ' + 'and generate the report.') + return print_n_send_error_response(request, msg, api) + api_analysis = ios_api_analysis(app_dir) + dump_analaysis = run_analysis(app_dir, bundle_id, checksum) + trk = Trackers.Trackers(app_dir, tools_dir) + trackers = trk.get_trackers_domains_or_deps( + dump_analaysis['domains'], None) + screenshots = get_screenshots(checksum, download_dir) + context = { + 'hash': checksum, + 'version': settings.MOBSF_VER, + 'title': 'iOS Dynamic Analysis Report', + 'instance_id': dev, + 'bundleid': bundle_id, + 'trackers': trackers, + 'screenshots': screenshots, + 'frida_logs': frida_log.exists(), + } + context.update(api_analysis) + context.update(dump_analaysis) + template = 'dynamic_analysis/ios/dynamic_report.html' + if api: + return context + return render(request, template, context) + except Exception as exp: + logger.exception('Dynamic Analysis Report Generation') + err = f'Error Generating Dynamic Analysis Report. {str(exp)}' + return print_n_send_error_response(request, err, api) diff --git a/mobsf/DynamicAnalyzer/views/ios/tests_frida.py b/mobsf/DynamicAnalyzer/views/ios/tests_frida.py new file mode 100644 index 0000000000..17bd0d9cd9 --- /dev/null +++ b/mobsf/DynamicAnalyzer/views/ios/tests_frida.py @@ -0,0 +1,111 @@ +# -*- coding: utf_8 -*- +"""Frida tests for iOS.""" +from threading import Thread +import logging + +from django.views.decorators.http import require_http_methods + +from mobsf.DynamicAnalyzer.views.ios.frida_core import ( + Frida, +) +from mobsf.DynamicAnalyzer.views.common.shared import ( + invalid_params, + is_attack_pattern, + send_response, +) +from mobsf.DynamicAnalyzer.views.ios.corellium_apis import ( + CorelliumInstanceAPI, + OK, +) +from mobsf.MobSF.utils import ( + common_check, + is_md5, + strict_package_check, +) + +logger = logging.getLogger(__name__) + +# AJAX + + +@require_http_methods(['POST']) +def ios_instrument(request, api=False): + """Instrument app with frida.""" + data = { + 'status': 'failed', + 'message': 'Failed to instrument app'} + try: + action = request.POST.get('frida_action', 'spawn') + pid = request.POST.get('pid') + new_bundle_id = request.POST.get('new_bundle_id') + instance_id = request.POST['instance_id'] + bundle_id = request.POST['bundle_id'] + md5_hash = request.POST['hash'] + default_hooks = request.POST['default_hooks'] + dump_hooks = request.POST['dump_hooks'] + auxiliary_hooks = request.POST['auxiliary_hooks'] + code = request.POST['frida_code'] + + failed = common_check(instance_id) + if failed: + return send_response(failed, api) + if not strict_package_check(bundle_id): + data['message'] = 'Invalid iOS Bundle id' + return send_response(data, api) + if new_bundle_id and not strict_package_check(new_bundle_id): + data['message'] = 'Invalid iOS Bundle id' + return send_response(data, api) + ci = CorelliumInstanceAPI(instance_id) + + # Fill extras + extras = {} + class_name = request.POST.get('class_name') + if class_name: + extras['class_name'] = class_name.strip() + class_search = request.POST.get('class_search') + if class_search: + extras['class_search'] = class_search.strip() + method_search = request.POST.get('method_search') + if method_search: + extras['method_search'] = method_search.strip() + cls_trace = request.POST.get('class_trace') + if cls_trace: + extras['class_trace'] = cls_trace.strip() + if (is_attack_pattern(default_hooks) + or is_attack_pattern(dump_hooks) + or is_attack_pattern(auxiliary_hooks) + or not is_md5(md5_hash)): + return invalid_params(api) + + frida_obj = Frida( + ci.get_ssh_connection_string(), + md5_hash, + bundle_id, + default_hooks.split(','), + dump_hooks.split(','), + auxiliary_hooks.split(','), + extras, + code, + action) + if action == 'spawn': + logger.info('Starting Instrumentation') + frida_obj.spawn() + elif action == 'ps': + logger.info('Enumerating running applications') + data['message'] = frida_obj.ps() + if action in ('spawn', 'session'): + if pid and pid.isdigit(): + # Attach to a different pid/bundle id + args = (int(pid), new_bundle_id) + logger.info('Attaching to %s [PID: %s]', new_bundle_id, pid) + else: + # Injecting to existing session/spawn + if action == 'session': + logger.info('Injecting to existing frida session') + args = (None, None) + Thread(target=frida_obj.session, args=args, daemon=True).start() + data['status'] = OK + except Exception as exp: + logger.exception('Frida Instrumentation failed') + data = {'status': 'failed', 'message': str(exp)} + return send_response(data, api) diff --git a/mobsf/MobSF/init.py b/mobsf/MobSF/init.py index de462c6843..fe780491c0 100644 --- a/mobsf/MobSF/init.py +++ b/mobsf/MobSF/init.py @@ -10,13 +10,13 @@ logger = logging.getLogger(__name__) -VERSION = '3.7.9' +VERSION = '3.8.0' BANNER = """ - __ __ _ ____ _____ _____ _____ - | \/ | ___ | |__/ ___|| ___|_ _|___ /|___ | - | |\/| |/ _ \| '_ \___ \| |_ \ \ / / |_ \ / / - | | | | (_) | |_) |__) | _| \ V / ___) | / / - |_| |_|\___/|_.__/____/|_| \_/ |____(_)_/ + __ __ _ ____ _____ _____ ___ + | \/ | ___ | |__/ ___|| ___|_ _|___ / ( _ ) + | |\/| |/ _ \| '_ \___ \| |_ \ \ / / |_ \ / _ \ + | | | | (_) | |_) |__) | _| \ V / ___) | (_) | + |_| |_|\___/|_.__/____/|_| \_/ |____(_)___/ """ # noqa: W291 # ASCII Font: Standard diff --git a/mobsf/MobSF/settings.py b/mobsf/MobSF/settings.py index 9d17c47c99..c53b219634 100644 --- a/mobsf/MobSF/settings.py +++ b/mobsf/MobSF/settings.py @@ -77,6 +77,7 @@ '.so': 'application/octet-stream', '.dylib': 'application/octet-stream', '.a': 'application/octet-stream', + '.pcap': 'application/vnd.tcpdump.pcap', } # =============ALLOWED MIMETYPES================= APK_MIME = [ @@ -180,7 +181,6 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', @@ -190,6 +190,7 @@ ) MIDDLEWARE = ( 'mobsf.MobSF.views.api.api_middleware.RestApiAuthMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', ) ROOT_URLCONF = 'mobsf.MobSF.urls' WSGI_APPLICATION = 'mobsf.MobSF.wsgi.application' @@ -435,5 +436,10 @@ # https://www.virustotal.com/en/user//apikey/ # Files will be uploaded to VirusTotal # if VT_UPLOAD is set to True. - # ============================================== + # =============================================== + # =======IOS DYNAMIC ANALYSIS SETTINGS=========== + CORELLIUM_API_KEY = os.getenv('MOBSF_CORELLIUM_API_KEY', '') + CORELLIUM_PROJECT_ID = os.getenv('MOBSF_CORELLIUM_PROJECT_ID', '') + # CORELLIUM_PROJECT_ID is optional, MobSF will use any available project id + # =============================================== # ^CONFIG-END^: Do not edit this line diff --git a/mobsf/MobSF/urls.py b/mobsf/MobSF/urls.py index 87fa2913b2..077823b35e 100755 --- a/mobsf/MobSF/urls.py +++ b/mobsf/MobSF/urls.py @@ -1,5 +1,9 @@ from django.urls import re_path +from mobsf.DynamicAnalyzer.views.common import ( + device, + frida, +) from mobsf.DynamicAnalyzer.views.android import dynamic_analyzer as dz from mobsf.DynamicAnalyzer.views.android import ( operations, @@ -7,6 +11,12 @@ tests_common, tests_frida, ) +from mobsf.DynamicAnalyzer.views.ios import dynamic_analyzer as idz +from mobsf.DynamicAnalyzer.views.ios import ( + corellium_instance as instance, + report as ios_view_report, + tests_frida as ios_tests_frida, +) from mobsf.MobSF import utils from mobsf.MobSF.views import home from mobsf.MobSF.views.api import api_static_analysis as api_sz @@ -69,9 +79,10 @@ re_path(r'^api/v1/frida/instrument$', api_dz.api_instrument), re_path(r'^api/v1/frida/api_monitor$', api_dz.api_api_monitor), re_path(r'^api/v1/frida/logs$', api_dz.api_frida_logs), + re_path(r'^api/v1/frida/get_dependencies$', api_dz.api_get_dependencies), + # Shared re_path(r'^api/v1/frida/list_scripts$', api_dz.api_list_frida_scripts), re_path(r'^api/v1/frida/get_script$', api_dz.api_get_script), - re_path(r'^api/v1/frida/get_dependencies$', api_dz.api_get_dependencies), ] if settings.API_ONLY == '0': urlpatterns.extend([ @@ -92,6 +103,7 @@ re_path(r'^error/$', home.error, name='error'), re_path(r'^not_found/$', home.not_found), re_path(r'^zip_format/$', home.zip_format), + re_path(r'^dynamic_analysis/$', home.dynamic_analysis, name='dynamic'), # Static Analysis # Android @@ -139,9 +151,9 @@ shared_func.compare_apps), # Dynamic Analysis - re_path(r'^dynamic_analysis/$', - dz.dynamic_analysis, - name='dynamic'), + re_path(r'^android/dynamic_analysis/$', + dz.android_dynamic_analysis, + name='dynamic_android'), re_path(r'^android_dynamic/(?P[0-9a-f]{32})$', dz.dynamic_analyzer, name='dynamic_analyzer'), @@ -178,16 +190,101 @@ re_path(r'^frida_logs/$', tests_frida.frida_logs, name='frida_logs'), - re_path(r'^list_frida_scripts/$', tests_frida.list_frida_scripts), - re_path(r'^get_script/$', tests_frida.get_script), re_path(r'^get_dependencies/$', tests_frida.get_runtime_dependencies), # Report re_path(r'^dynamic_report/(?P[0-9a-f]{32})$', report.view_report, name='dynamic_report'), + # Shared + re_path(r'^list_frida_scripts/$', + frida.list_frida_scripts, + name='list_frida_scripts'), + re_path(r'^get_script/$', + frida.get_script, + name='get_script'), re_path(r'^dynamic_view_file/$', - report.view_file, + device.view_file, name='dynamic_view_file'), + # iOS Dynamic Analysis + re_path(r'^ios/dynamic_analysis/$', + idz.dynamic_analysis, + name='dynamic_ios'), + re_path(r'^ios/create_vm_instance/$', + instance.create_vm_instance, + name='create_vm_instance'), + re_path(r'^ios/get_supported_models/$', + instance.get_supported_models, + name='get_supported_models'), + re_path(r'^ios/get_supported_os/$', + instance.get_supported_os, + name='get_supported_os'), + re_path(r'^ios/start_instance/$', + instance.start_instance, + name='start_instance'), + re_path(r'^ios/stop_instance/$', + instance.stop_instance, + name='stop_instance'), + re_path(r'^ios/unpause_instance/$', + instance.unpause_instance, + name='unpause_instance'), + re_path(r'^ios/reboot_instance/$', + instance.reboot_instance, + name='reboot_instance'), + re_path(r'^ios/destroy_instance/$', + instance.destroy_instance, + name='destroy_instance'), + re_path(r'^ios/list_apps/$', + instance.list_apps, + name='list_apps'), + re_path(r'^ios/setup_environment/(?P[0-9a-f]{32})$', + instance.setup_environment, + name='setup_environment'), + re_path(r'^ios/dynamic_analyzer/$', + idz.dynamic_analyzer, + name='dynamic_analyzer_ios'), + re_path(r'^ios/run_app/$', + instance.run_app, + name='run_app'), + re_path(r'^ios/remove_app/$', + instance.remove_app, + name='remove_app'), + re_path(r'^ios/take_screenshot/$', + instance.take_screenshot, + name='take_screenshot'), + re_path(r'^ios/network_capture/$', + instance.network_capture, + name='network_capture'), + re_path(r'^ios/live_pcap_download/$', + instance.live_pcap_download, + name='ios_live_pcap_download'), + re_path(r'^ios/ssh_execute/$', + instance.ssh_execute, + name='ssh_execute'), + re_path(r'^ios/upload_file/$', + instance.upload_file, + name='upload_file'), + re_path(r'^ios/download_file/$', + instance.download_file, + name='download_file'), + re_path(r'^ios/touch/$', + instance.touch, + name='ios_touch'), + re_path(r'^ios/get_container_path/$', + instance.get_container_path, + name='get_container_path'), + re_path(r'^ios/system_logs/$', + instance.system_logs, + name='ios_system_logs'), + re_path(r'^ios/download_data/(?P([\w]*\.)+[\w]{2,155})$', + instance.download_data, + name='ios_download_data'), + re_path(r'^ios/instrument/$', + ios_tests_frida.ios_instrument, + name='ios_instrument'), + re_path(r'^ios/view_report/(?P([\w]*\.)+[\w]{2,155})$', + ios_view_report.ios_view_report, + name='ios_view_report'), + # Test re_path(r'^tests/$', tests.start_test), ]) diff --git a/mobsf/MobSF/utils.py b/mobsf/MobSF/utils.py index 7f0eb05187..fe25cda762 100755 --- a/mobsf/MobSF/utils.py +++ b/mobsf/MobSF/utils.py @@ -1,11 +1,14 @@ """Common Utils.""" import ast +import base64 import hashlib import io +import json import logging import ntpath import os import platform +import random import re import sys import shutil @@ -30,6 +33,19 @@ logger = logging.getLogger(__name__) ADB_PATH = None +BASE64_REGEX = re.compile(r'^[-A-Za-z0-9+/]*={0,3}$') +MD5_REGEX = re.compile(r'^[0-9a-f]{32}$') +# Regex to capture strings between quotes or tag +STRINGS_REGEX = re.compile(r'(?<=\")(.+?)(?=\")|(?<=\)(.+?)(?=\<)') +# MobSF Custom regex to catch maximum URI like strings +URL_REGEX = re.compile( + ( + r'((?:https?://|s?ftps?://|' + r'file://|javascript:|data:|www\d{0,3}[.])' + r'[\w().=/;,#:@?&~*+!$%\'{}-]+)' + ), + re.UNICODE) +EMAIL_REGEX = re.compile(r'[\w+.-]{1,20}@[\w-]{1,20}\.[\w]{2,10}') class Color(object): @@ -242,7 +258,7 @@ def python_dict(value): def is_base64(b_str): - return re.match('^[A-Za-z0-9+/]+[=]{0,2}$', b_str) + return BASE64_REGEX.match(b_str) def is_internet_available(): @@ -290,7 +306,9 @@ def sha256_object(file_obj): def gen_sha256_hash(msg): """Generate SHA 256 Hash of the message.""" - hash_object = hashlib.sha256(msg.encode('utf-8')) + if isinstance(msg, str): + msg = msg.encode('utf-8') + hash_object = hashlib.sha256(msg) return hash_object.hexdigest() @@ -441,7 +459,6 @@ def check_basic_env(): 'Java/jdk1.7.0_17/bin/"' '\nJAVA_DIRECTORY = "/usr/bin/"') os.kill(os.getpid(), signal.SIGTERM) - get_adb() def update_local_db(db_name, url, local_file): @@ -558,7 +575,7 @@ def file_size(app_path): def is_md5(user_input): """Check if string is valid MD5.""" - stat = re.match(r'^[0-9a-f]{32}$', user_input) + stat = MD5_REGEX.match(user_input) if not stat: logger.error('Invalid scan hash') return stat @@ -601,14 +618,49 @@ def cmd_injection_check(data): def strict_package_check(user_input): - """Strict package name check.""" - pat = re.compile(r'^([A-Za-z]{1}[\w]*\.)+[A-Za-z][\w]*$') + """Strict package name check. + + For android package and ios bundle id + """ + pat = re.compile(r'^([\w]*\.)+[\w]{2,155}$') + resp = re.match(pat, user_input) + if not resp or '..' in user_input: + logger.error('Invalid package name/bundle id/class name') + return resp + + +def strict_ios_class(user_input): + """Strict check to see if input is valid iOS class.""" + pat = re.compile(r'^([\w\.]+)$') resp = re.match(pat, user_input) if not resp: - logger.error('Invalid package/class name') + logger.error('Invalid class name') return resp +def is_instance_id(user_input): + """Check if string is valid instance id.""" + reg = r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + stat = re.match(reg, user_input) + if not stat: + logger.error('Invalid instance identifier') + return stat + + +def common_check(instance_id): + """Common checks for instance APIs.""" + if not getattr(settings, 'CORELLIUM_API_KEY', ''): + return { + 'status': 'failed', + 'message': 'Missing Corellium API key'} + elif not is_instance_id(instance_id): + return { + 'status': 'failed', + 'message': 'Invalid instance identifier'} + else: + return None + + def is_path_traversal(user_input): """Check for path traversal.""" if (('../' in user_input) @@ -689,6 +741,49 @@ def key(data, key_name): return data.get(key_name) +def replace(value, arg): + """ + Replacing filter. + + Use `{{ "aaa"|replace:"a|b" }}` + """ + if len(arg.split('|')) != 2: + return value + + what, to = arg.split('|') + return value.replace(what, to) + + +def pretty_json(value): + """Pretty print JSON.""" + try: + return json.dumps(json.loads(value), indent=4) + except Exception: + return value + + +def base64_decode(value): + """Try Base64 decode.""" + commonb64s = ('eyJ0') + decoded = None + try: + if is_base64(value) or value.startswith(commonb64s): + decoded = base64.b64decode( + value).decode('ISO-8859-1') + except Exception: + pass + if decoded: + return f'{value}\n\nBase64 Decoded: {decoded}' + return value + + +def base64_encode(value): + """Base64 encode.""" + if isinstance(value, str): + value = value.encode('utf-8') + return base64.b64encode(value) + + def android_component(data): """Return Android component from data.""" cmp = '' @@ -709,9 +804,8 @@ def get_android_dm_exception_msg(): return ( 'Is your Android VM/emulator running? MobSF cannot' ' find the android device identifier.' - ' Please run an android instance and refresh' - ' this page. If this error persists,' - ' set ANALYZER_IDENTIFIER in ' + ' Please read official documentation.' + ' If this error persists, set ANALYZER_IDENTIFIER in ' f'{get_config_loc()} or via environment variable' ' MOBSF_ANALYZER_IDENTIFIER') @@ -737,3 +831,8 @@ def settings_enabled(attr): return getattr(settings, attr) not in disabled except Exception: return False + + +def id_generator(size=6, chars=string.ascii_uppercase + string.digits): + """Generate random string.""" + return ''.join(random.choice(chars) for _ in range(size)) diff --git a/mobsf/MobSF/views/api/api_dynamic_analysis.py b/mobsf/MobSF/views/api/api_dynamic_analysis.py index b0f5edb3a8..5d0c3ca29e 100644 --- a/mobsf/MobSF/views/api/api_dynamic_analysis.py +++ b/mobsf/MobSF/views/api/api_dynamic_analysis.py @@ -12,6 +12,10 @@ tests_common, tests_frida, ) +from mobsf.DynamicAnalyzer.views.common import ( + device, + frida, +) # Dynamic Analyzer APIs @@ -19,7 +23,7 @@ @csrf_exempt def api_get_apps(request): """GET - Get Apps for dynamic analysis API.""" - resp = dynamic_analyzer.dynamic_analysis(request, True) + resp = dynamic_analyzer.android_dynamic_analysis(request, True) if 'error' in resp: return make_api_response(resp, 500) return make_api_response(resp, 200) @@ -225,11 +229,14 @@ def api_frida_logs(request): return make_api_response(resp, 500) -@request_method(['GET']) +@request_method(['POST']) @csrf_exempt def api_list_frida_scripts(request): - """GET - List Frida Scripts.""" - resp = tests_frida.list_frida_scripts(request, True) + """POST - List Frida Scripts.""" + if 'device' not in request.POST: + return make_api_response( + {'error': 'Missing Parameters'}, 422) + resp = frida.list_frida_scripts(request, True) if resp['status'] == 'ok': return make_api_response(resp, 200) return make_api_response(resp, 500) @@ -242,7 +249,10 @@ def api_get_script(request): if not request.POST.getlist('scripts[]'): return make_api_response( {'error': 'Missing Parameters'}, 422) - resp = tests_frida.get_script(request, True) + if 'device' not in request.POST: + return make_api_response( + {'error': 'Missing Parameters'}, 422) + resp = frida.get_script(request, True) if resp['status'] == 'ok': return make_api_response(resp, 200) return make_api_response(resp, 500) @@ -286,7 +296,7 @@ def api_dynamic_view_file(request): if set(request.POST) < params: return make_api_response( {'error': 'Missing Parameters'}, 422) - resp = report.view_file(request, True) + resp = device.view_file(request, True) if 'error' in resp: return make_api_response(resp, 500) return make_api_response(resp, 200) diff --git a/mobsf/MobSF/views/api/api_static_analysis.py b/mobsf/MobSF/views/api/api_static_analysis.py index e61cebf36b..bf3bcaaf4a 100755 --- a/mobsf/MobSF/views/api/api_static_analysis.py +++ b/mobsf/MobSF/views/api/api_static_analysis.py @@ -1,13 +1,14 @@ # -*- coding: utf_8 -*- """MobSF REST API V 1.""" -import re - from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from mobsf.StaticAnalyzer.models import ( RecentScansDB, ) +from mobsf.MobSF.utils import ( + is_md5, +) from mobsf.MobSF.views.helpers import request_method from mobsf.MobSF.views.home import RecentScans, Upload, delete_scan from mobsf.MobSF.views.api.api_middleware import make_api_response @@ -56,7 +57,7 @@ def api_scan(request): return make_api_response( {'error': 'Missing Parameters'}, 422) checksum = request.POST['hash'] - if not re.match('^[0-9a-f]{32}$', checksum): + if not is_md5(checksum): return make_api_response( {'error': 'Invalid Checksum'}, 500) robj = RecentScansDB.objects.filter(MD5=checksum) diff --git a/mobsf/MobSF/views/home.py b/mobsf/MobSF/views/home.py index 2cb603d173..c4cf9a2a7a 100755 --- a/mobsf/MobSF/views/home.py +++ b/mobsf/MobSF/views/home.py @@ -22,8 +22,10 @@ from mobsf.MobSF.forms import FormUtil, UploadFileForm from mobsf.MobSF.utils import ( api_key, + get_md5, is_dir_exists, is_file_exists, + is_md5, is_safe_path, key, print_n_send_error_response, @@ -210,6 +212,16 @@ def zip_format(request): return render(request, template, context) +def dynamic_analysis(request): + """Dynamic Analysis Landing.""" + context = { + 'title': 'Dynamic Analysis', + 'version': settings.MOBSF_VER, + } + template = 'general/dynamic.html' + return render(request, template, context) + + def not_found(request): """Not Found Route.""" context = { @@ -226,6 +238,7 @@ def recent_scans(request): db_obj = RecentScansDB.objects.all().order_by('-TIMESTAMP').values() android = StaticAnalyzerAndroid.objects.all() ios = StaticAnalyzerIOS.objects.all() + updir = Path(settings.UPLD_DIR) icon_mapping = {} package_mapping = {} for item in android: @@ -239,8 +252,13 @@ def recent_scans(request): else: entry['PACKAGE'] = '' entry['ICON_PATH'] = icon_mapping.get(entry['MD5'], '') - logcat = Path(settings.UPLD_DIR) / entry['MD5'] / 'logcat.txt' - entry['DYNAMIC_REPORT_EXISTS'] = logcat.exists() + if entry['FILE_NAME'].endswith('.ipa'): + entry['BUNDLE_HASH'] = get_md5( + entry['PACKAGE_NAME'].encode('utf-8')) + report_file = updir / entry['BUNDLE_HASH'] / 'mobsf_dump_file.txt' + else: + report_file = updir / entry['MD5'] / 'logcat.txt' + entry['DYNAMIC_REPORT_EXISTS'] = report_file.exists() entries.append(entry) context = { 'title': 'Recent Scans', @@ -317,8 +335,7 @@ def generate_download(request): logger.info('Generating Downloads') md5 = request.GET['hash'] file_type = request.GET['file_type'] - match = re.match('^[0-9a-f]{32}$', md5) - if (not match + if (not is_md5(md5) or file_type not in binary + source): msg = 'Invalid download type or hash' logger.exception(msg) diff --git a/mobsf/StaticAnalyzer/forms.py b/mobsf/StaticAnalyzer/forms.py index dab1a0461d..7c662c1500 100644 --- a/mobsf/StaticAnalyzer/forms.py +++ b/mobsf/StaticAnalyzer/forms.py @@ -3,6 +3,8 @@ from django import forms +from mobsf.MobSF.utils import is_md5 + class AttackDetect(forms.Form): file = forms.CharField() @@ -36,8 +38,7 @@ class APIChecks(forms.Form): def clean_hash(self): """Hash is valid.""" md5 = self.cleaned_data['hash'] - md5_match = re.match('^[0-9a-f]{32}$', md5) - if not md5_match: + if not is_md5(md5): raise forms.ValidationError('Invalid Hash') return md5 @@ -48,8 +49,7 @@ class WebChecks(forms.Form): def clean_md5(self): """Hash is valid.""" md5 = self.cleaned_data['md5'] - md5_match = re.match('^[0-9a-f]{32}$', md5) - if not md5_match: + if not is_md5(md5): raise forms.ValidationError('Invalid Hash') return md5 diff --git a/mobsf/StaticAnalyzer/views/android/cert_analysis.py b/mobsf/StaticAnalyzer/views/android/cert_analysis.py index b4c50d5e7c..104eb10aa9 100755 --- a/mobsf/StaticAnalyzer/views/android/cert_analysis.py +++ b/mobsf/StaticAnalyzer/views/android/cert_analysis.py @@ -1,7 +1,6 @@ # -*- coding: utf_8 -*- """Module holding the functions for code analysis.""" -import binascii import hashlib import logging import os @@ -13,12 +12,19 @@ from asn1crypto import x509 -from oscrypto import asymmetric +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ( + dsa, + ec, + rsa, +) from django.utils.html import escape from mobsf.MobSF.utils import ( find_java_binary, + gen_sha256_hash, ) logger = logging.getLogger(__name__) @@ -83,11 +89,35 @@ def get_cert_details(data): def get_pub_key_details(data): """Get public key details.""" certlist = [] - x509_public_key = asymmetric.load_public_key(data) - certlist.append(f'PublicKey Algorithm: {x509_public_key.algorithm}') - certlist.append(f'Bit Size: {x509_public_key.bit_size}') - fp = binascii.hexlify(x509_public_key.fingerprint).decode('utf-8') - certlist.append(f'Fingerprint: {fp}') + + x509_public_key = serialization.load_der_public_key( + data, + backend=default_backend()) + alg = 'unknown' + fingerprint = '' + if isinstance(x509_public_key, rsa.RSAPublicKey): + alg = 'rsa' + modulus = x509_public_key.public_numbers().n + public_exponent = x509_public_key.public_numbers().e + to_hash = f'{modulus}:{public_exponent}' + elif isinstance(x509_public_key, dsa.DSAPublicKey): + alg = 'dsa' + dsa_parameters = x509_public_key.parameters() + p = dsa_parameters.p + q = dsa_parameters.q + g = dsa_parameters.g + y = x509_public_key.public_numbers().y + to_hash = f'{p}:{q}:{g}:{y}' + elif isinstance(x509_public_key, ec.EllipticCurvePublicKey): + alg = 'ec' + to_hash = f'{x509_public_key.public_numbers().curve.name}:' + to_hash = to_hash.encode('utf-8') + # Untested, possibly wrong key size and fingerprint + to_hash += data[25:] + fingerprint = gen_sha256_hash(to_hash) + certlist.append(f'PublicKey Algorithm: {alg}') + certlist.append(f'Bit Size: {x509_public_key.key_size}') + certlist.append(f'Fingerprint: {fingerprint}') return certlist diff --git a/mobsf/StaticAnalyzer/views/android/find.py b/mobsf/StaticAnalyzer/views/android/find.py index 5dc19acec6..da3351ea48 100755 --- a/mobsf/StaticAnalyzer/views/android/find.py +++ b/mobsf/StaticAnalyzer/views/android/find.py @@ -2,7 +2,6 @@ """Find in java or smali files.""" import logging -import re import json from pathlib import Path @@ -10,7 +9,10 @@ from django.http import JsonResponse from django.utils.html import escape -from mobsf.MobSF.utils import print_n_send_error_response +from mobsf.MobSF.utils import ( + is_md5, + print_n_send_error_response, +) from mobsf.StaticAnalyzer.views.common.shared_func import ( find_java_source_folder, ) @@ -21,9 +23,8 @@ def run(request): """Find filename/content in source files (ajax response).""" try: - match = re.match('^[0-9a-f]{32}$', request.POST['md5']) - if not match: - raise ValueError('Invalid MD5 hash') + if not is_md5(request.POST['md5']): + raise ValueError('Invalid Hash') md5 = request.POST['md5'] query = request.POST['q'] code = request.POST['code'] diff --git a/mobsf/StaticAnalyzer/views/android/manifest_view.py b/mobsf/StaticAnalyzer/views/android/manifest_view.py index e64fcebd47..61929c0386 100755 --- a/mobsf/StaticAnalyzer/views/android/manifest_view.py +++ b/mobsf/StaticAnalyzer/views/android/manifest_view.py @@ -3,13 +3,15 @@ import logging import os -import re from pathlib import Path from django.conf import settings from django.shortcuts import render -from mobsf.MobSF.utils import print_n_send_error_response +from mobsf.MobSF.utils import ( + is_md5, + print_n_send_error_response, +) from mobsf.StaticAnalyzer.views.android.manifest_utils import ( get_manifest_file, ) @@ -20,10 +22,10 @@ def run(request, checksum): """View the manifest.""" try: + supported = ['eclipse', 'studio', 'apk', 'aar'] directory = settings.BASE_DIR # BASE DIR typ = request.GET['type'] # APK or SOURCE - match = re.match('^[0-9a-f]{32}$', checksum) - if match and (typ in ['eclipse', 'studio', 'apk', 'aar']): + if is_md5(checksum) and (typ in supported): app_dir = os.path.join( settings.UPLD_DIR, checksum + '/') # APP DIRECTORY tools_dir = os.path.join( diff --git a/mobsf/StaticAnalyzer/views/android/source_tree.py b/mobsf/StaticAnalyzer/views/android/source_tree.py index dede2b3644..206e75b049 100644 --- a/mobsf/StaticAnalyzer/views/android/source_tree.py +++ b/mobsf/StaticAnalyzer/views/android/source_tree.py @@ -2,7 +2,6 @@ """List all java files.""" import logging -import re from pathlib import Path from django.conf import settings @@ -13,6 +12,7 @@ from mobsf.MobSF.utils import ( api_key, + is_md5, print_n_send_error_response, ) from mobsf.StaticAnalyzer.views.common.shared_func import ( @@ -45,8 +45,7 @@ def run(request): """Source Tree - Java/Smali view.""" try: logger.info('Listing Source files') - match = re.match('^[0-9a-f]{32}$', request.GET['md5']) - if not match: + if not is_md5(request.GET['md5']): return print_n_send_error_response(request, 'Scan hash not found') md5 = request.GET['md5'] typ = request.GET['type'] diff --git a/mobsf/StaticAnalyzer/views/android/static_analyzer.py b/mobsf/StaticAnalyzer/views/android/static_analyzer.py index c5e129484d..2390d32ed8 100755 --- a/mobsf/StaticAnalyzer/views/android/static_analyzer.py +++ b/mobsf/StaticAnalyzer/views/android/static_analyzer.py @@ -3,7 +3,6 @@ import logging import os -import re import shutil from pathlib import Path @@ -23,6 +22,7 @@ file_size, is_dir_exists, is_file_exists, + is_md5, key, print_n_send_error_response, ) @@ -106,13 +106,17 @@ def static_analyzer(request, checksum, api=False): rescan = True # Input validation app_dic = {} - if not re.match('^[0-9a-f]{32}$', checksum): - msg = 'Invalid checksum' - return print_n_send_error_response(request, msg, api) + if not is_md5(checksum): + return print_n_send_error_response( + request, + 'Invalid Hash', + api) robj = RecentScansDB.objects.filter(MD5=checksum) if not robj.exists(): - msg = 'The file is not uploaded/available' - return print_n_send_error_response(request, msg, api) + return print_n_send_error_response( + request, + 'The file is not uploaded/available', + api) typ = robj[0].SCAN_TYPE filename = robj[0].FILE_NAME allowed_exts = ( @@ -121,8 +125,10 @@ def static_analyzer(request, checksum, api=False): allowed_typ = [i.replace('.', '') for i in allowed_exts] if (not filename.lower().endswith(allowed_exts) or typ not in allowed_typ): - msg = 'Invalid file extension or file type' - return print_n_send_error_response(request, msg, api) + return print_n_send_error_response( + request, + 'Invalid file extension or file type', + api) app_dic['dir'] = Path(settings.BASE_DIR) # BASE DIR app_dic['app_name'] = filename # APP ORIGINAL NAME diff --git a/mobsf/StaticAnalyzer/views/common/appsec.py b/mobsf/StaticAnalyzer/views/common/appsec.py index 7d167275b8..fe0887e1d8 100644 --- a/mobsf/StaticAnalyzer/views/common/appsec.py +++ b/mobsf/StaticAnalyzer/views/common/appsec.py @@ -10,6 +10,7 @@ from mobsf.MobSF import settings from mobsf.MobSF.utils import ( + is_md5, print_n_send_error_response, ) from mobsf.StaticAnalyzer.models import ( @@ -349,6 +350,13 @@ def get_ios_dashboard(context, from_ctx=False): def appsec_dashboard(request, checksum, api=False): """Provide data for appsec dashboard.""" try: + if not is_md5(checksum): + # We need this check since checksum is not validated + # in REST API + return print_n_send_error_response( + request, + 'Invalid Hash', + api) android_static_db = StaticAnalyzerAndroid.objects.filter( MD5=checksum) ios_static_db = StaticAnalyzerIOS.objects.filter( @@ -362,7 +370,7 @@ def appsec_dashboard(request, checksum, api=False): return {'not_found': 'Report not found or supported'} else: msg = 'Report not found or supported' - return print_n_send_error_response(request, msg) + return print_n_send_error_response(request, msg, api) context['version'] = settings.MOBSF_VER context['title'] = 'AppSec Scorecard' context['efr01'] = True if settings.EFR_01 == '1' else False diff --git a/mobsf/StaticAnalyzer/views/common/pdf.py b/mobsf/StaticAnalyzer/views/common/pdf.py index bf26244abd..dc0661adf4 100644 --- a/mobsf/StaticAnalyzer/views/common/pdf.py +++ b/mobsf/StaticAnalyzer/views/common/pdf.py @@ -7,7 +7,6 @@ import json import logging import os -import re import platform from django.http import HttpResponse @@ -16,6 +15,7 @@ import mobsf.MalwareAnalyzer.views.VirusTotal as VirusTotal from mobsf.MobSF import settings from mobsf.MobSF.utils import ( + is_md5, print_n_send_error_response, upstream_proxy, ) @@ -52,13 +52,12 @@ def pdf(request, checksum, api=False, jsonres=False): try: - hash_match = re.match('^[0-9a-f]{32}$', checksum) - if not hash_match: + if not is_md5(checksum): if api: - return {'error': 'Invalid scan hash'} + return {'error': 'Invalid Hash'} else: return HttpResponse( - json.dumps({'md5': 'Invalid scan hash'}), + json.dumps({'md5': 'Invalid Hash'}), content_type=ctype, status=500) # Do Lookups android_static_db = StaticAnalyzerAndroid.objects.filter( diff --git a/mobsf/StaticAnalyzer/views/common/shared_func.py b/mobsf/StaticAnalyzer/views/common/shared_func.py index 5df7cb1757..67bfe41bf3 100755 --- a/mobsf/StaticAnalyzer/views/common/shared_func.py +++ b/mobsf/StaticAnalyzer/views/common/shared_func.py @@ -24,6 +24,9 @@ from mobsf.MobSF import settings from mobsf.MobSF.utils import ( + EMAIL_REGEX, + STRINGS_REGEX, + URL_REGEX, is_md5, print_n_send_error_response, upstream_proxy, @@ -37,17 +40,6 @@ logger = logging.getLogger(__name__) -# Regex to capture strings between quotes or tag -STRINGS_REGEX = re.compile(r'(?<=\")(.+?)(?=\")|(?<=\)(.+?)(?=\<)') -# MobSF Custom regex to catch maximum URI like strings -URL_REGEX = re.compile( - ( - r'((?:https?://|s?ftps?://|' - r'file://|javascript:|data:|www\d{0,3}[.])' - r'[\w().=/;,#:@?&~*+!$%\'{}-]+)' - ), - re.UNICODE) -EMAIL_REGEX = re.compile(r'[\w.-]{1,20}@[\w-]{1,20}\.[\w]{2,10}') def hash_gen(app_path) -> tuple: diff --git a/mobsf/StaticAnalyzer/views/common/suppression.py b/mobsf/StaticAnalyzer/views/common/suppression.py index b4d549ade6..a7126451b6 100644 --- a/mobsf/StaticAnalyzer/views/common/suppression.py +++ b/mobsf/StaticAnalyzer/views/common/suppression.py @@ -13,7 +13,7 @@ StaticAnalyzerAndroid, StaticAnalyzerIOS, ) -from mobsf.DynamicAnalyzer.views.android.operations import ( +from mobsf.DynamicAnalyzer.views.common.shared import ( invalid_params, is_attack_pattern, send_response, diff --git a/mobsf/StaticAnalyzer/views/ios/static_analyzer.py b/mobsf/StaticAnalyzer/views/ios/static_analyzer.py index 5c9f918f6b..5446ce5e4f 100755 --- a/mobsf/StaticAnalyzer/views/ios/static_analyzer.py +++ b/mobsf/StaticAnalyzer/views/ios/static_analyzer.py @@ -1,7 +1,6 @@ # -*- coding: utf_8 -*- """iOS Static Code Analysis.""" import logging -import re from pathlib import Path import mobsf.MalwareAnalyzer.views.Trackers as Trackers @@ -12,6 +11,7 @@ from mobsf.MobSF.utils import ( file_size, + is_md5, print_n_send_error_response, ) from mobsf.StaticAnalyzer.models import ( @@ -78,21 +78,27 @@ def static_analyzer_ios(request, checksum, api=False): if re_scan == '1': rescan = True app_dict = {} - if not re.match('^[0-9a-f]{32}$', checksum): - msg = 'Invalid checksum' - return print_n_send_error_response(request, msg, api) + if not is_md5(checksum): + return print_n_send_error_response( + request, + 'Invalid Hash', + api) robj = RecentScansDB.objects.filter(MD5=checksum) if not robj.exists(): - msg = 'The file is not uploaded/available' - return print_n_send_error_response(request, msg, api) + return print_n_send_error_response( + request, + 'The file is not uploaded/available', + api) file_type = robj[0].SCAN_TYPE filename = robj[0].FILE_NAME allowed_exts = ('ios', '.ipa', '.zip', '.dylib', '.a') allowed_typ = [i.replace('.', '') for i in allowed_exts] if (not filename.lower().endswith(allowed_exts) or file_type not in allowed_typ): - msg = 'Invalid file extension or file type' - return print_n_send_error_response(request, msg, api) + return print_n_send_error_response( + request, + 'Invalid file extension or file type', + api) app_dict['directory'] = Path(settings.BASE_DIR) # BASE DIR app_dict['file_name'] = filename # APP ORIGINAL NAME diff --git a/mobsf/StaticAnalyzer/views/windows/windows.py b/mobsf/StaticAnalyzer/views/windows/windows.py index 9d4f051023..6f6a3fb12d 100755 --- a/mobsf/StaticAnalyzer/views/windows/windows.py +++ b/mobsf/StaticAnalyzer/views/windows/windows.py @@ -25,6 +25,7 @@ from mobsf.MobSF.utils import ( file_size, get_config_loc, + is_md5, print_n_send_error_response, ) from mobsf.MobSF.views.home import update_scan_timestamp @@ -70,18 +71,24 @@ def staticanalyzer_windows(request, checksum, api=False): re_scan = request.GET.get('rescan', 0) if re_scan == '1': rescan = True - if not re.match('^[0-9a-f]{32}$', checksum): - msg = 'Invalid checksum' - return print_n_send_error_response(request, msg, api) + if not is_md5(checksum): + return print_n_send_error_response( + request, + 'Invalid Hash', + api) robj = RecentScansDB.objects.filter(MD5=checksum) if not robj.exists(): - msg = 'The file is not uploaded/available' - return print_n_send_error_response(request, msg, api) + return print_n_send_error_response( + request, + 'The file is not uploaded/available', + api) typ = robj[0].SCAN_TYPE filename = robj[0].FILE_NAME if typ != 'appx': - msg = 'File type not supported' - return print_n_send_error_response(request, msg, api) + return print_n_send_error_response( + request, + 'File type not supported', + api) app_dic['app_name'] = filename # APP ORIGINAL NAME app_dic['md5'] = checksum diff --git a/mobsf/install/windows/setup.py b/mobsf/install/windows/setup.py index 3ff454bcdc..fb64712e8d 100755 --- a/mobsf/install/windows/setup.py +++ b/mobsf/install/windows/setup.py @@ -13,7 +13,7 @@ import sys from os.path import expanduser -from six.moves import input +from six.moves import input as sinput try: import urllib.request as urlrequest @@ -315,7 +315,7 @@ def generate_secret(): 'mobsf/MobSF/windows_vm_priv_key.asc)' .format(CONFIG['MobSF']['priv_key'], config_path) )) - input('Please press any key when done..') + sinput('Please press any key when done..') def autostart(): diff --git a/mobsf/static/others/css/devices.min.css b/mobsf/static/others/css/devices.min.css index e64d9f734a..8d63bd92d9 100644 --- a/mobsf/static/others/css/devices.min.css +++ b/mobsf/static/others/css/devices.min.css @@ -1 +1 @@ -.marvel-device{display:inline-block;position:relative;box-sizing:content-box !important}.marvel-device .screen{width:100%;position:relative;height:100%;color:white;z-index:2;text-align:center;display:block;-webkit-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 0 0 3px #111;box-shadow:0 0 0 3px #111}.marvel-device .top-bar,.marvel-device .bottom-bar{height:3px;background:black;width:100%;display:block}.marvel-device .middle-bar{width:3px;height:4px;top:0px;left:90px;background:black;position:absolute}.marvel-device.iphone6{width:375px;height:667px;padding:105px 24px;background:#d9dbdc;-webkit-border-radius:56px;border-radius:56px;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2)}.marvel-device.iphone6:before{width:calc(100% - 12px);height:calc(100% - 12px);position:absolute;top:6px;content:'';left:6px;-webkit-border-radius:50px;border-radius:50px;background:#f8f8f8;z-index:1}.marvel-device.iphone6:after{width:calc(100% - 16px);height:calc(100% - 16px);position:absolute;top:8px;content:'';left:8px;-webkit-border-radius:48px;border-radius:48px;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #fff;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #fff;z-index:2}.marvel-device.iphone6 .home{-webkit-border-radius:100%;border-radius:100%;width:68px;height:68px;position:absolute;left:50%;margin-left:-34px;bottom:22px;z-index:3;background:#303233;background:-moz-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #303233), color-stop(50%, #b5b7b9), color-stop(69%, #f0f2f2), color-stop(100%, #303233));background:-webkit-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:-o-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:-ms-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:linear-gradient(135deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#303233', endColorstr='#303233',GradientType=1 )}.marvel-device.iphone6 .home:before{background:#f8f8f8;position:absolute;content:'';-webkit-border-radius:100%;border-radius:100%;width:calc(100% - 8px);height:calc(100% - 8px);top:4px;left:4px}.marvel-device.iphone6 .top-bar{height:14px;background:#bfbfc0;position:absolute;top:68px;left:0}.marvel-device.iphone6 .bottom-bar{height:14px;background:#bfbfc0;position:absolute;bottom:68px;left:0}.marvel-device.iphone6 .sleep{position:absolute;top:190px;right:-4px;width:4px;height:66px;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px;background:#d9dbdc}.marvel-device.iphone6 .volume{position:absolute;left:-4px;top:188px;z-index:0;height:66px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:#d9dbdc}.marvel-device.iphone6 .volume:before{position:absolute;left:2px;top:-78px;height:40px;width:2px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone6 .volume:after{position:absolute;left:0px;top:82px;height:66px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone6 .camera{background:#3c3d3d;width:12px;height:12px;position:absolute;top:24px;left:50%;margin-left:-6px;-webkit-border-radius:100%;border-radius:100%;z-index:3}.marvel-device.iphone6 .sensor{background:#3c3d3d;width:16px;height:16px;position:absolute;top:49px;left:134px;z-index:3;-webkit-border-radius:100%;border-radius:100%}.marvel-device.iphone6 .speaker{background:#292728;width:70px;height:6px;position:absolute;top:54px;left:50%;margin-left:-35px;-webkit-border-radius:6px;border-radius:6px;z-index:3}.marvel-device.iphone6.gold{background:#f9e7d3}.marvel-device.iphone6.gold .top-bar,.marvel-device.iphone6.gold .bottom-bar{background:white}.marvel-device.iphone6.gold .sleep,.marvel-device.iphone6.gold .volume{background:#f9e7d3}.marvel-device.iphone6.gold .home{background:#cebba9;background:-moz-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #cebba9), color-stop(50%, #f9e7d3), color-stop(100%, #cebba9));background:-webkit-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:-o-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:-ms-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:linear-gradient(135deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#cebba9', endColorstr='#cebba9',GradientType=1 )}.marvel-device.iphone6.black{background:#464646;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.7);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.7)}.marvel-device.iphone6.black:before{background:#080808}.marvel-device.iphone6.black:after{-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #212121;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #212121}.marvel-device.iphone6.black .top-bar,.marvel-device.iphone6.black .bottom-bar{background:#212121}.marvel-device.iphone6.black .volume,.marvel-device.iphone6.black .sleep{background:#464646}.marvel-device.iphone6.black .camera{background:#080808}.marvel-device.iphone6.black .home{background:#080808;background:-moz-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #080808), color-stop(50%, #464646), color-stop(100%, #080808));background:-webkit-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:-o-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:-ms-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:linear-gradient(135deg, #080808 0%, #464646 50%, #080808 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#080808', endColorstr='#080808',GradientType=1 )}.marvel-device.iphone6.black .home:before{background:#080808}.marvel-device.iphone6.landscape{padding:24px 105px;height:375px;width:667px}.marvel-device.iphone6.landscape .sleep{top:100%;-webkit-border-radius:0px 0px 2px 2px;border-radius:0px 0px 2px 2px;right:190px;height:4px;width:66px}.marvel-device.iphone6.landscape .volume{width:66px;height:4px;top:-4px;left:calc(100% - 188px - 66px);-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone6.landscape .volume:before{width:40px;height:2px;top:2px;right:-78px;left:auto;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone6.landscape .volume:after{left:-82px;width:66px;height:4px;top:0;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone6.landscape .top-bar{width:14px;height:100%;left:calc(100% - 68px - 14px);top:0}.marvel-device.iphone6.landscape .bottom-bar{width:14px;height:100%;left:68px;top:0}.marvel-device.iphone6.landscape .home{top:50%;margin-top:-34px;margin-left:0;left:22px}.marvel-device.iphone6.landscape .sensor{top:134px;left:calc(100% - 49px - 16px)}.marvel-device.iphone6.landscape .speaker{height:70px;width:6px;left:calc(100% - 54px - 6px);top:50%;margin-left:0px;margin-top:-35px}.marvel-device.iphone6.landscape .camera{left:calc(100% - 32px);top:50%;margin-left:0px;margin-top:-5px}.marvel-device.iphone6plus{width:414px;height:736px;padding:112px 26px;background:#d9dbdc;-webkit-border-radius:56px;border-radius:56px;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2)}.marvel-device.iphone6plus:before{width:calc(100% - 12px);height:calc(100% - 12px);position:absolute;top:6px;content:'';left:6px;-webkit-border-radius:50px;border-radius:50px;background:#f8f8f8;z-index:1}.marvel-device.iphone6plus:after{width:calc(100% - 16px);height:calc(100% - 16px);position:absolute;top:8px;content:'';left:8px;-webkit-border-radius:48px;border-radius:48px;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #fff;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #fff;z-index:2}.marvel-device.iphone6plus .home{-webkit-border-radius:100%;border-radius:100%;width:68px;height:68px;position:absolute;left:50%;margin-left:-34px;bottom:24px;z-index:3;background:#303233;background:-moz-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #303233), color-stop(50%, #b5b7b9), color-stop(69%, #f0f2f2), color-stop(100%, #303233));background:-webkit-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:-o-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:-ms-linear-gradient(-45deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);background:linear-gradient(135deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#303233', endColorstr='#303233',GradientType=1 )}.marvel-device.iphone6plus .home:before{background:#f8f8f8;position:absolute;content:'';-webkit-border-radius:100%;border-radius:100%;width:calc(100% - 8px);height:calc(100% - 8px);top:4px;left:4px}.marvel-device.iphone6plus .top-bar{height:14px;background:#bfbfc0;position:absolute;top:68px;left:0}.marvel-device.iphone6plus .bottom-bar{height:14px;background:#bfbfc0;position:absolute;bottom:68px;left:0}.marvel-device.iphone6plus .sleep{position:absolute;top:190px;right:-4px;width:4px;height:66px;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px;background:#d9dbdc}.marvel-device.iphone6plus .volume{position:absolute;left:-4px;top:188px;z-index:0;height:66px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:#d9dbdc}.marvel-device.iphone6plus .volume:before{position:absolute;left:2px;top:-78px;height:40px;width:2px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone6plus .volume:after{position:absolute;left:0px;top:82px;height:66px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone6plus .camera{background:#3c3d3d;width:12px;height:12px;position:absolute;top:29px;left:50%;margin-left:-6px;-webkit-border-radius:100%;border-radius:100%;z-index:3}.marvel-device.iphone6plus .sensor{background:#3c3d3d;width:16px;height:16px;position:absolute;top:54px;left:154px;z-index:3;-webkit-border-radius:100%;border-radius:100%}.marvel-device.iphone6plus .speaker{background:#292728;width:70px;height:6px;position:absolute;top:59px;left:50%;margin-left:-35px;-webkit-border-radius:6px;border-radius:6px;z-index:3}.marvel-device.iphone6plus.gold{background:#f9e7d3}.marvel-device.iphone6plus.gold .top-bar,.marvel-device.iphone6plus.gold .bottom-bar{background:white}.marvel-device.iphone6plus.gold .sleep,.marvel-device.iphone6plus.gold .volume{background:#f9e7d3}.marvel-device.iphone6plus.gold .home{background:#cebba9;background:-moz-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #cebba9), color-stop(50%, #f9e7d3), color-stop(100%, #cebba9));background:-webkit-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:-o-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:-ms-linear-gradient(-45deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);background:linear-gradient(135deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#cebba9', endColorstr='#cebba9',GradientType=1 )}.marvel-device.iphone6plus.black{background:#464646;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.7);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.7)}.marvel-device.iphone6plus.black:before{background:#080808}.marvel-device.iphone6plus.black:after{-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #212121;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #212121}.marvel-device.iphone6plus.black .top-bar,.marvel-device.iphone6plus.black .bottom-bar{background:#212121}.marvel-device.iphone6plus.black .volume,.marvel-device.iphone6plus.black .sleep{background:#464646}.marvel-device.iphone6plus.black .camera{background:#080808}.marvel-device.iphone6plus.black .home{background:#080808;background:-moz-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:-webkit-gradient(linear, left top, right bottom, color-stop(0%, #080808), color-stop(50%, #464646), color-stop(100%, #080808));background:-webkit-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:-o-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:-ms-linear-gradient(-45deg, #080808 0%, #464646 50%, #080808 100%);background:linear-gradient(135deg, #080808 0%, #464646 50%, #080808 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#080808', endColorstr='#080808',GradientType=1 )}.marvel-device.iphone6plus.black .home:before{background:#080808}.marvel-device.iphone6plus.landscape{padding:26px 112px;height:414px;width:736px}.marvel-device.iphone6plus.landscape .sleep{top:100%;-webkit-border-radius:0px 0px 2px 2px;border-radius:0px 0px 2px 2px;right:190px;height:4px;width:66px}.marvel-device.iphone6plus.landscape .volume{width:66px;height:4px;top:-4px;left:calc(100% - 188px - 66px);-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone6plus.landscape .volume:before{width:40px;height:2px;top:2px;right:-78px;left:auto;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone6plus.landscape .volume:after{left:-82px;width:66px;height:4px;top:0;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone6plus.landscape .top-bar{width:14px;height:100%;left:calc(100% - 68px - 14px);top:0}.marvel-device.iphone6plus.landscape .bottom-bar{width:14px;height:100%;left:68px;top:0}.marvel-device.iphone6plus.landscape .home{top:50%;margin-top:-34px;margin-left:0;left:24px}.marvel-device.iphone6plus.landscape .sensor{top:154px;left:calc(100% - 54px - 16px)}.marvel-device.iphone6plus.landscape .speaker{height:70px;width:6px;left:calc(100% - 59px - 6px);top:50%;margin-left:0px;margin-top:-35px}.marvel-device.iphone6plus.landscape .camera{left:calc(100% - 29px);top:50%;margin-left:0px;margin-top:-5px}.marvel-device.iphone5s,.marvel-device.iphone5c{padding:105px 22px;background:#2c2b2c;width:320px;height:568px;-webkit-border-radius:50px;border-radius:50px}.marvel-device.iphone5s:before,.marvel-device.iphone5c:before{width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;top:4px;content:'';left:4px;-webkit-border-radius:46px;border-radius:46px;background:#1e1e1e;z-index:1}.marvel-device.iphone5s .sleep,.marvel-device.iphone5c .sleep{position:absolute;top:-4px;right:60px;width:60px;height:4px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px;background:#282727}.marvel-device.iphone5s .volume,.marvel-device.iphone5c .volume{position:absolute;left:-4px;top:180px;z-index:0;height:27px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:#282727}.marvel-device.iphone5s .volume:before,.marvel-device.iphone5c .volume:before{position:absolute;left:0px;top:-75px;height:35px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone5s .volume:after,.marvel-device.iphone5c .volume:after{position:absolute;left:0px;bottom:-64px;height:27px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone5s .camera,.marvel-device.iphone5c .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:32px;left:50%;margin-left:-5px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;z-index:3}.marvel-device.iphone5s .sensor,.marvel-device.iphone5c .sensor{background:#3c3d3d;width:10px;height:10px;position:absolute;top:60px;left:160px;z-index:3;margin-left:-32px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px}.marvel-device.iphone5s .speaker,.marvel-device.iphone5c .speaker{background:#292728;width:64px;height:10px;position:absolute;top:60px;left:50%;margin-left:-32px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;z-index:3}.marvel-device.iphone5s.landscape,.marvel-device.iphone5c.landscape{padding:22px 105px;height:320px;width:568px}.marvel-device.iphone5s.landscape .sleep,.marvel-device.iphone5c.landscape .sleep{right:-4px;top:calc(100% - 120px);height:60px;width:4px;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px}.marvel-device.iphone5s.landscape .volume,.marvel-device.iphone5c.landscape .volume{width:27px;height:4px;top:-4px;left:calc(100% - 180px);-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone5s.landscape .volume:before,.marvel-device.iphone5c.landscape .volume:before{width:35px;height:4px;top:0px;right:-75px;left:auto;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone5s.landscape .volume:after,.marvel-device.iphone5c.landscape .volume:after{bottom:0px;left:-64px;z-index:999;height:4px;width:27px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone5s.landscape .sensor,.marvel-device.iphone5c.landscape .sensor{top:160px;left:calc(100% - 60px);margin-left:0px;margin-top:-32px}.marvel-device.iphone5s.landscape .speaker,.marvel-device.iphone5c.landscape .speaker{height:64px;width:10px;left:calc(100% - 60px);top:50%;margin-left:0px;margin-top:-32px}.marvel-device.iphone5s.landscape .camera,.marvel-device.iphone5c.landscape .camera{left:calc(100% - 32px);top:50%;margin-left:0px;margin-top:-5px}.marvel-device.iphone5s .home{-moz-border-radius:36px;-webkit-border-radius:36px;border-radius:36px;width:68px;-webkit-box-shadow:inset 0 0 0 4px #2c2b2c;box-shadow:inset 0 0 0 4px #2c2b2c;height:68px;position:absolute;left:50%;margin-left:-34px;bottom:19px;z-index:3}.marvel-device.iphone5s .top-bar{top:70px;position:absolute;left:0}.marvel-device.iphone5s .bottom-bar{bottom:70px;position:absolute;left:0}.marvel-device.iphone5s.landscape .home{left:19px;bottom:50%;margin-bottom:-34px;margin-left:0px}.marvel-device.iphone5s.landscape .top-bar{left:70px;top:0px;width:3px;height:100%}.marvel-device.iphone5s.landscape .bottom-bar{right:70px;left:auto;bottom:0px;width:3px;height:100%}.marvel-device.iphone5s.silver{background:#bcbcbc}.marvel-device.iphone5s.silver:before{background:#fcfcfc}.marvel-device.iphone5s.silver .volume,.marvel-device.iphone5s.silver .sleep{background:#d6d6d6}.marvel-device.iphone5s.silver .top-bar,.marvel-device.iphone5s.silver .bottom-bar{background:#eaebec}.marvel-device.iphone5s.silver .home{-webkit-box-shadow:inset 0 0 0 4px #bcbcbc;box-shadow:inset 0 0 0 4px #bcbcbc}.marvel-device.iphone5s.gold{background:#f9e7d3}.marvel-device.iphone5s.gold:before{background:#fcfcfc}.marvel-device.iphone5s.gold .volume,.marvel-device.iphone5s.gold .sleep{background:#f9e7d3}.marvel-device.iphone5s.gold .top-bar,.marvel-device.iphone5s.gold .bottom-bar{background:white}.marvel-device.iphone5s.gold .home{-webkit-box-shadow:inset 0 0 0 4px #f9e7d3;box-shadow:inset 0 0 0 4px #f9e7d3}.marvel-device.iphone5c{background:white;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,0.2);box-shadow:0 1px 2px 0 rgba(0,0,0,0.2)}.marvel-device.iphone5c .top-bar,.marvel-device.iphone5c .bottom-bar{display:none}.marvel-device.iphone5c .home{background:#242324;-moz-border-radius:36px;-webkit-border-radius:36px;border-radius:36px;width:68px;height:68px;z-index:3;position:absolute;left:50%;margin-left:-34px;bottom:19px}.marvel-device.iphone5c .home:after{width:20px;height:20px;border:1px solid rgba(255,255,255,0.1);-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;position:absolute;display:block;content:'';top:50%;left:50%;margin-top:-11px;margin-left:-11px}.marvel-device.iphone5c.landscape .home{left:19px;bottom:50%;margin-bottom:-34px;margin-left:0px}.marvel-device.iphone5c .volume,.marvel-device.iphone5c .sleep{background:#dddddd}.marvel-device.iphone5c.red{background:#f96b6c}.marvel-device.iphone5c.red .volume,.marvel-device.iphone5c.red .sleep{background:#ed5758}.marvel-device.iphone5c.yellow{background:#f2dc60}.marvel-device.iphone5c.yellow .volume,.marvel-device.iphone5c.yellow .sleep{background:#e5ce4c}.marvel-device.iphone5c.green{background:#97e563}.marvel-device.iphone5c.green .volume,.marvel-device.iphone5c.green .sleep{background:#85d94d}.marvel-device.iphone5c.blue{background:#33a2db}.marvel-device.iphone5c.blue .volume,.marvel-device.iphone5c.blue .sleep{background:#2694cd}.marvel-device.iphone4s{padding:129px 27px;width:320px;height:480px;background:#686868;-webkit-border-radius:54px;border-radius:54px}.marvel-device.iphone4s:before{content:'';width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;top:4px;left:4px;z-index:1;-webkit-border-radius:50px;border-radius:50px;background:#1e1e1e}.marvel-device.iphone4s .top-bar{top:60px;position:absolute;left:0}.marvel-device.iphone4s .bottom-bar{bottom:90px;position:absolute;left:0}.marvel-device.iphone4s .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:72px;left:134px;z-index:3;margin-left:-5px;-webkit-border-radius:100%;border-radius:100%}.marvel-device.iphone4s .speaker{background:#292728;width:64px;height:10px;position:absolute;top:72px;left:50%;z-index:3;margin-left:-32px;-webkit-border-radius:5px;border-radius:5px}.marvel-device.iphone4s .sensor{background:#292728;width:40px;height:10px;position:absolute;top:36px;left:50%;z-index:3;margin-left:-20px;-webkit-border-radius:5px;border-radius:5px}.marvel-device.iphone4s .home{background:#242324;-webkit-border-radius:100%;border-radius:100%;width:72px;height:72px;z-index:3;position:absolute;left:50%;margin-left:-36px;bottom:30px}.marvel-device.iphone4s .home:after{width:20px;height:20px;border:1px solid rgba(255,255,255,0.1);-webkit-border-radius:4px;border-radius:4px;position:absolute;display:block;content:'';top:50%;left:50%;margin-top:-11px;margin-left:-11px}.marvel-device.iphone4s .sleep{position:absolute;top:-4px;right:60px;width:60px;height:4px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px;background:#4D4D4D}.marvel-device.iphone4s .volume{position:absolute;left:-4px;top:160px;height:27px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:#4D4D4D}.marvel-device.iphone4s .volume:before{position:absolute;left:0px;top:-70px;height:35px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone4s .volume:after{position:absolute;left:0px;bottom:-64px;height:27px;width:4px;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone4s.landscape{padding:27px 129px;height:320px;width:480px}.marvel-device.iphone4s.landscape .bottom-bar{left:90px;bottom:0px;height:100%;width:3px}.marvel-device.iphone4s.landscape .top-bar{left:calc(100% - 60px);top:0px;height:100%;width:3px}.marvel-device.iphone4s.landscape .camera{top:134px;left:calc(100% - 72px);margin-left:0}.marvel-device.iphone4s.landscape .speaker{top:50%;margin-left:0;margin-top:-32px;left:calc(100% - 72px);width:10px;height:64px}.marvel-device.iphone4s.landscape .sensor{height:40px;width:10px;left:calc(100% - 36px);top:50%;margin-left:0;margin-top:-20px}.marvel-device.iphone4s.landscape .home{left:30px;bottom:50%;margin-left:0;margin-bottom:-36px}.marvel-device.iphone4s.landscape .sleep{height:60px;width:4px;right:-4px;top:calc(100% - 120px);-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px}.marvel-device.iphone4s.landscape .volume{top:-4px;left:calc(100% - 187px);height:4px;width:27px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone4s.landscape .volume:before{right:-70px;left:auto;top:0px;width:35px;height:4px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone4s.landscape .volume:after{width:27px;height:4px;bottom:0px;left:-64px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.iphone4s.silver{background:#bcbcbc}.marvel-device.iphone4s.silver:before{background:#fcfcfc}.marvel-device.iphone4s.silver .home{background:#fcfcfc;-webkit-box-shadow:inset 0 0 0 1px #bcbcbc;box-shadow:inset 0 0 0 1px #bcbcbc}.marvel-device.iphone4s.silver .home:after{border:1px solid rgba(0,0,0,0.2)}.marvel-device.iphone4s.silver .volume,.marvel-device.iphone4s.silver .sleep{background:#d6d6d6}.marvel-device.nexus5{padding:50px 15px 50px 15px;width:320px;height:568px;background:#1e1e1e;-webkit-border-radius:20px;border-radius:20px}.marvel-device.nexus5:before{-webkit-border-radius:600px / 50px;border-radius:600px / 50px;background:inherit;content:'';top:0;position:absolute;height:103.1%;width:calc(100% - 26px);top:50%;left:50%;-moz-transform:translateX(-50%) translateY(-50%);-webkit-transform:translateX(-50%) translateY(-50%);-o-transform:translateX(-50%) translateY(-50%);-ms-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.marvel-device.nexus5 .top-bar{width:calc(100% - 8px);height:calc(100% - 6px);position:absolute;top:3px;left:4px;-webkit-border-radius:20px;border-radius:20px;background:#181818}.marvel-device.nexus5 .top-bar:before{-webkit-border-radius:600px / 50px;border-radius:600px / 50px;background:inherit;content:'';top:0;position:absolute;height:103.0%;width:calc(100% - 26px);top:50%;left:50%;-moz-transform:translateX(-50%) translateY(-50%);-webkit-transform:translateX(-50%) translateY(-50%);-o-transform:translateX(-50%) translateY(-50%);-ms-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.marvel-device.nexus5 .bottom-bar{display:none}.marvel-device.nexus5 .sleep{width:3px;position:absolute;left:-3px;top:110px;height:100px;background:inherit;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px}.marvel-device.nexus5 .volume{width:3px;position:absolute;right:-3px;top:70px;height:45px;background:inherit;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px}.marvel-device.nexus5 .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:18px;left:50%;z-index:3;margin-left:-5px;-webkit-border-radius:100%;border-radius:100%}.marvel-device.nexus5 .camera:before{background:#3c3d3d;width:6px;height:6px;content:'';display:block;position:absolute;top:2px;left:-100px;z-index:3;-webkit-border-radius:100%;border-radius:100%}.marvel-device.nexus5.landscape{padding:15px 50px 15px 50px;height:320px;width:568px}.marvel-device.nexus5.landscape:before{width:103.1%;height:calc(100% - 26px);-webkit-border-radius:50px / 600px;border-radius:50px / 600px}.marvel-device.nexus5.landscape .top-bar{left:3px;top:4px;height:calc(100% - 8px);width:calc(100% - 6px)}.marvel-device.nexus5.landscape .top-bar:before{width:103%;height:calc(100% - 26px);-webkit-border-radius:50px / 600px;border-radius:50px / 600px}.marvel-device.nexus5.landscape .sleep{height:3px;width:100px;left:calc(100% - 210px);top:-3px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.nexus5.landscape .volume{height:3px;width:45px;right:70px;top:100%;-webkit-border-radius:0px 0px 2px 2px;border-radius:0px 0px 2px 2px}.marvel-device.nexus5.landscape .camera{top:50%;left:calc(100% - 18px);margin-left:0;margin-top:-5px}.marvel-device.nexus5.landscape .camera:before{top:-100px;left:2px}.marvel-device.s5{padding:60px 18px;-webkit-border-radius:42px;border-radius:42px;width:320px;height:568px;background:#bcbcbc}.marvel-device.s5:before,.marvel-device.s5:after{width:calc(100% - 52px);content:'';display:block;height:26px;background:inherit;position:absolute;-webkit-border-radius:500px / 40px;border-radius:500px / 40px;left:50%;-moz-transform:translateX(-50%);-webkit-transform:translateX(-50%);-o-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.marvel-device.s5:before{top:-7px}.marvel-device.s5:after{bottom:-7px}.marvel-device.s5 .bottom-bar{display:none}.marvel-device.s5 .top-bar{-webkit-border-radius:37px;border-radius:37px;width:calc(100% - 10px);height:calc(100% - 10px);top:5px;left:5px;background:radial-gradient(rgba(0,0,0,0.02) 20%, transparent 60%) 0 0,radial-gradient(rgba(0,0,0,0.02) 20%, transparent 60%) 3px 3px;background-color:white;background-size:4px 4px;background-position:center;z-index:2;position:absolute}.marvel-device.s5 .top-bar:before,.marvel-device.s5 .top-bar:after{width:calc(100% - 48px);content:'';display:block;height:26px;background:inherit;position:absolute;-webkit-border-radius:500px / 40px;border-radius:500px / 40px;left:50%;-moz-transform:translateX(-50%);-webkit-transform:translateX(-50%);-o-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.marvel-device.s5 .top-bar:before{top:-7px}.marvel-device.s5 .top-bar:after{bottom:-7px}.marvel-device.s5 .sleep{width:3px;position:absolute;left:-3px;top:100px;height:100px;background:#cecece;-webkit-border-radius:2px 0px 0px 2px;border-radius:2px 0px 0px 2px}.marvel-device.s5 .speaker{width:68px;height:8px;position:absolute;top:20px;display:block;z-index:3;left:50%;margin-left:-34px;background-color:#bcbcbc;background-position:top left;-webkit-border-radius:4px;border-radius:4px}.marvel-device.s5 .sensor{display:block;position:absolute;top:20px;right:110px;background:#3c3d3d;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;width:8px;height:8px;z-index:3}.marvel-device.s5 .sensor:after{display:block;content:'';position:absolute;top:0px;right:12px;background:#3c3d3d;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;width:8px;height:8px;z-index:3}.marvel-device.s5 .camera{display:block;position:absolute;top:24px;right:42px;background:black;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;width:10px;height:10px;z-index:3}.marvel-device.s5 .camera:before{width:4px;height:4px;background:#3c3d3d;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;position:absolute;content:'';top:50%;left:50%;margin-top:-2px;margin-left:-2px}.marvel-device.s5 .home{position:absolute;z-index:3;bottom:17px;left:50%;width:70px;height:20px;background:white;-webkit-border-radius:18px;border-radius:18px;display:block;margin-left:-35px;border:2px solid black}.marvel-device.s5.landscape{padding:18px 60px;height:320px;width:568px}.marvel-device.s5.landscape:before,.marvel-device.s5.landscape:after{height:calc(100% - 52px);width:26px;-webkit-border-radius:40px / 500px;border-radius:40px / 500px;-moz-transform:translateY(-50%);-webkit-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.marvel-device.s5.landscape:before{top:50%;left:-7px}.marvel-device.s5.landscape:after{top:50%;left:auto;right:-7px}.marvel-device.s5.landscape .top-bar:before,.marvel-device.s5.landscape .top-bar:after{width:26px;height:calc(100% - 48px);-webkit-border-radius:40px / 500px;border-radius:40px / 500px;-moz-transform:translateY(-50%);-webkit-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.marvel-device.s5.landscape .top-bar:before{right:-7px;top:50%;left:auto}.marvel-device.s5.landscape .top-bar:after{left:-7px;top:50%;right:auto}.marvel-device.s5.landscape .sleep{height:3px;width:100px;left:calc(100% - 200px);top:-3px;-webkit-border-radius:2px 2px 0px 0px;border-radius:2px 2px 0px 0px}.marvel-device.s5.landscape .speaker{height:68px;width:8px;left:calc(100% - 20px);top:50%;margin-left:0;margin-top:-34px}.marvel-device.s5.landscape .sensor{right:20px;top:calc(100% - 110px)}.marvel-device.s5.landscape .sensor:after{left:-12px;right:0px}.marvel-device.s5.landscape .camera{top:calc(100% - 42px);right:24px}.marvel-device.s5.landscape .home{width:20px;height:70px;bottom:50%;margin-bottom:-35px;margin-left:0;left:17px}.marvel-device.s5.black{background:#1e1e1e}.marvel-device.s5.black .speaker{background:black}.marvel-device.s5.black .sleep{background:#1e1e1e}.marvel-device.s5.black .top-bar{background:radial-gradient(rgba(0,0,0,0.05) 20%, transparent 60%) 0 0,radial-gradient(rgba(0,0,0,0.05) 20%, transparent 60%) 3px 3px;background-color:#2c2b2c;background-size:4px 4px}.marvel-device.s5.black .home{background:#2c2b2c}.marvel-device.lumia920{padding:80px 35px 125px 35px;background:#ffdd00;width:320px;height:533px;-moz-border-radius:40px / 3px;-webkit-border-radius:40px / 3px;border-radius:40px / 3px}.marvel-device.lumia920 .bottom-bar{display:none}.marvel-device.lumia920 .top-bar{width:calc(100% - 24px);height:calc(100% - 32px);position:absolute;top:16px;left:12px;-moz-border-radius:24px;-webkit-border-radius:24px;border-radius:24px;background:black;z-index:1}.marvel-device.lumia920 .top-bar:before{background:#1e1e1e;display:block;content:'';width:calc(100% - 4px);height:calc(100% - 4px);top:2px;left:2px;position:absolute;-moz-border-radius:22px;-webkit-border-radius:22px;border-radius:22px}.marvel-device.lumia920 .volume{width:3px;position:absolute;top:130px;height:100px;background:#1e1e1e;right:-3px;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px}.marvel-device.lumia920 .volume:before{width:3px;position:absolute;top:190px;content:'';display:block;height:50px;background:inherit;right:0px;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px}.marvel-device.lumia920 .volume:after{width:3px;position:absolute;top:460px;content:'';display:block;height:50px;background:inherit;right:0px;-webkit-border-radius:0px 2px 2px 0px;border-radius:0px 2px 2px 0px}.marvel-device.lumia920 .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:34px;right:130px;z-index:5;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px}.marvel-device.lumia920 .speaker{background:#292728;width:64px;height:10px;position:absolute;top:38px;left:50%;margin-left:-32px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;z-index:3}.marvel-device.lumia920.landscape{padding:35px 80px 35px 125px;height:320px;width:568px;-moz-border-radius:2px / 100px;-webkit-border-radius:2px / 100px;border-radius:2px / 100px}.marvel-device.lumia920.landscape .top-bar{height:calc(100% - 24px);width:calc(100% - 32px);left:16px;top:12px}.marvel-device.lumia920.landscape .volume{height:3px;right:130px;width:100px;top:100%;-webkit-border-radius:0px 0px 2px 2px;border-radius:0px 0px 2px 2px}.marvel-device.lumia920.landscape .volume:before{height:3px;right:190px;top:0px;width:50px;-webkit-border-radius:0px 0px 2px 2px;border-radius:0px 0px 2px 2px}.marvel-device.lumia920.landscape .volume:after{height:3px;right:430px;top:0px;width:50px;-webkit-border-radius:0px 0px 2px 2px;border-radius:0px 0px 2px 2px}.marvel-device.lumia920.landscape .camera{right:30px;top:calc(100% - 140px)}.marvel-device.lumia920.landscape .speaker{width:10px;height:64px;top:50%;margin-left:0;margin-top:-32px;left:calc(100% - 48px)}.marvel-device.lumia920.black{background:black}.marvel-device.lumia920.white{background:white;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,0.2);box-shadow:0 1px 2px 0 rgba(0,0,0,0.2)}.marvel-device.lumia920.blue{background:#00acdd}.marvel-device.lumia920.red{background:#CC3E32}.marvel-device.htc-one{padding:72px 25px 100px 25px;width:320px;height:568px;background:#bebebe;-webkit-border-radius:34px;border-radius:34px}.marvel-device.htc-one:before{content:'';display:block;width:calc(100% - 4px);height:calc(100% - 4px);position:absolute;top:2px;left:2px;background:#adadad;-webkit-border-radius:32px;border-radius:32px}.marvel-device.htc-one:after{content:'';display:block;width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;top:4px;left:4px;background:#eeeeee;-webkit-border-radius:30px;border-radius:30px}.marvel-device.htc-one .top-bar{width:calc(100% - 4px);height:635px;position:absolute;background:#424242;top:50px;z-index:1;left:2px}.marvel-device.htc-one .top-bar:before{content:'';position:absolute;width:calc(100% - 4px);height:100%;position:absolute;background:black;top:0px;z-index:1;left:2px}.marvel-device.htc-one .bottom-bar{display:none}.marvel-device.htc-one .speaker{height:16px;width:216px;display:block;position:absolute;top:22px;z-index:2;left:50%;margin-left:-108px;background:radial-gradient(#343434 25%, transparent 50%) 0 0,radial-gradient(#343434 25%, transparent 50%) 4px 4px;background-size:4px 4px;background-position:top left}.marvel-device.htc-one .speaker:after{content:'';height:16px;width:216px;display:block;position:absolute;top:676px;z-index:2;left:50%;margin-left:-108px;background:inherit}.marvel-device.htc-one .camera{display:block;position:absolute;top:18px;right:38px;background:#3c3d3d;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;width:24px;height:24px;z-index:3}.marvel-device.htc-one .camera:before{width:8px;height:8px;background:black;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;position:absolute;content:'';top:50%;left:50%;margin-top:-4px;margin-left:-4px}.marvel-device.htc-one .sensor{display:block;position:absolute;top:29px;left:60px;background:#3c3d3d;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;width:8px;height:8px;z-index:3}.marvel-device.htc-one .sensor:after{display:block;content:'';position:absolute;top:0px;right:12px;background:#3c3d3d;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%;width:8px;height:8px;z-index:3}.marvel-device.htc-one.landscape{padding:25px 72px 25px 100px;height:320px;width:568px}.marvel-device.htc-one.landscape .top-bar{height:calc(100% - 4px);width:635px;left:calc(100% - 685px);top:2px}.marvel-device.htc-one.landscape .speaker{width:16px;height:216px;left:calc(100% - 38px);top:50%;margin-left:0px;margin-top:-108px}.marvel-device.htc-one.landscape .speaker:after{width:16px;height:216px;left:calc(100% - 692px);top:50%;margin-left:0;margin-top:-108px}.marvel-device.htc-one.landscape .camera{right:18px;top:calc(100% - 38px)}.marvel-device.htc-one.landscape .sensor{left:calc(100% - 29px);top:60px}.marvel-device.htc-one.landscape .sensor :after{right:0;top:-12px}.marvel-device.ipad{width:576px;height:768px;padding:90px 25px;background:#242324;-webkit-border-radius:44px;border-radius:44px}.marvel-device.ipad:before{width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;content:'';display:block;top:4px;left:4px;-webkit-border-radius:40px;border-radius:40px;background:#1e1e1e}.marvel-device.ipad .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:44px;left:50%;margin-left:-5px;-webkit-border-radius:100%;border-radius:100%}.marvel-device.ipad .top-bar,.marvel-device.ipad .bottom-bar{display:none}.marvel-device.ipad .home{background:#242324;-webkit-border-radius:36px;border-radius:36px;width:50px;height:50px;position:absolute;left:50%;margin-left:-25px;bottom:22px}.marvel-device.ipad .home:after{width:15px;height:15px;margin-top:-8px;margin-left:-8px;border:1px solid rgba(255,255,255,0.1);-webkit-border-radius:4px;border-radius:4px;position:absolute;display:block;content:'';top:50%;left:50%}.marvel-device.ipad.landscape{height:576px;width:768px;padding:25px 90px}.marvel-device.ipad.landscape .camera{left:calc(100% - 44px);top:50%;margin-left:0;margin-top:-5px}.marvel-device.ipad.landscape .home{top:50%;left:22px;margin-left:0;margin-top:-25px}.marvel-device.ipad.silver{background:#bcbcbc}.marvel-device.ipad.silver:before{background:#fcfcfc}.marvel-device.ipad.silver .home{background:#fcfcfc;-webkit-box-shadow:inset 0 0 0 1px #bcbcbc;box-shadow:inset 0 0 0 1px #bcbcbc}.marvel-device.ipad.silver .home:after{border:1px solid rgba(0,0,0,0.2)}.marvel-device.macbook{width:960px;height:600px;padding:44px 44px 76px;margin:0 auto;background:#bebebe;-webkit-border-radius:34px;border-radius:34px}.marvel-device.macbook:before{width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;content:'';display:block;top:4px;left:4px;-webkit-border-radius:30px;border-radius:30px;background:#1e1e1e}.marvel-device.macbook .top-bar{width:calc(100% + 2 * 70px);height:40px;position:absolute;content:'';display:block;top:680px;left:-70px;border-bottom-left-radius:90px 18px;border-bottom-right-radius:90px 18px;background:#bebebe;-webkit-box-shadow:inset 0px -4px 13px 3px rgba(34,34,34,0.6);-moz-box-shadow:inset 0px -4px 13px 3px rgba(34,34,34,0.6);box-shadow:inset 0px -4px 13px 3px rgba(34,34,34,0.6)}.marvel-device.macbook .top-bar:before{width:100%;height:24px;content:'';display:block;top:0;left:0;background:#f0f0f0;border-bottom:2px solid #aaa;-webkit-border-radius:5px;border-radius:5px;position:relative}.marvel-device.macbook .top-bar:after{width:16%;height:14px;content:'';display:block;top:0;background:#ddd;position:absolute;margin-left:auto;margin-right:auto;left:0;right:0;-webkit-border-radius:0 0 20px 20px;border-radius:0 0 20px 20px;-webkit-box-shadow:inset 0px -3px 10px #999;-moz-box-shadow:inset 0px -3px 10px #999;box-shadow:inset 0px -3px 10px #999}.marvel-device.macbook .bottom-bar{background:transparent;width:calc(100% + 2 * 70px);height:26px;position:absolute;content:'';display:block;top:680px;left:-70px}.marvel-device.macbook .bottom-bar:before,.marvel-device.macbook .bottom-bar:after{height:calc(100% - 2px);width:80px;content:'';display:block;top:0;position:absolute}.marvel-device.macbook .bottom-bar:before{left:0;background:#f0f0f0;background:-moz-linear-gradient(left, #747474 0%, #c3c3c3 5%, #ebebeb 14%, #979797 41%, #f0f0f0 80%, #f0f0f0 100%, #f0f0f0 100%);background:-webkit-gradient(linear, left top, right top, color-stop(0%, #747474), color-stop(5%, #c3c3c3), color-stop(14%, #ebebeb), color-stop(41%, #979797), color-stop(80%, #f0f0f0), color-stop(100%, #f0f0f0), color-stop(100%, #f0f0f0));background:-webkit-linear-gradient(left, #747474 0%, #c3c3c3 5%, #ebebeb 14%, #979797 41%, #f0f0f0 80%, #f0f0f0 100%, #f0f0f0 100%);background:-o-linear-gradient(left, #747474 0%, #c3c3c3 5%, #ebebeb 14%, #979797 41%, #f0f0f0 80%, #f0f0f0 100%, #f0f0f0 100%);background:-ms-linear-gradient(left, #747474 0%, #c3c3c3 5%, #ebebeb 14%, #979797 41%, #f0f0f0 80%, #f0f0f0 100%, #f0f0f0 100%);background:linear-gradient(to right, #747474 0%, #c3c3c3 5%, #ebebeb 14%, #979797 41%, #f0f0f0 80%, #f0f0f0 100%, #f0f0f0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#747474', endColorstr='#f0f0f0', GradientType=1)}.marvel-device.macbook .bottom-bar:after{right:0;background:#f0f0f0;background:-moz-linear-gradient(left, #f0f0f0 0%, #f0f0f0 0%, #f0f0f0 20%, #979797 59%, #ebebeb 86%, #c3c3c3 95%, #747474 100%);background:-webkit-gradient(linear, left top, right top, color-stop(0%, #f0f0f0), color-stop(0%, #f0f0f0), color-stop(20%, #f0f0f0), color-stop(59%, #979797), color-stop(86%, #ebebeb), color-stop(95%, #c3c3c3), color-stop(100%, #747474));background:-webkit-linear-gradient(left, #f0f0f0 0%, #f0f0f0 0%, #f0f0f0 20%, #979797 59%, #ebebeb 86%, #c3c3c3 95%, #747474 100%);background:-o-linear-gradient(left, #f0f0f0 0%, #f0f0f0 0%, #f0f0f0 20%, #979797 59%, #ebebeb 86%, #c3c3c3 95%, #747474 100%);background:-ms-linear-gradient(left, #f0f0f0 0%, #f0f0f0 0%, #f0f0f0 20%, #979797 59%, #ebebeb 86%, #c3c3c3 95%, #747474 100%);background:linear-gradient(to right, #f0f0f0 0%, #f0f0f0 0%, #f0f0f0 20%, #979797 59%, #ebebeb 86%, #c3c3c3 95%, #747474 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f0f0f0', endColorstr='#747474', GradientType=1)}.marvel-device.macbook .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:20px;left:50%;margin-left:-5px;-webkit-border-radius:100%;border-radius:100%}.marvel-device.macbook .home{display:none} +.marvel-device{display:inline-block;position:relative;-webkit-box-sizing:content-box !important;box-sizing:content-box !important}.marvel-device .screen{width:100%;position:relative;height:100%;z-index:3;background:white;overflow:hidden;display:block;border-radius:1px;-webkit-box-shadow:0 0 0 3px #111;box-shadow:0 0 0 3px #111}.marvel-device .top-bar,.marvel-device .bottom-bar{height:3px;background:black;width:100%;display:block}.marvel-device .middle-bar{width:3px;height:4px;top:0px;left:90px;background:black;position:absolute}.marvel-device.iphone8{width:375px;height:667px;padding:105px 24px;background:#d9dbdc;border-radius:56px;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2)}.marvel-device.iphone8:before{width:calc(100% - 12px);height:calc(100% - 12px);position:absolute;top:6px;content:'';left:6px;border-radius:50px;background:#f8f8f8;z-index:1}.marvel-device.iphone8:after{width:calc(100% - 16px);height:calc(100% - 16px);position:absolute;top:8px;content:'';left:8px;border-radius:48px;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #fff;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #fff;z-index:2}.marvel-device.iphone8 .home{border-radius:100%;width:68px;height:68px;position:absolute;left:50%;margin-left:-34px;bottom:22px;z-index:3;background:#303233;background:linear-gradient(135deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%)}.marvel-device.iphone8 .home:before{background:#f8f8f8;position:absolute;content:'';border-radius:100%;width:calc(100% - 8px);height:calc(100% - 8px);top:4px;left:4px}.marvel-device.iphone8 .top-bar{height:14px;background:#bfbfc0;position:absolute;top:68px;left:0}.marvel-device.iphone8 .bottom-bar{height:14px;background:#bfbfc0;position:absolute;bottom:68px;left:0}.marvel-device.iphone8 .sleep{position:absolute;top:190px;right:-4px;width:4px;height:66px;border-radius:0px 2px 2px 0px;background:#d9dbdc}.marvel-device.iphone8 .volume{position:absolute;left:-4px;top:188px;z-index:0;height:66px;width:4px;border-radius:2px 0px 0px 2px;background:#d9dbdc}.marvel-device.iphone8 .volume:before{position:absolute;left:2px;top:-78px;height:40px;width:2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone8 .volume:after{position:absolute;left:0px;top:82px;height:66px;width:4px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone8 .camera{background:#3c3d3d;width:12px;height:12px;position:absolute;top:24px;left:50%;margin-left:-6px;border-radius:100%;z-index:3}.marvel-device.iphone8 .sensor{background:#3c3d3d;width:16px;height:16px;position:absolute;top:49px;left:134px;z-index:3;border-radius:100%}.marvel-device.iphone8 .speaker{background:#292728;width:70px;height:6px;position:absolute;top:54px;left:50%;margin-left:-35px;border-radius:6px;z-index:3}.marvel-device.iphone8.gold{background:#f9e7d3}.marvel-device.iphone8.gold .top-bar,.marvel-device.iphone8.gold .bottom-bar{background:white}.marvel-device.iphone8.gold .sleep,.marvel-device.iphone8.gold .volume{background:#f9e7d3}.marvel-device.iphone8.gold .home{background:#cebba9;background:linear-gradient(135deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%)}.marvel-device.iphone8.black{background:#464646;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.7);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.7)}.marvel-device.iphone8.black:before{background:#080808}.marvel-device.iphone8.black:after{-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #212121;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #212121}.marvel-device.iphone8.black .top-bar,.marvel-device.iphone8.black .bottom-bar{background:#212121}.marvel-device.iphone8.black .volume,.marvel-device.iphone8.black .sleep{background:#464646}.marvel-device.iphone8.black .camera{background:#080808}.marvel-device.iphone8.black .home{background:#080808;background:linear-gradient(135deg, #080808 0%, #464646 50%, #080808 100%)}.marvel-device.iphone8.black .home:before{background:#080808}.marvel-device.iphone8.landscape{padding:24px 105px;height:375px;width:667px}.marvel-device.iphone8.landscape .sleep{top:100%;border-radius:0px 0px 2px 2px;right:190px;height:4px;width:66px}.marvel-device.iphone8.landscape .volume{width:66px;height:4px;top:-4px;left:calc(100% - 188px - 66px);border-radius:2px 2px 0px 0px}.marvel-device.iphone8.landscape .volume:before{width:40px;height:2px;top:2px;right:-78px;left:auto;border-radius:2px 2px 0px 0px}.marvel-device.iphone8.landscape .volume:after{left:-82px;width:66px;height:4px;top:0;border-radius:2px 2px 0px 0px}.marvel-device.iphone8.landscape .top-bar{width:14px;height:100%;left:calc(100% - 68px - 14px);top:0}.marvel-device.iphone8.landscape .bottom-bar{width:14px;height:100%;left:68px;top:0}.marvel-device.iphone8.landscape .home{top:50%;margin-top:-34px;margin-left:0;left:22px}.marvel-device.iphone8.landscape .sensor{top:134px;left:calc(100% - 49px - 16px)}.marvel-device.iphone8.landscape .speaker{height:70px;width:6px;left:calc(100% - 54px - 6px);top:50%;margin-left:0px;margin-top:-35px}.marvel-device.iphone8.landscape .camera{left:calc(100% - 32px);top:50%;margin-left:0px;margin-top:-5px}.marvel-device.iphone8plus{width:414px;height:736px;padding:112px 26px;background:#d9dbdc;border-radius:56px;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2)}.marvel-device.iphone8plus:before{width:calc(100% - 12px);height:calc(100% - 12px);position:absolute;top:6px;content:'';left:6px;border-radius:50px;background:#f8f8f8;z-index:1}.marvel-device.iphone8plus:after{width:calc(100% - 16px);height:calc(100% - 16px);position:absolute;top:8px;content:'';left:8px;border-radius:48px;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #fff;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #fff;z-index:2}.marvel-device.iphone8plus .home{border-radius:100%;width:68px;height:68px;position:absolute;left:50%;margin-left:-34px;bottom:24px;z-index:3;background:#303233;background:linear-gradient(135deg, #303233 0%, #b5b7b9 50%, #f0f2f2 69%, #303233 100%)}.marvel-device.iphone8plus .home:before{background:#f8f8f8;position:absolute;content:'';border-radius:100%;width:calc(100% - 8px);height:calc(100% - 8px);top:4px;left:4px}.marvel-device.iphone8plus .top-bar{height:14px;background:#bfbfc0;position:absolute;top:68px;left:0}.marvel-device.iphone8plus .bottom-bar{height:14px;background:#bfbfc0;position:absolute;bottom:68px;left:0}.marvel-device.iphone8plus .sleep{position:absolute;top:190px;right:-4px;width:4px;height:66px;border-radius:0px 2px 2px 0px;background:#d9dbdc}.marvel-device.iphone8plus .volume{position:absolute;left:-4px;top:188px;z-index:0;height:66px;width:4px;border-radius:2px 0px 0px 2px;background:#d9dbdc}.marvel-device.iphone8plus .volume:before{position:absolute;left:2px;top:-78px;height:40px;width:2px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone8plus .volume:after{position:absolute;left:0px;top:82px;height:66px;width:4px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone8plus .camera{background:#3c3d3d;width:12px;height:12px;position:absolute;top:29px;left:50%;margin-left:-6px;border-radius:100%;z-index:3}.marvel-device.iphone8plus .sensor{background:#3c3d3d;width:16px;height:16px;position:absolute;top:54px;left:154px;z-index:3;border-radius:100%}.marvel-device.iphone8plus .speaker{background:#292728;width:70px;height:6px;position:absolute;top:59px;left:50%;margin-left:-35px;border-radius:6px;z-index:3}.marvel-device.iphone8plus.gold{background:#f9e7d3}.marvel-device.iphone8plus.gold .top-bar,.marvel-device.iphone8plus.gold .bottom-bar{background:white}.marvel-device.iphone8plus.gold .sleep,.marvel-device.iphone8plus.gold .volume{background:#f9e7d3}.marvel-device.iphone8plus.gold .home{background:#cebba9;background:linear-gradient(135deg, #cebba9 0%, #f9e7d3 50%, #cebba9 100%)}.marvel-device.iphone8plus.black{background:#464646;-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.7);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.7)}.marvel-device.iphone8plus.black:before{background:#080808}.marvel-device.iphone8plus.black:after{-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #212121;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.1),inset 0 0 6px 3px #212121}.marvel-device.iphone8plus.black .top-bar,.marvel-device.iphone8plus.black .bottom-bar{background:#212121}.marvel-device.iphone8plus.black .volume,.marvel-device.iphone8plus.black .sleep{background:#464646}.marvel-device.iphone8plus.black .camera{background:#080808}.marvel-device.iphone8plus.black .home{background:#080808;background:linear-gradient(135deg, #080808 0%, #464646 50%, #080808 100%)}.marvel-device.iphone8plus.black .home:before{background:#080808}.marvel-device.iphone8plus.landscape{padding:26px 112px;height:414px;width:736px}.marvel-device.iphone8plus.landscape .sleep{top:100%;border-radius:0px 0px 2px 2px;right:190px;height:4px;width:66px}.marvel-device.iphone8plus.landscape .volume{width:66px;height:4px;top:-4px;left:calc(100% - 188px - 66px);border-radius:2px 2px 0px 0px}.marvel-device.iphone8plus.landscape .volume:before{width:40px;height:2px;top:2px;right:-78px;left:auto;border-radius:2px 2px 0px 0px}.marvel-device.iphone8plus.landscape .volume:after{left:-82px;width:66px;height:4px;top:0;border-radius:2px 2px 0px 0px}.marvel-device.iphone8plus.landscape .top-bar{width:14px;height:100%;left:calc(100% - 68px - 14px);top:0}.marvel-device.iphone8plus.landscape .bottom-bar{width:14px;height:100%;left:68px;top:0}.marvel-device.iphone8plus.landscape .home{top:50%;margin-top:-34px;margin-left:0;left:24px}.marvel-device.iphone8plus.landscape .sensor{top:154px;left:calc(100% - 54px - 16px)}.marvel-device.iphone8plus.landscape .speaker{height:70px;width:6px;left:calc(100% - 59px - 6px);top:50%;margin-left:0px;margin-top:-35px}.marvel-device.iphone8plus.landscape .camera{left:calc(100% - 29px);top:50%;margin-left:0px;margin-top:-5px}.marvel-device.iphone5s,.marvel-device.iphone5c{padding:105px 22px;background:#2c2b2c;width:320px;height:568px;border-radius:50px}.marvel-device.iphone5s:before,.marvel-device.iphone5c:before{width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;top:4px;content:'';left:4px;border-radius:46px;background:#1e1e1e;z-index:1}.marvel-device.iphone5s .sleep,.marvel-device.iphone5c .sleep{position:absolute;top:-4px;right:60px;width:60px;height:4px;border-radius:2px 2px 0px 0px;background:#282727}.marvel-device.iphone5s .volume,.marvel-device.iphone5c .volume{position:absolute;left:-4px;top:180px;z-index:0;height:27px;width:4px;border-radius:2px 0px 0px 2px;background:#282727}.marvel-device.iphone5s .volume:before,.marvel-device.iphone5c .volume:before{position:absolute;left:0px;top:-75px;height:35px;width:4px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone5s .volume:after,.marvel-device.iphone5c .volume:after{position:absolute;left:0px;bottom:-64px;height:27px;width:4px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone5s .camera,.marvel-device.iphone5c .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:32px;left:50%;margin-left:-5px;border-radius:5px;z-index:3}.marvel-device.iphone5s .sensor,.marvel-device.iphone5c .sensor{background:#3c3d3d;width:10px;height:10px;position:absolute;top:60px;left:160px;z-index:3;margin-left:-32px;border-radius:5px}.marvel-device.iphone5s .speaker,.marvel-device.iphone5c .speaker{background:#292728;width:64px;height:10px;position:absolute;top:60px;left:50%;margin-left:-32px;border-radius:5px;z-index:3}.marvel-device.iphone5s.landscape,.marvel-device.iphone5c.landscape{padding:22px 105px;height:320px;width:568px}.marvel-device.iphone5s.landscape .sleep,.marvel-device.iphone5c.landscape .sleep{right:-4px;top:calc(100% - 120px);height:60px;width:4px;border-radius:0px 2px 2px 0px}.marvel-device.iphone5s.landscape .volume,.marvel-device.iphone5c.landscape .volume{width:27px;height:4px;top:-4px;left:calc(100% - 180px);border-radius:2px 2px 0px 0px}.marvel-device.iphone5s.landscape .volume:before,.marvel-device.iphone5c.landscape .volume:before{width:35px;height:4px;top:0px;right:-75px;left:auto;border-radius:2px 2px 0px 0px}.marvel-device.iphone5s.landscape .volume:after,.marvel-device.iphone5c.landscape .volume:after{bottom:0px;left:-64px;z-index:999;height:4px;width:27px;border-radius:2px 2px 0px 0px}.marvel-device.iphone5s.landscape .sensor,.marvel-device.iphone5c.landscape .sensor{top:160px;left:calc(100% - 60px);margin-left:0px;margin-top:-32px}.marvel-device.iphone5s.landscape .speaker,.marvel-device.iphone5c.landscape .speaker{height:64px;width:10px;left:calc(100% - 60px);top:50%;margin-left:0px;margin-top:-32px}.marvel-device.iphone5s.landscape .camera,.marvel-device.iphone5c.landscape .camera{left:calc(100% - 32px);top:50%;margin-left:0px;margin-top:-5px}.marvel-device.iphone5s .home{border-radius:36px;width:68px;-webkit-box-shadow:inset 0 0 0 4px #2c2b2c;box-shadow:inset 0 0 0 4px #2c2b2c;height:68px;position:absolute;left:50%;margin-left:-34px;bottom:19px;z-index:3}.marvel-device.iphone5s .top-bar{top:70px;position:absolute;left:0}.marvel-device.iphone5s .bottom-bar{bottom:70px;position:absolute;left:0}.marvel-device.iphone5s.landscape .home{left:19px;bottom:50%;margin-bottom:-34px;margin-left:0px}.marvel-device.iphone5s.landscape .top-bar{left:70px;top:0px;width:3px;height:100%}.marvel-device.iphone5s.landscape .bottom-bar{right:70px;left:auto;bottom:0px;width:3px;height:100%}.marvel-device.iphone5s.silver{background:#bcbcbc}.marvel-device.iphone5s.silver:before{background:#fcfcfc}.marvel-device.iphone5s.silver .volume,.marvel-device.iphone5s.silver .sleep{background:#d6d6d6}.marvel-device.iphone5s.silver .top-bar,.marvel-device.iphone5s.silver .bottom-bar{background:#eaebec}.marvel-device.iphone5s.silver .home{-webkit-box-shadow:inset 0 0 0 4px #bcbcbc;box-shadow:inset 0 0 0 4px #bcbcbc}.marvel-device.iphone5s.gold{background:#f9e7d3}.marvel-device.iphone5s.gold:before{background:#fcfcfc}.marvel-device.iphone5s.gold .volume,.marvel-device.iphone5s.gold .sleep{background:#f9e7d3}.marvel-device.iphone5s.gold .top-bar,.marvel-device.iphone5s.gold .bottom-bar{background:white}.marvel-device.iphone5s.gold .home{-webkit-box-shadow:inset 0 0 0 4px #f9e7d3;box-shadow:inset 0 0 0 4px #f9e7d3}.marvel-device.iphone5c{background:white;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,0.2);box-shadow:0 1px 2px 0 rgba(0,0,0,0.2)}.marvel-device.iphone5c .top-bar,.marvel-device.iphone5c .bottom-bar{display:none}.marvel-device.iphone5c .home{background:#242324;border-radius:36px;width:68px;height:68px;z-index:3;position:absolute;left:50%;margin-left:-34px;bottom:19px}.marvel-device.iphone5c .home:after{width:20px;height:20px;border:1px solid rgba(255,255,255,0.1);border-radius:4px;position:absolute;display:block;content:'';top:50%;left:50%;margin-top:-11px;margin-left:-11px}.marvel-device.iphone5c.landscape .home{left:19px;bottom:50%;margin-bottom:-34px;margin-left:0px}.marvel-device.iphone5c .volume,.marvel-device.iphone5c .sleep{background:#dddddd}.marvel-device.iphone5c.red{background:#f96b6c}.marvel-device.iphone5c.red .volume,.marvel-device.iphone5c.red .sleep{background:#ed5758}.marvel-device.iphone5c.yellow{background:#f2dc60}.marvel-device.iphone5c.yellow .volume,.marvel-device.iphone5c.yellow .sleep{background:#e5ce4c}.marvel-device.iphone5c.green{background:#97e563}.marvel-device.iphone5c.green .volume,.marvel-device.iphone5c.green .sleep{background:#85d94d}.marvel-device.iphone5c.blue{background:#33a2db}.marvel-device.iphone5c.blue .volume,.marvel-device.iphone5c.blue .sleep{background:#2694cd}.marvel-device.iphone4s{padding:129px 27px;width:320px;height:480px;background:#686868;border-radius:54px}.marvel-device.iphone4s:before{content:'';width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;top:4px;left:4px;z-index:1;border-radius:50px;background:#1e1e1e}.marvel-device.iphone4s .top-bar{top:60px;position:absolute;left:0}.marvel-device.iphone4s .bottom-bar{bottom:90px;position:absolute;left:0}.marvel-device.iphone4s .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:72px;left:134px;z-index:3;margin-left:-5px;border-radius:100%}.marvel-device.iphone4s .speaker{background:#292728;width:64px;height:10px;position:absolute;top:72px;left:50%;z-index:3;margin-left:-32px;border-radius:5px}.marvel-device.iphone4s .sensor{background:#292728;width:40px;height:10px;position:absolute;top:36px;left:50%;z-index:3;margin-left:-20px;border-radius:5px}.marvel-device.iphone4s .home{background:#242324;border-radius:100%;width:72px;height:72px;z-index:3;position:absolute;left:50%;margin-left:-36px;bottom:30px}.marvel-device.iphone4s .home:after{width:20px;height:20px;border:1px solid rgba(255,255,255,0.1);border-radius:4px;position:absolute;display:block;content:'';top:50%;left:50%;margin-top:-11px;margin-left:-11px}.marvel-device.iphone4s .sleep{position:absolute;top:-4px;right:60px;width:60px;height:4px;border-radius:2px 2px 0px 0px;background:#4D4D4D}.marvel-device.iphone4s .volume{position:absolute;left:-4px;top:160px;height:27px;width:4px;border-radius:2px 0px 0px 2px;background:#4D4D4D}.marvel-device.iphone4s .volume:before{position:absolute;left:0px;top:-70px;height:35px;width:4px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone4s .volume:after{position:absolute;left:0px;bottom:-64px;height:27px;width:4px;border-radius:2px 0px 0px 2px;background:inherit;content:'';display:block}.marvel-device.iphone4s.landscape{padding:27px 129px;height:320px;width:480px}.marvel-device.iphone4s.landscape .bottom-bar{left:90px;bottom:0px;height:100%;width:3px}.marvel-device.iphone4s.landscape .top-bar{left:calc(100% - 60px);top:0px;height:100%;width:3px}.marvel-device.iphone4s.landscape .camera{top:134px;left:calc(100% - 72px);margin-left:0}.marvel-device.iphone4s.landscape .speaker{top:50%;margin-left:0;margin-top:-32px;left:calc(100% - 72px);width:10px;height:64px}.marvel-device.iphone4s.landscape .sensor{height:40px;width:10px;left:calc(100% - 36px);top:50%;margin-left:0;margin-top:-20px}.marvel-device.iphone4s.landscape .home{left:30px;bottom:50%;margin-left:0;margin-bottom:-36px}.marvel-device.iphone4s.landscape .sleep{height:60px;width:4px;right:-4px;top:calc(100% - 120px);border-radius:0px 2px 2px 0px}.marvel-device.iphone4s.landscape .volume{top:-4px;left:calc(100% - 187px);height:4px;width:27px;border-radius:2px 2px 0px 0px}.marvel-device.iphone4s.landscape .volume:before{right:-70px;left:auto;top:0px;width:35px;height:4px;border-radius:2px 2px 0px 0px}.marvel-device.iphone4s.landscape .volume:after{width:27px;height:4px;bottom:0px;left:-64px;border-radius:2px 2px 0px 0px}.marvel-device.iphone4s.silver{background:#bcbcbc}.marvel-device.iphone4s.silver:before{background:#fcfcfc}.marvel-device.iphone4s.silver .home{background:#fcfcfc;-webkit-box-shadow:inset 0 0 0 1px #bcbcbc;box-shadow:inset 0 0 0 1px #bcbcbc}.marvel-device.iphone4s.silver .home:after{border:1px solid rgba(0,0,0,0.2)}.marvel-device.iphone4s.silver .volume,.marvel-device.iphone4s.silver .sleep{background:#d6d6d6}.marvel-device.nexus5{padding:50px 15px 50px 15px;width:320px;height:568px;background:#1e1e1e;border-radius:20px}.marvel-device.nexus5:before{border-radius:600px / 50px;background:inherit;content:'';top:0;position:absolute;height:103.1%;width:calc(100% - 26px);top:50%;left:50%;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.marvel-device.nexus5 .top-bar{width:calc(100% - 8px);height:calc(100% - 6px);position:absolute;top:3px;left:4px;border-radius:20px;background:#181818}.marvel-device.nexus5 .top-bar:before{border-radius:600px / 50px;background:inherit;content:'';top:0;position:absolute;height:103.0%;width:calc(100% - 26px);top:50%;left:50%;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.marvel-device.nexus5 .bottom-bar{display:none}.marvel-device.nexus5 .sleep{width:3px;position:absolute;left:-3px;top:110px;height:100px;background:inherit;border-radius:2px 0px 0px 2px}.marvel-device.nexus5 .volume{width:3px;position:absolute;right:-3px;top:70px;height:45px;background:inherit;border-radius:0px 2px 2px 0px}.marvel-device.nexus5 .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:18px;left:50%;z-index:3;margin-left:-5px;border-radius:100%}.marvel-device.nexus5 .camera:before{background:#3c3d3d;width:6px;height:6px;content:'';display:block;position:absolute;top:2px;left:-100px;z-index:3;border-radius:100%}.marvel-device.nexus5.landscape{padding:15px 50px 15px 50px;height:320px;width:568px}.marvel-device.nexus5.landscape:before{width:103.1%;height:calc(100% - 26px);border-radius:50px / 600px}.marvel-device.nexus5.landscape .top-bar{left:3px;top:4px;height:calc(100% - 8px);width:calc(100% - 6px)}.marvel-device.nexus5.landscape .top-bar:before{width:103%;height:calc(100% - 26px);border-radius:50px / 600px}.marvel-device.nexus5.landscape .sleep{height:3px;width:100px;left:calc(100% - 210px);top:-3px;border-radius:2px 2px 0px 0px}.marvel-device.nexus5.landscape .volume{height:3px;width:45px;right:70px;top:100%;border-radius:0px 0px 2px 2px}.marvel-device.nexus5.landscape .camera{top:50%;left:calc(100% - 18px);margin-left:0;margin-top:-5px}.marvel-device.nexus5.landscape .camera:before{top:-100px;left:2px}.marvel-device.s5{padding:60px 18px;border-radius:42px;width:320px;height:568px;background:#bcbcbc}.marvel-device.s5:before,.marvel-device.s5:after{width:calc(100% - 52px);content:'';display:block;height:26px;background:inherit;position:absolute;border-radius:500px / 40px;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.marvel-device.s5:before{top:-7px}.marvel-device.s5:after{bottom:-7px}.marvel-device.s5 .bottom-bar{display:none}.marvel-device.s5 .top-bar{border-radius:37px;width:calc(100% - 10px);height:calc(100% - 10px);top:5px;left:5px;background:radial-gradient(rgba(0,0,0,0.02) 20%, transparent 60%) 0 0,radial-gradient(rgba(0,0,0,0.02) 20%, transparent 60%) 3px 3px;background-color:white;background-size:4px 4px;background-position:center;z-index:2;position:absolute}.marvel-device.s5 .top-bar:before,.marvel-device.s5 .top-bar:after{width:calc(100% - 48px);content:'';display:block;height:26px;background:inherit;position:absolute;border-radius:500px / 40px;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.marvel-device.s5 .top-bar:before{top:-7px}.marvel-device.s5 .top-bar:after{bottom:-7px}.marvel-device.s5 .sleep{width:3px;position:absolute;left:-3px;top:100px;height:100px;background:#cecece;border-radius:2px 0px 0px 2px}.marvel-device.s5 .speaker{width:68px;height:8px;position:absolute;top:20px;display:block;z-index:3;left:50%;margin-left:-34px;background-color:#bcbcbc;background-position:top left;border-radius:4px}.marvel-device.s5 .sensor{display:block;position:absolute;top:20px;right:110px;background:#3c3d3d;border-radius:100%;width:8px;height:8px;z-index:3}.marvel-device.s5 .sensor:after{display:block;content:'';position:absolute;top:0px;right:12px;background:#3c3d3d;border-radius:100%;width:8px;height:8px;z-index:3}.marvel-device.s5 .camera{display:block;position:absolute;top:24px;right:42px;background:black;border-radius:100%;width:10px;height:10px;z-index:3}.marvel-device.s5 .camera:before{width:4px;height:4px;background:#3c3d3d;border-radius:100%;position:absolute;content:'';top:50%;left:50%;margin-top:-2px;margin-left:-2px}.marvel-device.s5 .home{position:absolute;z-index:3;bottom:17px;left:50%;width:70px;height:20px;background:white;border-radius:18px;display:block;margin-left:-35px;border:2px solid black}.marvel-device.s5.landscape{padding:18px 60px;height:320px;width:568px}.marvel-device.s5.landscape:before,.marvel-device.s5.landscape:after{height:calc(100% - 52px);width:26px;border-radius:40px / 500px;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.marvel-device.s5.landscape:before{top:50%;left:-7px}.marvel-device.s5.landscape:after{top:50%;left:auto;right:-7px}.marvel-device.s5.landscape .top-bar:before,.marvel-device.s5.landscape .top-bar:after{width:26px;height:calc(100% - 48px);border-radius:40px / 500px;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.marvel-device.s5.landscape .top-bar:before{right:-7px;top:50%;left:auto}.marvel-device.s5.landscape .top-bar:after{left:-7px;top:50%;right:auto}.marvel-device.s5.landscape .sleep{height:3px;width:100px;left:calc(100% - 200px);top:-3px;border-radius:2px 2px 0px 0px}.marvel-device.s5.landscape .speaker{height:68px;width:8px;left:calc(100% - 20px);top:50%;margin-left:0;margin-top:-34px}.marvel-device.s5.landscape .sensor{right:20px;top:calc(100% - 110px)}.marvel-device.s5.landscape .sensor:after{left:-12px;right:0px}.marvel-device.s5.landscape .camera{top:calc(100% - 42px);right:24px}.marvel-device.s5.landscape .home{width:20px;height:70px;bottom:50%;margin-bottom:-35px;margin-left:0;left:17px}.marvel-device.s5.black{background:#1e1e1e}.marvel-device.s5.black .speaker{background:black}.marvel-device.s5.black .sleep{background:#1e1e1e}.marvel-device.s5.black .top-bar{background:radial-gradient(rgba(0,0,0,0.05) 20%, transparent 60%) 0 0,radial-gradient(rgba(0,0,0,0.05) 20%, transparent 60%) 3px 3px;background-color:#2c2b2c;background-size:4px 4px}.marvel-device.s5.black .home{background:#2c2b2c}.marvel-device.lumia920{padding:80px 35px 125px 35px;background:#ffdd00;width:320px;height:533px;border-radius:40px / 3px}.marvel-device.lumia920 .bottom-bar{display:none}.marvel-device.lumia920 .top-bar{width:calc(100% - 24px);height:calc(100% - 32px);position:absolute;top:16px;left:12px;border-radius:24px;background:black;z-index:1}.marvel-device.lumia920 .top-bar:before{background:#1e1e1e;display:block;content:'';width:calc(100% - 4px);height:calc(100% - 4px);top:2px;left:2px;position:absolute;border-radius:22px}.marvel-device.lumia920 .volume{width:3px;position:absolute;top:130px;height:100px;background:#1e1e1e;right:-3px;border-radius:0px 2px 2px 0px}.marvel-device.lumia920 .volume:before{width:3px;position:absolute;top:190px;content:'';display:block;height:50px;background:inherit;right:0px;border-radius:0px 2px 2px 0px}.marvel-device.lumia920 .volume:after{width:3px;position:absolute;top:460px;content:'';display:block;height:50px;background:inherit;right:0px;border-radius:0px 2px 2px 0px}.marvel-device.lumia920 .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:34px;right:130px;z-index:5;border-radius:5px}.marvel-device.lumia920 .speaker{background:#292728;width:64px;height:10px;position:absolute;top:38px;left:50%;margin-left:-32px;border-radius:5px;z-index:3}.marvel-device.lumia920.landscape{padding:35px 80px 35px 125px;height:320px;width:568px;border-radius:2px / 100px}.marvel-device.lumia920.landscape .top-bar{height:calc(100% - 24px);width:calc(100% - 32px);left:16px;top:12px}.marvel-device.lumia920.landscape .volume{height:3px;right:130px;width:100px;top:100%;border-radius:0px 0px 2px 2px}.marvel-device.lumia920.landscape .volume:before{height:3px;right:190px;top:0px;width:50px;border-radius:0px 0px 2px 2px}.marvel-device.lumia920.landscape .volume:after{height:3px;right:430px;top:0px;width:50px;border-radius:0px 0px 2px 2px}.marvel-device.lumia920.landscape .camera{right:30px;top:calc(100% - 140px)}.marvel-device.lumia920.landscape .speaker{width:10px;height:64px;top:50%;margin-left:0;margin-top:-32px;left:calc(100% - 48px)}.marvel-device.lumia920.black{background:black}.marvel-device.lumia920.white{background:white;-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,0.2);box-shadow:0 1px 2px 0 rgba(0,0,0,0.2)}.marvel-device.lumia920.blue{background:#00acdd}.marvel-device.lumia920.red{background:#CC3E32}.marvel-device.htc-one{padding:72px 25px 100px 25px;width:320px;height:568px;background:#bebebe;border-radius:34px}.marvel-device.htc-one:before{content:'';display:block;width:calc(100% - 4px);height:calc(100% - 4px);position:absolute;top:2px;left:2px;background:#adadad;border-radius:32px}.marvel-device.htc-one:after{content:'';display:block;width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;top:4px;left:4px;background:#eeeeee;border-radius:30px}.marvel-device.htc-one .top-bar{width:calc(100% - 4px);height:635px;position:absolute;background:#424242;top:50px;z-index:1;left:2px}.marvel-device.htc-one .top-bar:before{content:'';position:absolute;width:calc(100% - 4px);height:100%;position:absolute;background:black;top:0px;z-index:1;left:2px}.marvel-device.htc-one .bottom-bar{display:none}.marvel-device.htc-one .speaker{height:16px;width:216px;display:block;position:absolute;top:22px;z-index:2;left:50%;margin-left:-108px;background:radial-gradient(#343434 25%, transparent 50%) 0 0,radial-gradient(#343434 25%, transparent 50%) 4px 4px;background-size:4px 4px;background-position:top left}.marvel-device.htc-one .speaker:after{content:'';height:16px;width:216px;display:block;position:absolute;top:676px;z-index:2;left:50%;margin-left:-108px;background:inherit}.marvel-device.htc-one .camera{display:block;position:absolute;top:18px;right:38px;background:#3c3d3d;border-radius:100%;width:24px;height:24px;z-index:3}.marvel-device.htc-one .camera:before{width:8px;height:8px;background:black;border-radius:100%;position:absolute;content:'';top:50%;left:50%;margin-top:-4px;margin-left:-4px}.marvel-device.htc-one .sensor{display:block;position:absolute;top:29px;left:60px;background:#3c3d3d;border-radius:100%;width:8px;height:8px;z-index:3}.marvel-device.htc-one .sensor:after{display:block;content:'';position:absolute;top:0px;right:12px;background:#3c3d3d;border-radius:100%;width:8px;height:8px;z-index:3}.marvel-device.htc-one.landscape{padding:25px 72px 25px 100px;height:320px;width:568px}.marvel-device.htc-one.landscape .top-bar{height:calc(100% - 4px);width:635px;left:calc(100% - 685px);top:2px}.marvel-device.htc-one.landscape .speaker{width:16px;height:216px;left:calc(100% - 38px);top:50%;margin-left:0px;margin-top:-108px}.marvel-device.htc-one.landscape .speaker:after{width:16px;height:216px;left:calc(100% - 692px);top:50%;margin-left:0;margin-top:-108px}.marvel-device.htc-one.landscape .camera{right:18px;top:calc(100% - 38px)}.marvel-device.htc-one.landscape .sensor{left:calc(100% - 29px);top:60px}.marvel-device.htc-one.landscape .sensor :after{right:0;top:-12px}.marvel-device.ipad{width:576px;height:768px;padding:90px 25px;background:#242324;border-radius:44px}.marvel-device.ipad:before{width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;content:'';display:block;top:4px;left:4px;border-radius:40px;background:#1e1e1e}.marvel-device.ipad .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:44px;left:50%;margin-left:-5px;border-radius:100%}.marvel-device.ipad .top-bar,.marvel-device.ipad .bottom-bar{display:none}.marvel-device.ipad .home{background:#242324;border-radius:36px;width:50px;height:50px;position:absolute;left:50%;margin-left:-25px;bottom:22px}.marvel-device.ipad .home:after{width:15px;height:15px;margin-top:-8px;margin-left:-8px;border:1px solid rgba(255,255,255,0.1);border-radius:4px;position:absolute;display:block;content:'';top:50%;left:50%}.marvel-device.ipad.landscape{height:576px;width:768px;padding:25px 90px}.marvel-device.ipad.landscape .camera{left:calc(100% - 44px);top:50%;margin-left:0;margin-top:-5px}.marvel-device.ipad.landscape .home{top:50%;left:22px;margin-left:0;margin-top:-25px}.marvel-device.ipad.silver{background:#bcbcbc}.marvel-device.ipad.silver:before{background:#fcfcfc}.marvel-device.ipad.silver .home{background:#fcfcfc;-webkit-box-shadow:inset 0 0 0 1px #bcbcbc;box-shadow:inset 0 0 0 1px #bcbcbc}.marvel-device.ipad.silver .home:after{border:1px solid rgba(0,0,0,0.2)}.marvel-device.macbook{width:960px;height:600px;padding:44px 44px 76px;margin:0 auto;background:#bebebe;border-radius:34px}.marvel-device.macbook:before{width:calc(100% - 8px);height:calc(100% - 8px);position:absolute;content:'';display:block;top:4px;left:4px;border-radius:30px;background:#1e1e1e}.marvel-device.macbook .top-bar{width:calc(100% + 2 * 70px);height:40px;position:absolute;content:'';display:block;top:680px;left:-70px;border-bottom-left-radius:90px 18px;border-bottom-right-radius:90px 18px;background:#bebebe;-webkit-box-shadow:inset 0px -4px 13px 3px rgba(34,34,34,0.6);box-shadow:inset 0px -4px 13px 3px rgba(34,34,34,0.6)}.marvel-device.macbook .top-bar:before{width:100%;height:24px;content:'';display:block;top:0;left:0;background:#f0f0f0;border-bottom:2px solid #aaa;border-radius:5px;position:relative}.marvel-device.macbook .top-bar:after{width:16%;height:14px;content:'';display:block;top:0;background:#ddd;position:absolute;margin-left:auto;margin-right:auto;left:0;right:0;border-radius:0 0 20px 20px;-webkit-box-shadow:inset 0px -3px 10px #999;box-shadow:inset 0px -3px 10px #999}.marvel-device.macbook .bottom-bar{background:transparent;width:calc(100% + 2 * 70px);height:26px;position:absolute;content:'';display:block;top:680px;left:-70px}.marvel-device.macbook .bottom-bar:before,.marvel-device.macbook .bottom-bar:after{height:calc(100% - 2px);width:80px;content:'';display:block;top:0;position:absolute}.marvel-device.macbook .bottom-bar:before{left:0;background:#f0f0f0;background:-webkit-gradient(linear, left top, right top, from(#747474), color-stop(5%, #c3c3c3), color-stop(14%, #ebebeb), color-stop(41%, #979797), color-stop(80%, #f0f0f0), color-stop(100%, #f0f0f0), to(#f0f0f0));background:linear-gradient(to right, #747474 0%, #c3c3c3 5%, #ebebeb 14%, #979797 41%, #f0f0f0 80%, #f0f0f0 100%, #f0f0f0 100%)}.marvel-device.macbook .bottom-bar:after{right:0;background:#f0f0f0;background:-webkit-gradient(linear, left top, right top, from(#f0f0f0), color-stop(0%, #f0f0f0), color-stop(20%, #f0f0f0), color-stop(59%, #979797), color-stop(86%, #ebebeb), color-stop(95%, #c3c3c3), to(#747474));background:linear-gradient(to right, #f0f0f0 0%, #f0f0f0 0%, #f0f0f0 20%, #979797 59%, #ebebeb 86%, #c3c3c3 95%, #747474 100%)}.marvel-device.macbook .camera{background:#3c3d3d;width:10px;height:10px;position:absolute;top:20px;left:50%;margin-left:-5px;border-radius:100%}.marvel-device.macbook .home{display:none}.marvel-device.iphone-x{width:375px;height:812px;padding:26px;background:#fdfdfd;-webkit-box-shadow:inset 0 0 11px 0 black;box-shadow:inset 0 0 11px 0 black;border-radius:66px}.marvel-device.iphone-x .overflow{width:100%;height:100%;position:absolute;top:0;left:0;border-radius:66px;overflow:hidden}.marvel-device.iphone-x .shadow{border-radius:100%;width:90px;height:90px;position:absolute;background:radial-gradient(ellipse at center, rgba(0,0,0,0.6) 0%, rgba(255,255,255,0) 60%)}.marvel-device.iphone-x .shadow--tl{top:-20px;left:-20px}.marvel-device.iphone-x .shadow--tr{top:-20px;right:-20px}.marvel-device.iphone-x .shadow--bl{bottom:-20px;left:-20px}.marvel-device.iphone-x .shadow--br{bottom:-20px;right:-20px}.marvel-device.iphone-x:before{width:calc(100% - 10px);height:calc(100% - 10px);position:absolute;top:5px;content:'';left:5px;border-radius:61px;background:black;z-index:1}.marvel-device.iphone-x .inner-shadow{width:calc(100% - 20px);height:calc(100% - 20px);position:absolute;top:10px;overflow:hidden;left:10px;border-radius:56px;-webkit-box-shadow:inset 0 0 15px 0 rgba(255,255,255,0.66);box-shadow:inset 0 0 15px 0 rgba(255,255,255,0.66);z-index:1}.marvel-device.iphone-x .inner-shadow:before{-webkit-box-shadow:inset 0 0 20px 0 #FFFFFF;box-shadow:inset 0 0 20px 0 #FFFFFF;width:100%;height:116%;position:absolute;top:-8%;content:'';left:0;border-radius:200px / 112px;z-index:2}.marvel-device.iphone-x .screen{border-radius:40px;-webkit-box-shadow:none;box-shadow:none}.marvel-device.iphone-x .top-bar,.marvel-device.iphone-x .bottom-bar{width:100%;position:absolute;height:8px;background:rgba(0,0,0,0.1);left:0}.marvel-device.iphone-x .top-bar{top:80px}.marvel-device.iphone-x .bottom-bar{bottom:80px}.marvel-device.iphone-x .volume,.marvel-device.iphone-x .volume:before,.marvel-device.iphone-x .volume:after,.marvel-device.iphone-x .sleep{width:3px;background:#b5b5b5;position:absolute}.marvel-device.iphone-x .volume{left:-3px;top:116px;height:32px}.marvel-device.iphone-x .volume:before{height:62px;top:62px;content:'';left:0}.marvel-device.iphone-x .volume:after{height:62px;top:140px;content:'';left:0}.marvel-device.iphone-x .sleep{height:96px;top:200px;right:-3px}.marvel-device.iphone-x .camera{width:6px;height:6px;top:9px;border-radius:100%;position:absolute;left:154px;background:#0d4d71}.marvel-device.iphone-x .speaker{height:6px;width:60px;left:50%;position:absolute;top:9px;margin-left:-30px;background:#171818;border-radius:6px}.marvel-device.iphone-x .notch{position:absolute;width:210px;height:30px;top:26px;left:108px;z-index:4;background:black;border-bottom-left-radius:24px;border-bottom-right-radius:24px}.marvel-device.iphone-x .notch:before,.marvel-device.iphone-x .notch:after{content:'';height:8px;position:absolute;top:0;width:8px}.marvel-device.iphone-x .notch:after{background:radial-gradient(circle at bottom left, transparent 0, transparent 70%, black 70%, black 100%);left:-8px}.marvel-device.iphone-x .notch:before{background:radial-gradient(circle at bottom right, transparent 0, transparent 70%, black 70%, black 100%);right:-8px}.marvel-device.iphone-x.landscape{height:375px;width:812px}.marvel-device.iphone-x.landscape .top-bar,.marvel-device.iphone-x.landscape .bottom-bar{width:8px;height:100%;top:0}.marvel-device.iphone-x.landscape .top-bar{left:80px}.marvel-device.iphone-x.landscape .bottom-bar{right:80px;bottom:auto;left:auto}.marvel-device.iphone-x.landscape .volume,.marvel-device.iphone-x.landscape .volume:before,.marvel-device.iphone-x.landscape .volume:after,.marvel-device.iphone-x.landscape .sleep{height:3px}.marvel-device.iphone-x.landscape .inner-shadow:before{height:100%;width:116%;left:-8%;top:0;border-radius:112px / 200px}.marvel-device.iphone-x.landscape .volume{bottom:-3px;top:auto;left:116px;width:32px}.marvel-device.iphone-x.landscape .volume:before{width:62px;left:62px;top:0}.marvel-device.iphone-x.landscape .volume:after{width:62px;left:140px;top:0}.marvel-device.iphone-x.landscape .sleep{width:96px;left:200px;top:-3px;right:auto}.marvel-device.iphone-x.landscape .camera{left:9px;bottom:154px;top:auto}.marvel-device.iphone-x.landscape .speaker{width:6px;height:60px;left:9px;top:50%;margin-top:-30px;margin-left:0}.marvel-device.iphone-x.landscape .notch{height:210px;width:30px;left:26px;bottom:108px;top:auto;border-top-right-radius:24px;border-bottom-right-radius:24px;border-bottom-left-radius:0}.marvel-device.iphone-x.landscape .notch:before,.marvel-device.iphone-x.landscape .notch:after{left:0}.marvel-device.iphone-x.landscape .notch:after{background:radial-gradient(circle at bottom right, transparent 0, transparent 70%, black 70%, black 100%);bottom:-8px;top:auto}.marvel-device.iphone-x.landscape .notch:before{background:radial-gradient(circle at top right, transparent 0, transparent 70%, black 70%, black 100%);top:-8px}.marvel-device.note8{width:400px;height:822px;background:black;border-radius:34px;padding:45px 10px}.marvel-device.note8 .overflow{width:100%;height:100%;position:absolute;top:0;left:0;border-radius:34px;overflow:hidden}.marvel-device.note8 .speaker{height:8px;width:56px;left:50%;position:absolute;top:25px;margin-left:-28px;background:#171818;z-index:1;border-radius:8px}.marvel-device.note8 .camera{height:18px;width:18px;left:86px;position:absolute;top:18px;background:#212b36;z-index:1;border-radius:100%}.marvel-device.note8 .camera:before{content:'';height:8px;width:8px;left:-22px;position:absolute;top:5px;background:#212b36;z-index:1;border-radius:100%}.marvel-device.note8 .sensors{height:10px;width:10px;left:120px;position:absolute;top:22px;background:#1d233b;z-index:1;border-radius:100%}.marvel-device.note8 .sensors:before{content:'';height:10px;width:10px;left:18px;position:absolute;top:0;background:#1d233b;z-index:1;border-radius:100%}.marvel-device.note8 .more-sensors{height:16px;width:16px;left:285px;position:absolute;top:18px;background:#33244a;-webkit-box-shadow:0 0 0 2px rgba(255,255,255,0.1);box-shadow:0 0 0 2px rgba(255,255,255,0.1);z-index:1;border-radius:100%}.marvel-device.note8 .more-sensors:before{content:'';height:11px;width:11px;left:40px;position:absolute;top:4px;background:#214a61;z-index:1;border-radius:100%}.marvel-device.note8 .sleep{width:2px;height:56px;background:black;position:absolute;top:288px;right:-2px}.marvel-device.note8 .volume{width:2px;height:120px;background:black;position:absolute;top:168px;left:-2px}.marvel-device.note8 .volume:before{content:'';top:168px;width:2px;position:absolute;left:0;background:black;height:56px}.marvel-device.note8 .inner{width:100%;height:calc(100% - 8px);position:absolute;top:2px;content:'';left:0px;border-radius:34px;border-top:2px solid #9fa0a2;border-bottom:2px solid #9fa0a2;background:black;z-index:1;-webkit-box-shadow:inset 0 0 6px 0 rgba(255,255,255,0.5);box-shadow:inset 0 0 6px 0 rgba(255,255,255,0.5)}.marvel-device.note8 .shadow{-webkit-box-shadow:inset 0 0 60px 0 white,inset 0 0 30px 0 rgba(255,255,255,0.5),0 0 20px 0 white,0 0 20px 0 rgba(255,255,255,0.5);box-shadow:inset 0 0 60px 0 white,inset 0 0 30px 0 rgba(255,255,255,0.5),0 0 20px 0 white,0 0 20px 0 rgba(255,255,255,0.5);height:101%;position:absolute;top:-0.5%;content:'';width:calc(100% - 20px);left:10px;border-radius:38px;z-index:5;pointer-events:none}.marvel-device.note8 .screen{border-radius:14px;-webkit-box-shadow:none;box-shadow:none}.marvel-device.note8.landscape{height:400px;width:822px;padding:10px 45px}.marvel-device.note8.landscape .speaker{height:56px;width:8px;top:50%;margin-top:-28px;margin-left:0;right:25px;left:auto}.marvel-device.note8.landscape .camera{top:86px;right:18px;left:auto}.marvel-device.note8.landscape .camera:before{top:-22px;left:5px}.marvel-device.note8.landscape .sensors{top:120px;right:22px;left:auto}.marvel-device.note8.landscape .sensors:before{top:18px;left:0}.marvel-device.note8.landscape .more-sensors{top:285px;right:18px;left:auto}.marvel-device.note8.landscape .more-sensors:before{top:40px;left:4px}.marvel-device.note8.landscape .sleep{bottom:-2px;top:auto;right:288px;width:56px;height:2px}.marvel-device.note8.landscape .volume{width:120px;height:2px;top:-2px;right:168px;left:auto}.marvel-device.note8.landscape .volume:before{right:168px;left:auto;top:0;width:56px;height:2px}.marvel-device.note8.landscape .inner{height:100%;width:calc(100% - 8px);left:2px;top:0;border-top:0;border-bottom:0;border-left:2px solid #9fa0a2;border-right:2px solid #9fa0a2}.marvel-device.note8.landscape .shadow{width:101%;height:calc(100% - 20px);left:-0.5%;top:10px} \ No newline at end of file diff --git a/mobsf/static/others/js/swipe.js b/mobsf/static/others/js/swipe.js new file mode 100644 index 0000000000..d1dbbcf35a --- /dev/null +++ b/mobsf/static/others/js/swipe.js @@ -0,0 +1,133 @@ + +class Swipe { + constructor(elem, options = {}) { + this.elem = elem; + this.minDistance = options.minDistance || 100; + this.maxTime = options.maxTime || 500; + this.corners = options.corners || false; + this.addListeners(); + this.events = { + live:[], + after:[] + }; + Swipe.directions().forEach(direction => this.events[direction] = []); + } + + static directions() { + return ['left', 'right', 'up', 'down', 'leftup', 'leftdown', 'rightup', 'rightdown']; + } + + static position(e) { + return {x: e.pageX, y: e.pageY}; + } + + static getOffsets(e, startPos) { + const newPos = Swipe.position(e); + return { + x: newPos.x - startPos.x, + y: newPos.y - startPos.y + }; + } + + static getDirections(offsets, corners) { + const directions = {}; + directions.left = offsets.x <= 0 ? Math.abs(offsets.x) : 0; + directions.right = offsets.x >= 0 ? Math.abs(offsets.x) : 0; + directions.up = offsets.y <= 0 ? Math.abs(offsets.y) : 0; + directions.down = offsets.y >= 0 ? Math.abs(offsets.y) : 0; + + if (corners) { + directions.leftup = (Math.abs((directions.left + directions.up)) / 1.5); + directions.leftdown = (Math.abs((directions.left + directions.down)) / 1.5); + directions.rightup = (Math.abs((directions.right + directions.up)) / 1.5); + directions.rightdown = (Math.abs((directions.right + directions.down)) / 1.5); + } + + return directions; + } + + static order(directions) { + return Object.keys(directions).sort((a, b) => directions[b] - directions[a]); + } + + addEventListener(evt, bc) { + const keys = Object.keys(this.events); + if (keys.indexOf(evt) !== -1) { + this.events[evt].push(bc); + const i = this.events.length - 1; + return { + clear: () => { + this.events[i] = undefined; + } + }; + } else { + throw new Error("Event is not valid, use " + keys.join(", ")); + } + } + + down(e) { + //e.preventDefault(); + this.didDown = true; + this.startTime = Date.now(); + this.startPos = Swipe.position(e); + } + + move(e) { + //e.preventDefault(); + if (!this.didDown) { + return; + } + this.didSwipe = true; + + if (this.events.live.length > 0) { + const offsets = Swipe.getOffsets(e, this.startPos); + const directions = Swipe.getDirections(offsets, this.corners); + const direction = Swipe.order(directions)[0]; + const distance = directions[direction]; + this.events.live.forEach(evt => { + if (typeof evt === "function") { + evt(direction, distance); + } + }); + } + } + + up(e) { + //e.preventDefault(); + this.didDown = false; + if (!this.didSwipe) { + return; + } + this.didSwipe = false; + + const elapsedTime = Date.now() - this.startTime; + if (elapsedTime <= this.maxTime) { + const offsets = Swipe.getOffsets(e, this.startPos); + const directions = Swipe.getDirections(offsets, this.corners); + const direction = Swipe.order(directions)[0]; + const distance = directions[direction]; + + if (distance >= this.minDistance) { + this.events.after.forEach(evt => { + if (typeof evt === "function") { + evt(direction, distance); + } + }); + this.events[direction].forEach(evt => { + if (typeof evt === "function") { + evt(distance); + } + }); + } + } + } + + addListeners() { + this.elem.addEventListener("touchstart", e => this.down(e)); + this.elem.addEventListener("mousedown", e => this.down(e)); + this.elem.addEventListener("touchmove", e => this.move(e)); + document.addEventListener("mousemove", e => this.move(e)); + this.elem.addEventListener("touchend", e => this.up(e)); + document.addEventListener("mouseup", e => this.up(e)); + } +} \ No newline at end of file diff --git a/mobsf/templates/dynamic_analysis/dynamic_analysis.html b/mobsf/templates/dynamic_analysis/android/dynamic_analysis.html similarity index 100% rename from mobsf/templates/dynamic_analysis/dynamic_analysis.html rename to mobsf/templates/dynamic_analysis/android/dynamic_analysis.html diff --git a/mobsf/templates/dynamic_analysis/android/dynamic_analyzer.html b/mobsf/templates/dynamic_analysis/android/dynamic_analyzer.html index f7b70cb58a..773aec4534 100644 --- a/mobsf/templates/dynamic_analysis/android/dynamic_analyzer.html +++ b/mobsf/templates/dynamic_analysis/android/dynamic_analyzer.html @@ -481,12 +481,12 @@ // Frida load other scripts function load_frida_others(){ - $.get(document.location.origin + '/list_frida_scripts/', function(json, status){ - if (json.status == 'ok'){ + action('{% url 'list_frida_scripts' %}', {device: 'android'}, function(json) { + if (json.status==="ok"){ json.files.forEach(function(script) { $('#fd_scs').append(''); }); - } + } }); } @@ -1001,7 +1001,7 @@ // Load Frida Scripts $("#loadscript").click(function() { var scripts = $('#fd_scs').val(); - action(document.location.origin + '/get_script/', {scripts: scripts}, function(json) { + action('{% url 'get_script' %}', {scripts: scripts, device: 'android'}, function(json) { if (json.status==="ok"){ editor.getDoc().setValue(json.content); } diff --git a/mobsf/templates/dynamic_analysis/ios/dynamic_analysis.html b/mobsf/templates/dynamic_analysis/ios/dynamic_analysis.html new file mode 100644 index 0000000000..4681e9e1a7 --- /dev/null +++ b/mobsf/templates/dynamic_analysis/ios/dynamic_analysis.html @@ -0,0 +1,817 @@ + +{% extends "base/base_layout.html" %} +{% load static %} +{% block sidebar_option %} + sidebar-collapse +{% endblock %} +{% block extra_css %} + + + + + +{% endblock %} +{% block content %} +
+
+
+
+ + +
+
+
+ +
+
+
+

MobSF iOS Dynamic Analyzer

+
+
+ {% if not dynamic_analyzer %}
Cannot authenticate with Corellium. Please ensure that MOBSF_CORELLIUM_API_KEY is configured.
{% endif %} +
Corellium Project ID: {{ project_id }}
+ Refresh + Create VM + + + + + + + + + + + + {% if instances %} + {% for i in instances %} + + + + + + + + {% endfor %} + {% endif %} + +
VMVERSIONSTATEPROGRESSACTIONS
+ {{i.name}}
({{ i.id}}) +
+ {{i.flavor }}, {{ i.os }} + + {{i.state}} + + {% if 'progress' in i.restoreStatus %} +
+
{{i.restoreStatus.progress| floatformat:"0"}}%
+
+ {% endif %} + {% if 'stage' in i.restoreStatus %} +
 {{ i.restoreStatus.stage}}
+ {% endif %} +
+

+ Start + Stop + Unpause + Reboot + Destroy +

+
+

+

Choose an instance for Dynamic Analysis:
+ +

+
+
+ +
+
+
+ +
+ +
+ + +
+
+
+

MobSF Dynamic Analysis

+
+
+
+

Apps Available

+ + + + + + + + + + + {% if apps %} + {% for e in apps %} + + + + + + + {% endfor %} + {% endif %} + +
APPFILE NAMEBUNDLE IDACTION
+ +
{{ e.APP_NAME }} - {{ e.APP_VERSION }} +
+ {{ e.FILE_NAME }} + + {{ e.BUNDLE_ID }} + + {% if e.ENCRYPTED %} + ENCRYPTED IPA
+ You need decrypted IPA for dynamic analysis. + {% else %} +

+ Upload & Install +

+

+ View Report +

+ {% endif %} +
+
+

Apps in Device

+ + + +
+
+
+ +
+ +
+ +
+ +
+
+ + + + + + + + + + + +{% endblock %} +{% block extra_scripts %} + + + + + +{% endblock %} diff --git a/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html b/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html new file mode 100644 index 0000000000..5e7794751d --- /dev/null +++ b/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html @@ -0,0 +1,1211 @@ + +{% extends "base/base_layout.html" %} +{% load static %} +{% block sidebar_option %} + sidebar-collapse +{% endblock %} +{% block extra_css %} + + + + + + + + +{% endblock %} +{% block content %} + + + +
+
+
+
+
+

Dynamic Analyzer - {{ bundle_id }}

+
+
+
+
+
+ + +
+ + + + +
+
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+

+

+ +
+ +
+
+

+
+ + +
+ +
+ +
+ + +
+
+ +
+ +
+
+ +
+
+
+
+ + +
+
+

Frida Scripts

+
+ + +
+
+
+
+

Default

+ + +
+
+
+

Trace

+ + + + + + + + + + + + + +
+
+
+

Auxiliary

+ + + + +
+ + + + + + + + +
+ +
+ + + +
+
+ + + +
+
+ +
+
+
+ +
+ + +
+ +
+ +
+ + +
+
+
+

Frida Code Editor

+ +
+ +
+
+
+ +
+
+
+
+ Available Scripts (Use CTRL to choose multiple) + +
+
+ +
+
+
+ + +
+ +
+ +
+ + +
+
+ +
+
+ + +
+
+ +
+
+
+
+ +
+ +
+ +
+ + + + +
+
+ +
+
+ +

Data refreshed in every 3 seconds.
+

+                  
+
+ +
+ +
+ + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + +{% endblock %} +{% block extra_scripts %} + + + + + + + + +{% endblock %} diff --git a/mobsf/templates/dynamic_analysis/ios/dynamic_report.html b/mobsf/templates/dynamic_analysis/ios/dynamic_report.html new file mode 100644 index 0000000000..182f2e3159 --- /dev/null +++ b/mobsf/templates/dynamic_analysis/ios/dynamic_report.html @@ -0,0 +1,1472 @@ +{% extends "base/base_layout.html" %} + {% load static %} + {% block sidebar_option %} + sidebar-mini + {% endblock %} + {% block extra_css %} + + + + + + + {% endblock %} + + {% block sidebar %} + + + + +{% endblock %} +{% block content %} + +
+ +
+ +
+
+
+
+

Dynamic Analysis Report - {{ bundleid }}

+
+
+
+
+
+ + + + + +
+
+
+
+
+
+
+
+
+

INFORMATION

+
+

+ {% if frida_logs %} + Frida Logs View + {% endif %} + Start HTTPTools +

+

Raw Logs

+

+ HTTP(S) Traffic + Network Pcap + Application Data +

+
+
+
+
+
+
+ + +
+
+
+ + + +
+
+
+
+
+
+

+ USERDEFAULTS DATA +

+
+ {% if userdefaults %} + + + + + + + + + {% for k, v in userdefaults.items %} + + + + + {% endfor %} + +
KEYVALUE
+ {{k}} + + {{v | base64_decode }} +
+ {% endif %} +
+
+
+
+ +
+
+
+ + + +
+
+
+
+
+
+

+ KEYCHAIN DATA +

+
+ {% if keychain %} + + + + + + + + + + + + {% for item in keychain %} + + + + + + + {% endfor %} + +
ITEMDATACREATE DATEMODIFICATION DATE
+ Entitlement Group: {{item.entitlement_group}}
+ Item Class: {{item.item_class}}
+ Accessible Attribute: {{item.accessible_attribute}}
+ Generic: {{item.generic}}
+ Service: {{item.service}}
+ Account: {{item.account}}
+
+ Protected: {{item.protected}}
+ Label: {{item.label}}
+ Access Control: {{item.access_control}}
+ Description: {{item.description}}
+ Comment: {{item.comment}}
+ Creator: {{item.creator}}
+ Type: {{item.type}}
+ Script Code: {{item.script_code}}
+ Alias: {{item.alias}}
+ Invisible: {{item.invisible}}
+ Negative: {{item.negative}}
+ Custom Icon: {{item.custom_icon}}
+
+
{{item.data | pretty_json | base64_decode}}
+
+ {{item.create_date}} + + {{item.modification_date}} +
+ {% endif %} +
+
+
+
+ +
+
+
+ + + +
+
+
+
+
+
+

+ FILE ACCESS +

+
+ {% if files %} + + + + + + + + + {% for item in files %} + + + + + {% endfor %} + +
FILE PATHDOWNLOAD
+ {{item}} + + +
+ {% endif %} +
+
+
+
+ +
+
+
+ + + + +
+
+
+
+
+
+

+ APP DATA DIRECTORY +

+
+ {% if datadir %} + + + + + + + + + + {% for item in datadir %} + + + + + + {% endfor %} + +
FILE PATHDOWNLOADFILE PROTECTION
+ {{ item.path | replace:"/private/var/mobile/Containers/Data/Application/|" }} + + + + {{ item.fileProtectionKey }} +
+ {% endif %} +
+
+
+
+ +
+
+
+ + + +
+
+
+
+
+
+

+ URLS INVOKED +

+
+ {% if network %} + + + + + + + + + {% for item in network %} + + + + + {% endfor %} + +
SOURCEURL
+ {{item.source}} + + {{item.url}} +
+ {% endif %} +
+
+
+
+ +
+
+
+ + +
+
+
+
+
+
+

+ JSON DATA +

+
+ {% if json %} + + + + + + + + {% for item in json %} + + + + {% endfor %} + +
JSON
+
{{item | pretty_json}}
+
+ {% endif %} +
+
+
+
+ +
+
+
+ + + + +
+
+
+
+
+
+

+ APP LOGS +

+
+ {% if logs %} + + + + + + + + {% for item in logs %} + + + + {% endfor %} + +
LOGS
+ {{item}} +
+ {% endif %} +
+
+
+
+ +
+
+
+ + +
+
+
+
+
+
+

+ TEXT INPUTS +

+
+ {% if textinputs %} + + + + + + + + {% for item in textinputs %} + + + + {% endfor %} + +
KEYSTROKES
+ {{item}} +
+ {% endif %} +
+
+
+
+ +
+
+
+ + + +
+
+
+
+
+
+

+ PASTEBOARD +

+
+ {% if pasteboard %} + + + + + + + + {% for item in pasteboard %} + + + + {% endfor %} + +
ITEMS IN PASTEBOARD
+ {{item}} +
+ {% endif %} +
+
+
+
+ +
+
+
+ + + +
+
+
+
+
+
+

+ APP COOKIES +

+
+ {% if cookies %} + + + + + + + + + + + + + + + {% for item in cookies %} + + + + + + + + + + + + {% endfor %} + +
NAMEVALUEDOMAINPATHEXPIRYHTTPONLYSECUREVERSION
+ {{item.name}} + + {{item.value}} + + {{item.domain}} + + {{item.path}} + + {{item.expiry}} + + {{item.httponly}} + + {{item.secure}} + + {{item.version}} +
+ {% endif %} +
+
+
+
+ +
+
+
+ + + +
+
+
+
+
+
+

+ CRYPTO OPERATIONS +

+
+ {% if crypto %} + + + + + + + {% for item in crypto %} + + {% for k, v in item.items %} + {% if v %} + + {% endif %} + {% endfor %} + + {% endfor %} + + +
+ {{ k }}:
 {{ v | base64_decode | pretty_json}}
+
+ {% endif %} +
+
+
+
+ +
+
+
+ + + +
+
+
+
+
+
+

+ CREDENTIAL STORAGE +

+
+ {% if credentials %} + + + + + + + + + + + + + {% for item in credentials %} + + + + + + + + + {% endfor %} + +
HOSTAUTHENTICATION METHODPROTOCOLPORTUSERPASSWORD
+ {{item.host}} + + {{item.authenticationMethod}} + + {{item.protocol}} + + {{item.port}} + + {{item.user}} + + {{item.password}} +
+ {% endif %} +
+
+
+
+ +
+
+
+ + + +
+
+
+
+
+
+

+ SQLITE QUERIES +

+
+ {% if sql %} + + + + + + + + {% for item in sql %} + + + + {% endfor %} + +
QUERIES
+ {{item}} +
+ {% endif %} +
+
+
+
+ +
+
+
+ + + + +
+
+
+
+
+
+

+ SCREENSHOTS +

+
+ {% for i in screenshots %} + Screenshot + {% endfor %} +
+ +
+
+
+ +
+
+
+ + + + +
+
+
+
+
+
+

+ SERVER LOCATIONS +

+
+
+
+ +
+ {% if domains %} +


This app may communicate with the following OFAC sanctioned list of countries.

+ + + + + + + + + {% for domain, details in domains.items %} + {% if details|key:"ofac" == True %} + + + + {% endif %} + {% endfor %} + +
DOMAINCOUNTRY/REGION
{{domain}} + IP: {{details|key:"geolocation"|key:"ip"}}
+ Country: {{details|key:"geolocation"|key:"country_long"}}
+ Region: {{details|key:"geolocation"|key:"region"}}
+ City: {{details|key:"geolocation"|key:"city"}}
+
+ {% endif %} +
+ +
+
+
+ +
+
+
+ + +
+
+
+
+
+
+

+ DOMAIN MALWARE CHECK +

+
+ {% if domains %} + + + + + + + + + + {% for domain, details in domains.items %} + + + + {% endfor %} + +
DOMAINSTATUSGEOLOCATION
{{domain}} + + {% if details|key:"bad" == "yes" %} + malware
+
+                      URL: {{details|key:"domain_or_url"}}
+                      IP: {{details|key:"ip"}}
+                      Description: {{details|key:"desc"}}
+                      
+ {% else %} + good
+ {% endif %} +
+ {% if details|key:"geolocation" %} + IP: {{details|key:"geolocation"|key:"ip"}}
+ Country: {{details|key:"geolocation"|key:"country_long"}}
+ Region: {{details|key:"geolocation"|key:"region"}}
+ City: {{details|key:"geolocation"|key:"city"}}
+ Latitude: {{details|key:"geolocation"|key:"latitude"}}
+ Longitude: {{details|key:"geolocation"|key:"longitude"}}
+ View: Google Map + + {% else %} + No Geolocation information available. + {% endif %} +
+ {% endif %} +
+
+
+
+ +
+
+
+ + +
+
+
+
+
+
+

+ URLS +

+ {% if urls %} +
+ {% for f in urls %} + {{ f}}
+ {% endfor %} + + {% endif %} +
+
+
+
+ +
+
+ + +
+
+
+
+
+
+

+ EMAILS +

+ {% if emails %} +
+ {% for f in emails %} + {{ f}}
+ {% endfor %} + + {% endif %} +
+
+
+ +
+
+
+ + +
+
+
+
+
+
+

+ TRACKERS +

+
+ {% if trackers %} + + + + + + + + + + {% for trk in trackers|key:"trackers" %} + + + + + + {% endfor %} + +
TRACKER NAMECATEGORIESURL
+ {{trk.name}} + + {{trk.categories}} + + {{trk.url}} +
+ {% endif %} +
+
+
+
+ +
+
+
+ + +
+
+
+
+
+
+

+ SQLITE DATABASE +

+
+ + + + + + + + {% for file in sqlite %} + + {% endfor %} + +
FILES
{{file|key:"file"}}
+
+
+
+
+ +
+
+
+ + +
+
+
+
+
+
+

+ PLIST FILES +

+
+ + + + + + + + {% for file in plist %} + + {% endfor %} + +
FILES
{{file|key:"file"}}
+
+
+
+
+
+ +
+
+ + +
+
+
+
+
+
+

+ OTHER FILES +

+
+ + + + + + + + {% for file in others %} + + {% endfor %} + +
FILES
{{file|key:"file"}}
+
+
+
+
+
+ +
+
+ + + + +
+ + + + + + +{% endblock %} + +{% block extra_scripts %} + + + + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/mobsf/templates/dynamic_analysis/ios/system_logs.html b/mobsf/templates/dynamic_analysis/ios/system_logs.html new file mode 100644 index 0000000000..17d27c90a8 --- /dev/null +++ b/mobsf/templates/dynamic_analysis/ios/system_logs.html @@ -0,0 +1,53 @@ +{% extends "base/base_layout.html" %} + {% block sidebar_option %} + sidebar-collapse +{% endblock %} +{% block content %} +
+
+
+
+
+
+
+
+ +

Data refreshed in every 10 seconds.
+

+          
+
+
+
+
+
+{% endblock %} +{% block extra_scripts %} + +{% endblock %} \ No newline at end of file diff --git a/mobsf/templates/general/apidocs.html b/mobsf/templates/general/apidocs.html index e902a898af..3b06d073a8 100644 --- a/mobsf/templates/general/apidocs.html +++ b/mobsf/templates/general/apidocs.html @@ -2336,12 +2336,31 @@

Frida List Scripts APIURL: /api/v1/frida/list_scripts

  • -

    Method: GET

    +

    Method: POST

  • Header: Authorization:<api_key> Or X-Mobsf-Api-Key:<api_key>

  • +
  • +

    Data Params

    +
  • + + + + + + + + + + + + + + + +
    Param NameParam ValueRequired
    deviceandroid/iosYes

    • @@ -2391,7 +2410,7 @@

      Frida List Scripts APISample Call:

      • -
        curl --url http://localhost:8000/api/v1/frida/list_scripts -H "Authorization:{{ api_key}}"
        +                    
        curl -X POST --url http://localhost:8000/api/v1/frida/list_scripts --data "device=android" -H "Authorization:{{ api_key}}"
                           
      @@ -2430,6 +2449,11 @@

      Frida Get Script API

      name of the script from the output of Frida List Scripts (/api/v1/frida/list_scripts) API. Yes + + device + android/ios + Yes +
      @@ -2466,7 +2490,7 @@

      Frida Get Script API

      Sample Call:

      • -
        curl -X POST --url http://localhost:8000/api/v1/frida/get_script --data "scripts[]=hook_java_reflection&scripts[]=jni_hook_by_address&scripts[]=default&scripts[]=get_android_id" -H "Authorization:{{ api_key}}"
        +                    
        curl -X POST --url http://localhost:8000/api/v1/frida/get_script --data "device=android&scripts[]=hook_java_reflection&scripts[]=jni_hook_by_address&scripts[]=default&scripts[]=get_android_id" -H "Authorization:{{ api_key}}"
                           
      diff --git a/mobsf/templates/general/dynamic.html b/mobsf/templates/general/dynamic.html new file mode 100644 index 0000000000..2635914994 --- /dev/null +++ b/mobsf/templates/general/dynamic.html @@ -0,0 +1,45 @@ +{% extends "base/base_layout.html" %} + {% block sidebar_option %} + sidebar-collapse +{% endblock %} +{% block content %} +
      +
      +
      +
      +
      + + +
      +
      +
      +

      MobSF Dynamic Analyzer

      +

      +
      +
      + + + +
      +

      Android Dynamic Analyzer

      +

      Perform Dynamic Analysis of Android Applications.

      + Android Dynamic Analyzer +
      +
      +
         
      +
      + +
      +

      iOS Dynamic Analyzer

      +

      Perform Dynamic Analysis of iOS Applications.

      + iOS Dynamic Analyzer +
      +
      +
      +
      +
      +
      +
      +
      +
      +{% endblock %} diff --git a/mobsf/templates/general/recent.html b/mobsf/templates/general/recent.html index b48e34e8e3..9a91b445f5 100644 --- a/mobsf/templates/general/recent.html +++ b/mobsf/templates/general/recent.html @@ -70,6 +70,10 @@

      Recent Scans

      Static Report {% if '.apk' == e.FILE_NAME|slice:"-4:" or '.xapk' == e.FILE_NAME|slice:"-5:" or '.apks' == e.FILE_NAME|slice:"-5:"%} Dynamic Report + {% elif '.ipa' == e.FILE_NAME|slice:"-4:" %} + {% if e.PACKAGE_NAME %} + Dynamic Report + {% endif %} {% endif %}

      {% else %} diff --git a/poetry.lock b/poetry.lock index 169087e2bd..b310ac3978 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,25 @@ # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +[[package]] +name = "aioquic-mitmproxy" +version = "0.9.21.1" +description = "Fork of aioquic (https://github.com/aiortc/aioquic) with adjustments for mitmproxy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "aioquic_mitmproxy-0.9.21.1-py3-none-any.whl", hash = "sha256:4c691232cc97f7c01c1677f21e2457523ee6780c4b5ad7d7936f6a3210d4f886"}, + {file = "aioquic_mitmproxy-0.9.21.1.tar.gz", hash = "sha256:0da539b95982dd02d1f971344215da5c15d0c409e2639d267a2815cd27335e26"}, +] + +[package.dependencies] +certifi = "*" +pylsqpack = ">=0.3.3,<0.4.0" +pyopenssl = ">=22" +service-identity = ">=23.1.0" + +[package.extras] +dev = ["coverage[toml] (>=7.2.2)"] + [[package]] name = "altgraph" version = "0.17.4" @@ -89,17 +109,6 @@ click = ">=6.0" cryptography = "*" simplejson = "*" -[[package]] -name = "appnope" -version = "0.1.3" -description = "Disable App Nap on macOS >= 10.9" -optional = false -python-versions = "*" -files = [ - {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, - {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, -] - [[package]] name = "arpy" version = "2.3.0" @@ -112,15 +121,18 @@ files = [ [[package]] name = "asgiref" -version = "3.5.2" +version = "3.7.2" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.7" files = [ - {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, - {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, + {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, + {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, ] +[package.dependencies] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} + [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] @@ -170,6 +182,36 @@ docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"] tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"] +[[package]] +name = "bcrypt" +version = "4.1.1" +description = "Modern password hashing for your software and your servers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "bcrypt-4.1.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:196008d91201bbb1aa4e666fee5e610face25d532e433a560cabb33bfdff958b"}, + {file = "bcrypt-4.1.1-cp37-abi3-macosx_13_0_universal2.whl", hash = "sha256:2e197534c884336f9020c1f3a8efbaab0aa96fc798068cb2da9c671818b7fbb0"}, + {file = "bcrypt-4.1.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d573885b637815a7f3a3cd5f87724d7d0822da64b0ab0aa7f7c78bae534e86dc"}, + {file = "bcrypt-4.1.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bab33473f973e8058d1b2df8d6e095d237c49fbf7a02b527541a86a5d1dc4444"}, + {file = "bcrypt-4.1.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fb931cd004a7ad36a89789caf18a54c20287ec1cd62161265344b9c4554fdb2e"}, + {file = "bcrypt-4.1.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:12f40f78dcba4aa7d1354d35acf45fae9488862a4fb695c7eeda5ace6aae273f"}, + {file = "bcrypt-4.1.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2ade10e8613a3b8446214846d3ddbd56cfe9205a7d64742f0b75458c868f7492"}, + {file = "bcrypt-4.1.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f33b385c3e80b5a26b3a5e148e6165f873c1c202423570fdf45fe34e00e5f3e5"}, + {file = "bcrypt-4.1.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:755b9d27abcab678e0b8fb4d0abdebeea1f68dd1183b3f518bad8d31fa77d8be"}, + {file = "bcrypt-4.1.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a7a7b8a87e51e5e8ca85b9fdaf3a5dc7aaf123365a09be7a27883d54b9a0c403"}, + {file = "bcrypt-4.1.1-cp37-abi3-win32.whl", hash = "sha256:3d6c4e0d6963c52f8142cdea428e875042e7ce8c84812d8e5507bd1e42534e07"}, + {file = "bcrypt-4.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:14d41933510717f98aac63378b7956bbe548986e435df173c841d7f2bd0b2de7"}, + {file = "bcrypt-4.1.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:24c2ebd287b5b11016f31d506ca1052d068c3f9dc817160628504690376ff050"}, + {file = "bcrypt-4.1.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:476aa8e8aca554260159d4c7a97d6be529c8e177dbc1d443cb6b471e24e82c74"}, + {file = "bcrypt-4.1.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:12611c4b0a8b1c461646228344784a1089bc0c49975680a2f54f516e71e9b79e"}, + {file = "bcrypt-4.1.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6450538a0fc32fb7ce4c6d511448c54c4ff7640b2ed81badf9898dcb9e5b737"}, + {file = "bcrypt-4.1.1.tar.gz", hash = "sha256:df37f5418d4f1cdcff845f60e747a015389fa4e63703c918330865e06ad80007"}, +] + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + [[package]] name = "beautifulsoup4" version = "4.12.2" @@ -198,6 +240,17 @@ files = [ {file = "biplist-1.0.3.tar.gz", hash = "sha256:4c0549764c5fe50b28042ec21aa2e14fe1a2224e239a1dae77d9e7f3932aa4c6"}, ] +[[package]] +name = "blinker" +version = "1.7.0" +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"}, +] + [[package]] name = "boltons" version = "21.0.0" @@ -222,93 +275,94 @@ files = [ [[package]] name = "brotli" -version = "1.0.9" +version = "1.1.0" description = "Python bindings for the Brotli compression library" optional = false python-versions = "*" files = [ - {file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"}, - {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"}, - {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6"}, - {file = "Brotli-1.0.9-cp27-cp27m-win32.whl", hash = "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa"}, - {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452"}, - {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7"}, - {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031"}, - {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43"}, - {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"}, - {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"}, - {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"}, - {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, - {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, - {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc0283a406774f465fb45ec7efb66857c09ffefbe49ec20b7882eff6d3c86d3a"}, - {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:11d3283d89af7033236fa4e73ec2cbe743d4f6a81d41bd234f24bf63dde979df"}, - {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1306004d49b84bd0c4f90457c6f57ad109f5cc6067a9664e12b7b79a9948ad"}, - {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1375b5d17d6145c798661b67e4ae9d5496920d9265e2f00f1c2c0b5ae91fbde"}, - {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cab1b5964b39607a66adbba01f1c12df2e55ac36c81ec6ed44f2fca44178bf1a"}, - {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ed6a5b3d23ecc00ea02e1ed8e0ff9a08f4fc87a1f58a2530e71c0f48adf882f"}, - {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb02ed34557afde2d2da68194d12f5719ee96cfb2eacc886352cb73e3808fc5d"}, - {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b3523f51818e8f16599613edddb1ff924eeb4b53ab7e7197f85cbc321cdca32f"}, - {file = "Brotli-1.0.9-cp311-cp311-win32.whl", hash = "sha256:ba72d37e2a924717990f4d7482e8ac88e2ef43fb95491eb6e0d124d77d2a150d"}, - {file = "Brotli-1.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:3ffaadcaeafe9d30a7e4e1e97ad727e4f5610b9fa2f7551998471e3736738679"}, - {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, - {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"}, - {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"}, - {file = "Brotli-1.0.9-cp35-cp35m-win32.whl", hash = "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1"}, - {file = "Brotli-1.0.9-cp35-cp35m-win_amd64.whl", hash = "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea"}, - {file = "Brotli-1.0.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f"}, - {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"}, - {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"}, - {file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"}, - {file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"}, - {file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"}, - {file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"}, - {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"}, - {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"}, - {file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"}, - {file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"}, - {file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"}, - {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"}, - {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8"}, - {file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"}, - {file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"}, - {file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"}, - {file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"}, - {file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"}, - {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"}, - {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7"}, - {file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"}, - {file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"}, - {file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"}, - {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, - {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73fd30d4ce0ea48010564ccee1a26bfe39323fde05cb34b5863455629db61dc7"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02177603aaca36e1fd21b091cb742bb3b305a569e2402f1ca38af471777fb019"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b43775532a5904bc938f9c15b77c613cb6ad6fb30990f3b0afaea82797a402d8"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bf37a08493232fbb0f8229f1824b366c2fc1d02d64e7e918af40acd15f3e337"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:330e3f10cd01da535c70d09c4283ba2df5fb78e915bea0a28becad6e2ac010be"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e1abbeef02962596548382e393f56e4c94acd286bd0c5afba756cffc33670e8a"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3148362937217b7072cf80a2dcc007f09bb5ecb96dae4617316638194113d5be"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:336b40348269f9b91268378de5ff44dc6fbaa2268194f85177b53463d313842a"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b09a16a1950b9ef495a0f8b9d0a87599a9d1f179e2d4ac014b2ec831f87e7"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c8e521a0ce7cf690ca84b8cc2272ddaf9d8a50294fd086da67e517439614c755"}, - {file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"}, + {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, + {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"}, + {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, + {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, + {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, + {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, + {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"}, + {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, + {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, + {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"}, + {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, + {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, + {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, + {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"}, + {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, + {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, + {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, + {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"}, + {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, + {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, + {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, + {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, + {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"}, + {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, + {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, + {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, + {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, + {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"}, + {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, + {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, + {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, + {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, ] [[package]] @@ -326,13 +380,13 @@ beautifulsoup4 = "*" [[package]] name = "certifi" -version = "2023.7.22" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] [[package]] @@ -401,101 +455,101 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.3.1" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, - {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -544,13 +598,13 @@ files = [ [[package]] name = "colorlog" -version = "6.7.0" +version = "6.8.0" description = "Add colours to the output of Python's logging module." optional = false python-versions = ">=3.6" files = [ - {file = "colorlog-6.7.0-py2.py3-none-any.whl", hash = "sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662"}, - {file = "colorlog-6.7.0.tar.gz", hash = "sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5"}, + {file = "colorlog-6.8.0-py3-none-any.whl", hash = "sha256:4ed23b05a1154294ac99f511fabe8c1d6d4364ec1f7fc989c7fb515ccc29d375"}, + {file = "colorlog-6.8.0.tar.gz", hash = "sha256:fbb6fdf9d5685f2517f388fb29bb27d54e8654dd31f58bc2a3b217e967a95ca6"}, ] [package.dependencies] @@ -561,182 +615,111 @@ development = ["black", "flake8", "mypy", "pytest", "types-colorama"] [[package]] name = "contourpy" -version = "1.1.0" -description = "Python library for calculating contours of 2D quadrilateral grids" -optional = false -python-versions = ">=3.8" -files = [ - {file = "contourpy-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:89f06eff3ce2f4b3eb24c1055a26981bffe4e7264acd86f15b97e40530b794bc"}, - {file = "contourpy-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dffcc2ddec1782dd2f2ce1ef16f070861af4fb78c69862ce0aab801495dda6a3"}, - {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ae46595e22f93592d39a7eac3d638cda552c3e1160255258b695f7b58e5655"}, - {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17cfaf5ec9862bc93af1ec1f302457371c34e688fbd381f4035a06cd47324f48"}, - {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"}, - {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"}, - {file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"}, - {file = "contourpy-1.1.0-cp310-cp310-win32.whl", hash = "sha256:9b2dd2ca3ac561aceef4c7c13ba654aaa404cf885b187427760d7f7d4c57cff8"}, - {file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"}, - {file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"}, - {file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"}, - {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052cc634bf903c604ef1a00a5aa093c54f81a2612faedaa43295809ffdde885e"}, - {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9382a1c0bc46230fb881c36229bfa23d8c303b889b788b939365578d762b5c18"}, - {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"}, - {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"}, - {file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"}, - {file = "contourpy-1.1.0-cp311-cp311-win32.whl", hash = "sha256:edb989d31065b1acef3828a3688f88b2abb799a7db891c9e282df5ec7e46221b"}, - {file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"}, - {file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"}, - {file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"}, - {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62013a2cf68abc80dadfd2307299bfa8f5aa0dcaec5b2954caeb5fa094171103"}, - {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b6616375d7de55797d7a66ee7d087efe27f03d336c27cf1f32c02b8c1a5ac70"}, - {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"}, - {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"}, - {file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"}, - {file = "contourpy-1.1.0-cp38-cp38-win32.whl", hash = "sha256:108dfb5b3e731046a96c60bdc46a1a0ebee0760418951abecbe0fc07b5b93b27"}, - {file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"}, - {file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"}, - {file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"}, - {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f2931ed4741f98f74b410b16e5213f71dcccee67518970c42f64153ea9313b9"}, - {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f511c05fab7f12e0b1b7730ebdc2ec8deedcfb505bc27eb570ff47c51a8f15"}, - {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"}, - {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"}, - {file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"}, - {file = "contourpy-1.1.0-cp39-cp39-win32.whl", hash = "sha256:71551f9520f008b2950bef5f16b0e3587506ef4f23c734b71ffb7b89f8721999"}, - {file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"}, - {file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"}, - {file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"}, - {file = "contourpy-1.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a67259c2b493b00e5a4d0f7bfae51fb4b3371395e47d079a4446e9b0f4d70e76"}, - {file = "contourpy-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2b836d22bd2c7bb2700348e4521b25e077255ebb6ab68e351ab5aa91ca27e027"}, - {file = "contourpy-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084eaa568400cfaf7179b847ac871582199b1b44d5699198e9602ecbbb5f6104"}, - {file = "contourpy-1.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:911ff4fd53e26b019f898f32db0d4956c9d227d51338fb3b03ec72ff0084ee5f"}, - {file = "contourpy-1.1.0.tar.gz", hash = "sha256:e53046c3863828d21d531cc3b53786e6580eb1ba02477e8681009b6aa0870b21"}, -] - -[package.dependencies] -numpy = ">=1.16" - -[package.extras] -bokeh = ["bokeh", "selenium"] -docs = ["furo", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.2.0)", "types-Pillow"] -test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "wurlitzer"] - -[[package]] -name = "contourpy" -version = "1.1.1" +version = "1.2.0" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "contourpy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:46e24f5412c948d81736509377e255f6040e94216bf1a9b5ea1eaa9d29f6ec1b"}, - {file = "contourpy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e48694d6a9c5a26ee85b10130c77a011a4fedf50a7279fa0bdaf44bafb4299d"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a66045af6cf00e19d02191ab578a50cb93b2028c3eefed999793698e9ea768ae"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ebf42695f75ee1a952f98ce9775c873e4971732a87334b099dde90b6af6a916"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6aec19457617ef468ff091669cca01fa7ea557b12b59a7908b9474bb9674cf0"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462c59914dc6d81e0b11f37e560b8a7c2dbab6aca4f38be31519d442d6cde1a1"}, - {file = "contourpy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6d0a8efc258659edc5299f9ef32d8d81de8b53b45d67bf4bfa3067f31366764d"}, - {file = "contourpy-1.1.1-cp310-cp310-win32.whl", hash = "sha256:d6ab42f223e58b7dac1bb0af32194a7b9311065583cc75ff59dcf301afd8a431"}, - {file = "contourpy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:549174b0713d49871c6dee90a4b499d3f12f5e5f69641cd23c50a4542e2ca1eb"}, - {file = "contourpy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:407d864db716a067cc696d61fa1ef6637fedf03606e8417fe2aeed20a061e6b2"}, - {file = "contourpy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe80c017973e6a4c367e037cb31601044dd55e6bfacd57370674867d15a899b"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e30aaf2b8a2bac57eb7e1650df1b3a4130e8d0c66fc2f861039d507a11760e1b"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3de23ca4f381c3770dee6d10ead6fff524d540c0f662e763ad1530bde5112532"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:566f0e41df06dfef2431defcfaa155f0acfa1ca4acbf8fd80895b1e7e2ada40e"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04c2f0adaf255bf756cf08ebef1be132d3c7a06fe6f9877d55640c5e60c72c5"}, - {file = "contourpy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0c188ae66b772d9d61d43c6030500344c13e3f73a00d1dc241da896f379bb62"}, - {file = "contourpy-1.1.1-cp311-cp311-win32.whl", hash = "sha256:0683e1ae20dc038075d92e0e0148f09ffcefab120e57f6b4c9c0f477ec171f33"}, - {file = "contourpy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:8636cd2fc5da0fb102a2504fa2c4bea3cbc149533b345d72cdf0e7a924decc45"}, - {file = "contourpy-1.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:560f1d68a33e89c62da5da4077ba98137a5e4d3a271b29f2f195d0fba2adcb6a"}, - {file = "contourpy-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24216552104ae8f3b34120ef84825400b16eb6133af2e27a190fdc13529f023e"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56de98a2fb23025882a18b60c7f0ea2d2d70bbbcfcf878f9067234b1c4818442"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:07d6f11dfaf80a84c97f1a5ba50d129d9303c5b4206f776e94037332e298dda8"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1eaac5257a8f8a047248d60e8f9315c6cff58f7803971170d952555ef6344a7"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19557fa407e70f20bfaba7d55b4d97b14f9480856c4fb65812e8a05fe1c6f9bf"}, - {file = "contourpy-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:081f3c0880712e40effc5f4c3b08feca6d064cb8cfbb372ca548105b86fd6c3d"}, - {file = "contourpy-1.1.1-cp312-cp312-win32.whl", hash = "sha256:059c3d2a94b930f4dafe8105bcdc1b21de99b30b51b5bce74c753686de858cb6"}, - {file = "contourpy-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f44d78b61740e4e8c71db1cf1fd56d9050a4747681c59ec1094750a658ceb970"}, - {file = "contourpy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70e5a10f8093d228bb2b552beeb318b8928b8a94763ef03b858ef3612b29395d"}, - {file = "contourpy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8394e652925a18ef0091115e3cc191fef350ab6dc3cc417f06da66bf98071ae9"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bd5680f844c3ff0008523a71949a3ff5e4953eb7701b28760805bc9bcff217"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66544f853bfa85c0d07a68f6c648b2ec81dafd30f272565c37ab47a33b220684"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0c02b75acfea5cab07585d25069207e478d12309557f90a61b5a3b4f77f46ce"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41339b24471c58dc1499e56783fedc1afa4bb018bcd035cfb0ee2ad2a7501ef8"}, - {file = "contourpy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f29fb0b3f1217dfe9362ec55440d0743fe868497359f2cf93293f4b2701b8251"}, - {file = "contourpy-1.1.1-cp38-cp38-win32.whl", hash = "sha256:f9dc7f933975367251c1b34da882c4f0e0b2e24bb35dc906d2f598a40b72bfc7"}, - {file = "contourpy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:498e53573e8b94b1caeb9e62d7c2d053c263ebb6aa259c81050766beb50ff8d9"}, - {file = "contourpy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba42e3810999a0ddd0439e6e5dbf6d034055cdc72b7c5c839f37a7c274cb4eba"}, - {file = "contourpy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c06e4c6e234fcc65435223c7b2a90f286b7f1b2733058bdf1345d218cc59e34"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6fab080484e419528e98624fb5c4282148b847e3602dc8dbe0cb0669469887"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93df44ab351119d14cd1e6b52a5063d3336f0754b72736cc63db59307dabb718"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eafbef886566dc1047d7b3d4b14db0d5b7deb99638d8e1be4e23a7c7ac59ff0f"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe0fab26d598e1ec07d72cf03eaeeba8e42b4ecf6b9ccb5a356fde60ff08b85"}, - {file = "contourpy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f08e469821a5e4751c97fcd34bcb586bc243c39c2e39321822060ba902eac49e"}, - {file = "contourpy-1.1.1-cp39-cp39-win32.whl", hash = "sha256:bfc8a5e9238232a45ebc5cb3bfee71f1167064c8d382cadd6076f0d51cff1da0"}, - {file = "contourpy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:c84fdf3da00c2827d634de4fcf17e3e067490c4aea82833625c4c8e6cdea0887"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:229a25f68046c5cf8067d6d6351c8b99e40da11b04d8416bf8d2b1d75922521e"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10dab5ea1bd4401c9483450b5b0ba5416be799bbd50fc7a6cc5e2a15e03e8a3"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f9147051cb8fdb29a51dc2482d792b3b23e50f8f57e3720ca2e3d438b7adf23"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a75cc163a5f4531a256f2c523bd80db509a49fc23721b36dd1ef2f60ff41c3cb"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b53d5769aa1f2d4ea407c65f2d1d08002952fac1d9e9d307aa2e1023554a163"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11b836b7dbfb74e049c302bbf74b4b8f6cb9d0b6ca1bf86cfa8ba144aedadd9c"}, - {file = "contourpy-1.1.1.tar.gz", hash = "sha256:96ba37c2e24b7212a77da85004c38e7c4d155d3e72a45eeaf22c1f03f607e8ab"}, + {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, + {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, + {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, + {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, + {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, + {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, + {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, + {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, + {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, + {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, + {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, ] [package.dependencies] -numpy = {version = ">=1.16,<2.0", markers = "python_version <= \"3.11\""} +numpy = ">=1.20,<2.0" [package.extras] bokeh = ["bokeh", "selenium"] docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.4.1)", "types-Pillow"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"] test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "wurlitzer"] +test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] [[package]] name = "cryptography" -version = "38.0.4" +version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "cryptography-38.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:2fa36a7b2cc0998a3a4d5af26ccb6273f3df133d61da2ba13b3286261e7efb70"}, - {file = "cryptography-38.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:1f13ddda26a04c06eb57119caf27a524ccae20533729f4b1e4a69b54e07035eb"}, - {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2ec2a8714dd005949d4019195d72abed84198d877112abb5a27740e217e0ea8d"}, - {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50a1494ed0c3f5b4d07650a68cd6ca62efe8b596ce743a5c94403e6f11bf06c1"}, - {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10498349d4c8eab7357a8f9aa3463791292845b79597ad1b98a543686fb1ec8"}, - {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:10652dd7282de17990b88679cb82f832752c4e8237f0c714be518044269415db"}, - {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bfe6472507986613dc6cc00b3d492b2f7564b02b3b3682d25ca7f40fa3fd321b"}, - {file = "cryptography-38.0.4-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce127dd0a6a0811c251a6cddd014d292728484e530d80e872ad9806cfb1c5b3c"}, - {file = "cryptography-38.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:53049f3379ef05182864d13bb9686657659407148f901f3f1eee57a733fb4b00"}, - {file = "cryptography-38.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:8a4b2bdb68a447fadebfd7d24855758fe2d6fecc7fed0b78d190b1af39a8e3b0"}, - {file = "cryptography-38.0.4-cp36-abi3-win32.whl", hash = "sha256:1d7e632804a248103b60b16fb145e8df0bc60eed790ece0d12efe8cd3f3e7744"}, - {file = "cryptography-38.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:8e45653fb97eb2f20b8c96f9cd2b3a0654d742b47d638cf2897afbd97f80fa6d"}, - {file = "cryptography-38.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca57eb3ddaccd1112c18fc80abe41db443cc2e9dcb1917078e02dfa010a4f353"}, - {file = "cryptography-38.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:c9e0d79ee4c56d841bd4ac6e7697c8ff3c8d6da67379057f29e66acffcd1e9a7"}, - {file = "cryptography-38.0.4-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0e70da4bdff7601b0ef48e6348339e490ebfb0cbe638e083c9c41fb49f00c8bd"}, - {file = "cryptography-38.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:998cd19189d8a747b226d24c0207fdaa1e6658a1d3f2494541cb9dfbf7dcb6d2"}, - {file = "cryptography-38.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67461b5ebca2e4c2ab991733f8ab637a7265bb582f07c7c88914b5afb88cb95b"}, - {file = "cryptography-38.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:4eb85075437f0b1fd8cd66c688469a0c4119e0ba855e3fef86691971b887caf6"}, - {file = "cryptography-38.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3178d46f363d4549b9a76264f41c6948752183b3f587666aff0555ac50fd7876"}, - {file = "cryptography-38.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6391e59ebe7c62d9902c24a4d8bcbc79a68e7c4ab65863536127c8a9cd94043b"}, - {file = "cryptography-38.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:78e47e28ddc4ace41dd38c42e6feecfdadf9c3be2af389abbfeef1ff06822285"}, - {file = "cryptography-38.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fb481682873035600b5502f0015b664abc26466153fab5c6bc92c1ea69d478b"}, - {file = "cryptography-38.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:4367da5705922cf7070462e964f66e4ac24162e22ab0a2e9d31f1b270dd78083"}, - {file = "cryptography-38.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b4cad0cea995af760f82820ab4ca54e5471fc782f70a007f31531957f43e9dee"}, - {file = "cryptography-38.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:80ca53981ceeb3241998443c4964a387771588c4e4a5d92735a493af868294f9"}, - {file = "cryptography-38.0.4.tar.gz", hash = "sha256:175c1a818b87c9ac80bb7377f5520b7f31b3ef2a0004e2420319beadedb67290"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, ] [package.dependencies] cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools-rust (>=0.11.4)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] [[package]] name = "cycler" @@ -788,18 +771,18 @@ files = [ [[package]] name = "django" -version = "4.1.13" +version = "4.2.7" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" files = [ - {file = "Django-4.1.13-py3-none-any.whl", hash = "sha256:04ab3f6f46d084a0bba5a2c9a93a3a2eb3fe81589512367a75f79ee8acf790ce"}, - {file = "Django-4.1.13.tar.gz", hash = "sha256:94a3f471e833c8f124ee7a2de11e92f633991d975e3fa5bdd91e8abd66426318"}, + {file = "Django-4.2.7-py3-none-any.whl", hash = "sha256:e1d37c51ad26186de355cbcec16613ebdabfa9689bbade9c538835205a8abbe9"}, + {file = "Django-4.2.7.tar.gz", hash = "sha256:8e0f1c2c2786b5c0e39fe1afce24c926040fad47c8ea8ad30aaf1188df29fc41"}, ] [package.dependencies] -asgiref = ">=3.5.2,<4" -sqlparse = ">=0.2.2" +asgiref = ">=3.6.0,<4" +sqlparse = ">=0.3.1" tzdata = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] @@ -808,13 +791,13 @@ bcrypt = ["bcrypt"] [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -866,21 +849,21 @@ typing = ["typing-extensions (>=4.8)"] [[package]] name = "flask" -version = "2.1.3" +version = "3.0.0" description = "A simple framework for building complex web applications." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Flask-2.1.3-py3-none-any.whl", hash = "sha256:9013281a7402ad527f8fd56375164f3aa021ecfaff89bfe3825346c24f87e04c"}, - {file = "Flask-2.1.3.tar.gz", hash = "sha256:15972e5017df0575c3d6c090ba168b6db90259e620ac8d7ea813a396bad5b6cb"}, + {file = "flask-3.0.0-py3-none-any.whl", hash = "sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638"}, + {file = "flask-3.0.0.tar.gz", hash = "sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58"}, ] [package.dependencies] -click = ">=8.0" -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} -itsdangerous = ">=2.0" -Jinja2 = ">=3.0" -Werkzeug = ">=2.0" +blinker = ">=1.6.2" +click = ">=8.1.3" +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=3.0.0" [package.extras] async = ["asgiref (>=3.2)"] @@ -888,57 +871,57 @@ dotenv = ["python-dotenv"] [[package]] name = "fonttools" -version = "4.43.1" +version = "4.46.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf11e2cca121df35e295bd34b309046c29476ee739753bc6bc9d5050de319273"}, - {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10b3922875ffcba636674f406f9ab9a559564fdbaa253d66222019d569db869c"}, - {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f727c3e3d08fd25352ed76cc3cb61486f8ed3f46109edf39e5a60fc9fecf6ca"}, - {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad0b3f6342cfa14be996971ea2b28b125ad681c6277c4cd0fbdb50340220dfb6"}, - {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b7ad05b2beeebafb86aa01982e9768d61c2232f16470f9d0d8e385798e37184"}, - {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c54466f642d2116686268c3e5f35ebb10e49b0d48d41a847f0e171c785f7ac7"}, - {file = "fonttools-4.43.1-cp310-cp310-win32.whl", hash = "sha256:1e09da7e8519e336239fbd375156488a4c4945f11c4c5792ee086dd84f784d02"}, - {file = "fonttools-4.43.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cf9e974f63b1080b1d2686180fc1fbfd3bfcfa3e1128695b5de337eb9075cef"}, - {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5db46659cfe4e321158de74c6f71617e65dc92e54980086823a207f1c1c0e24b"}, - {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1952c89a45caceedf2ab2506d9a95756e12b235c7182a7a0fff4f5e52227204f"}, - {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c36da88422e0270fbc7fd959dc9749d31a958506c1d000e16703c2fce43e3d0"}, - {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bbbf8174501285049e64d174e29f9578495e1b3b16c07c31910d55ad57683d8"}, - {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d4071bd1c183b8d0b368cc9ed3c07a0f6eb1bdfc4941c4c024c49a35429ac7cd"}, - {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d21099b411e2006d3c3e1f9aaf339e12037dbf7bf9337faf0e93ec915991f43b"}, - {file = "fonttools-4.43.1-cp311-cp311-win32.whl", hash = "sha256:b84a1c00f832feb9d0585ca8432fba104c819e42ff685fcce83537e2e7e91204"}, - {file = "fonttools-4.43.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a2f0aa6ca7c9bc1058a9d0b35483d4216e0c1bbe3962bc62ce112749954c7b8"}, - {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4d9740e3783c748521e77d3c397dc0662062c88fd93600a3c2087d3d627cd5e5"}, - {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884ef38a5a2fd47b0c1291647b15f4e88b9de5338ffa24ee52c77d52b4dfd09c"}, - {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9648518ef687ba818db3fcc5d9aae27a369253ac09a81ed25c3867e8657a0680"}, - {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e974d70238fc2be5f444fa91f6347191d0e914d5d8ae002c9aa189572cc215"}, - {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:34f713dad41aa21c637b4e04fe507c36b986a40f7179dcc86402237e2d39dcd3"}, - {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:360201d46165fc0753229afe785900bc9596ee6974833124f4e5e9f98d0f592b"}, - {file = "fonttools-4.43.1-cp312-cp312-win32.whl", hash = "sha256:bb6d2f8ef81ea076877d76acfb6f9534a9c5f31dc94ba70ad001267ac3a8e56f"}, - {file = "fonttools-4.43.1-cp312-cp312-win_amd64.whl", hash = "sha256:25d3da8a01442cbc1106490eddb6d31d7dffb38c1edbfabbcc8db371b3386d72"}, - {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8da417431bfc9885a505e86ba706f03f598c85f5a9c54f67d63e84b9948ce590"}, - {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51669b60ee2a4ad6c7fc17539a43ffffc8ef69fd5dbed186a38a79c0ac1f5db7"}, - {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748015d6f28f704e7d95cd3c808b483c5fb87fd3eefe172a9da54746ad56bfb6"}, - {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7a58eb5e736d7cf198eee94844b81c9573102ae5989ebcaa1d1a37acd04b33d"}, - {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6bb5ea9076e0e39defa2c325fc086593ae582088e91c0746bee7a5a197be3da0"}, - {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5f37e31291bf99a63328668bb83b0669f2688f329c4c0d80643acee6e63cd933"}, - {file = "fonttools-4.43.1-cp38-cp38-win32.whl", hash = "sha256:9c60ecfa62839f7184f741d0509b5c039d391c3aff71dc5bc57b87cc305cff3b"}, - {file = "fonttools-4.43.1-cp38-cp38-win_amd64.whl", hash = "sha256:fe9b1ec799b6086460a7480e0f55c447b1aca0a4eecc53e444f639e967348896"}, - {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13a9a185259ed144def3682f74fdcf6596f2294e56fe62dfd2be736674500dba"}, - {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2adca1b46d69dce4a37eecc096fe01a65d81a2f5c13b25ad54d5430ae430b13"}, - {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18eefac1b247049a3a44bcd6e8c8fd8b97f3cad6f728173b5d81dced12d6c477"}, - {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2062542a7565091cea4cc14dd99feff473268b5b8afdee564f7067dd9fff5860"}, - {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18a2477c62a728f4d6e88c45ee9ee0229405e7267d7d79ce1f5ce0f3e9f8ab86"}, - {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a7a06f8d95b7496e53af80d974d63516ffb263a468e614978f3899a6df52d4b3"}, - {file = "fonttools-4.43.1-cp39-cp39-win32.whl", hash = "sha256:10003ebd81fec0192c889e63a9c8c63f88c7d72ae0460b7ba0cd2a1db246e5ad"}, - {file = "fonttools-4.43.1-cp39-cp39-win_amd64.whl", hash = "sha256:e117a92b07407a061cde48158c03587ab97e74e7d73cb65e6aadb17af191162a"}, - {file = "fonttools-4.43.1-py3-none-any.whl", hash = "sha256:4f88cae635bfe4bbbdc29d479a297bb525a94889184bb69fa9560c2d4834ddb9"}, - {file = "fonttools-4.43.1.tar.gz", hash = "sha256:17dbc2eeafb38d5d0e865dcce16e313c58265a6d2d20081c435f84dc5a9d8212"}, + {file = "fonttools-4.46.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d4e69e2c7f93b695d2e6f18f709d501d945f65c1d237dafaabdd23cd935a5276"}, + {file = "fonttools-4.46.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:25852f0c63df0af022f698464a4a80f7d1d5bd974bcd22f995f6b4ad198e32dd"}, + {file = "fonttools-4.46.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adab73618d0a328b203a0e242b3eba60a2b5662d9cb2bd16ed9c52af8a7d86af"}, + {file = "fonttools-4.46.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf923a4a556ab4cc4c52f69a4a2db624cf5a2cf360394368b40c5152fe3321e"}, + {file = "fonttools-4.46.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:87c214197712cc14fd2a4621efce2a9c501a77041232b789568149a8a3161517"}, + {file = "fonttools-4.46.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:156ae342a1ed1fe38e180de471e98fbf5b2b6ae280fa3323138569c4ca215844"}, + {file = "fonttools-4.46.0-cp310-cp310-win32.whl", hash = "sha256:c506e3d3a9e898caee4dc094f34b49c5566870d5a2d1ca2125f0a9f35ecc2205"}, + {file = "fonttools-4.46.0-cp310-cp310-win_amd64.whl", hash = "sha256:f8bc3973ed58893c4107993e0a7ae34901cb572b5e798249cbef35d30801ffd4"}, + {file = "fonttools-4.46.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:982f69855ac258260f51048d9e0c53c5f19881138cc7ca06deb38dc4b97404b6"}, + {file = "fonttools-4.46.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c23c59d321d62588620f2255cf951270bf637d88070f38ed8b5e5558775b86c"}, + {file = "fonttools-4.46.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0e94244ec24a940ecfbe5b31c975c8a575d5ed2d80f9a280ce3b21fa5dc9c34"}, + {file = "fonttools-4.46.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a9f9cdd7ef63d1b8ac90db335762451452426b3207abd79f60da510cea62da5"}, + {file = "fonttools-4.46.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ca9eceebe70035b057ce549e2054cad73e95cac3fe91a9d827253d1c14618204"}, + {file = "fonttools-4.46.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8be6adfa4e15977075278dd0a0bae74dec59be7b969b5ceed93fb86af52aa5be"}, + {file = "fonttools-4.46.0-cp311-cp311-win32.whl", hash = "sha256:7b5636f5706d49f13b6d610fe54ee662336cdf56b5a6f6683c0b803e23d826d2"}, + {file = "fonttools-4.46.0-cp311-cp311-win_amd64.whl", hash = "sha256:49ea0983e55fd7586a809787cd4644a7ae471e53ab8ddc016f9093b400e32646"}, + {file = "fonttools-4.46.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7b460720ce81773da1a3e7cc964c48e1e11942b280619582a897fa0117b56a62"}, + {file = "fonttools-4.46.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8bee9f4fc8c99824a424ae45c789ee8c67cb84f8e747afa7f83b7d3cef439c3b"}, + {file = "fonttools-4.46.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3d7b96aba96e05e8c911ce2dfc5acc6a178b8f44f6aa69371ab91aa587563da"}, + {file = "fonttools-4.46.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e6aeb5c340416d11a3209d75c48d13e72deea9e1517837dd1522c1fd1f17c11"}, + {file = "fonttools-4.46.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c779f8701deedf41908f287aeb775b8a6f59875ad1002b98ac6034ae4ddc1b7b"}, + {file = "fonttools-4.46.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce199227ce7921eaafdd4f96536f16b232d6b580ce74ce337de544bf06cb2752"}, + {file = "fonttools-4.46.0-cp312-cp312-win32.whl", hash = "sha256:1c9937c4dd1061afd22643389445fabda858af5e805860ec3082a4bc07c7a720"}, + {file = "fonttools-4.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:a9fa52ef8fd14d7eb3d813e1451e7ace3e1eebfa9b7237d3f81fee8f3de6a114"}, + {file = "fonttools-4.46.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c94564b1f3b5dd87e73577610d85115b1936edcc596deaf84a31bbe70e17456b"}, + {file = "fonttools-4.46.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4a50a1dfad7f7ba5ca3f99cc73bf5cdac67ceade8e4b355a877521f20ad1b63"}, + {file = "fonttools-4.46.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89c2c520f9492844ecd6316d20c6c7a157b5c0cb73a1411b3db28ee304f30122"}, + {file = "fonttools-4.46.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5b7905fd68eacb7cc56a13139da5c312c45baae6950dd00b02563c54508a041"}, + {file = "fonttools-4.46.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8485cc468288e213f31afdaf1fdda3c79010f542559fbba936a54f4644df2570"}, + {file = "fonttools-4.46.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:87c3299da7da55394fb324349db0ede38114a46aafd0e7dfcabfecd28cdd94c3"}, + {file = "fonttools-4.46.0-cp38-cp38-win32.whl", hash = "sha256:f5f1423a504ccc329efb5aa79738de83d38c072be5308788dde6bd419969d7f5"}, + {file = "fonttools-4.46.0-cp38-cp38-win_amd64.whl", hash = "sha256:6d4a4ebcc76e30898ff3296ea786491c70e183f738319ae2629e0d44f17ece42"}, + {file = "fonttools-4.46.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9a0e422ab79e5cb2b47913be6a4b5fd20c4c7ac34a24f3691a4e099e965e0b8"}, + {file = "fonttools-4.46.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:13ac0cba2fc63fa4b232f2a7971f35f35c6eaf10bd1271fa96d4ce6253a8acfd"}, + {file = "fonttools-4.46.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:795150d5edc595e1a2cfb3d65e8f4f3d027704fc2579f8990d381bef6b188eb6"}, + {file = "fonttools-4.46.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d00fc63131dcac6b25f50a5a129758438317e54e3ce5587163f7058de4b0e933"}, + {file = "fonttools-4.46.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3033b55f401a622de2630b3982234d97219d89b058607b87927eccb0f922313c"}, + {file = "fonttools-4.46.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e26e7fb908ae4f622813e7cb32cd2db6c24e3122bb3b98f25e832a2fe0e7e228"}, + {file = "fonttools-4.46.0-cp39-cp39-win32.whl", hash = "sha256:2d0eba685938c603f2f648dfc0aadbf8c6a4fe1c7ca608c2970a6ef39e00f254"}, + {file = "fonttools-4.46.0-cp39-cp39-win_amd64.whl", hash = "sha256:5200b01f463d97cc2b7ff8a1e3584151f4413e98cb8419da5f17d1dbb84cc214"}, + {file = "fonttools-4.46.0-py3-none-any.whl", hash = "sha256:5b627ed142398ea9202bd752c04311592558964d1a765fb2f78dc441a05633f4"}, + {file = "fonttools-4.46.0.tar.gz", hash = "sha256:2ae45716c27a41807d58a9f3f59983bdc8c0a46cb259e4450ab7e196253a9853"}, ] [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] interpolatable = ["munkres", "scipy"] lxml = ["lxml (>=4.0,<5)"] @@ -948,25 +931,25 @@ repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] type1 = ["xattr"] ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.0.0)"] +unicode = ["unicodedata2 (>=15.1.0)"] woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] name = "frida" -version = "16.1.4" +version = "16.1.8" description = "Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers" optional = false python-versions = ">=3.7" files = [ - {file = "frida-16.1.4-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8acc2cd8c8e0dc2c4ae86bdf2c76aeef6f92e98f43857048f9f8e3757e523701"}, - {file = "frida-16.1.4-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:0f58fa6c483f8ab00bcd5267cc1bfd1e9469112d09e942061b72e6ef96606738"}, - {file = "frida-16.1.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7b1a20d481f4f323fc0cd99c1af4a5430421f4e220cb850f0a766fa4f192deb"}, - {file = "frida-16.1.4-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1a0b963a7d17f016b5aa403b8b4ef07191dc70848e2e6cf9a06f5a0d32bc7f96"}, - {file = "frida-16.1.4-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7bb70b3fbf775f010485479701a309ad29674644eda10a86a43b3844021c8cb5"}, - {file = "frida-16.1.4-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a1c658e75b451c11ff8393a206d3b50dd2cfccf7188f5947da57b5ee6d3da157"}, - {file = "frida-16.1.4-cp37-abi3-win32.whl", hash = "sha256:9d980dd79c99ea79c279000b53de475208ce636a973f47b18c4e90e11381297f"}, - {file = "frida-16.1.4-cp37-abi3-win_amd64.whl", hash = "sha256:3f75a99a4e4258ced12e6241e50f2df65add0baebdb484f8139d8d5255f1c698"}, - {file = "frida-16.1.4.tar.gz", hash = "sha256:b2d3a22f8422cd9379ee2a3ca870e50cdb1706cfa48be9587bf835d63e51e582"}, + {file = "frida-16.1.8-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fe063b49bed3dec5471fbecd1e92e9a478eaeb555e174e3ea3bcf28f0264f9bf"}, + {file = "frida-16.1.8-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:34020a4c0f4086cec69f0792bc15e9e5ed22e34069194c26fb3a362244181374"}, + {file = "frida-16.1.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6a167e57dc96e967b5b46df07b6ebb9f16ec5d9c72eaefda27f1c124428bb4f"}, + {file = "frida-16.1.8-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ba40604e2715cb32e0bd902ce557bff54f60d075da5f0ac29d970411a974717"}, + {file = "frida-16.1.8-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4dfc51d1669821c819f6d7ae661005a52d0848409d40fa70761d85db5fc0695"}, + {file = "frida-16.1.8-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:714747b913dc898927a027115fdcdfae90e868b9167c3b39670eb40595e7d39a"}, + {file = "frida-16.1.8-cp37-abi3-win32.whl", hash = "sha256:be81753b8d74b994f46071173a87f1f5caf1a8313f79da71c887537cc864dccb"}, + {file = "frida-16.1.8-cp37-abi3-win_amd64.whl", hash = "sha256:91b88d46a5c252722cc4db450f9d788a49f83539732a5b14b32749b3df4ad7c3"}, + {file = "frida-16.1.8.tar.gz", hash = "sha256:dc4118f48c5e1b2241eee7d53ab836d42636c511fbf2f8f8b2dcab133421d09d"}, ] [package.dependencies] @@ -1077,17 +1060,17 @@ files = [ [[package]] name = "http-tools" -version = "3.0.0" +version = "4.0.0" description = "httptools helps you to capture, repeat and live intercept HTTP requests. It is built on top of [mitmproxy](https://mitmproxy.org/)" optional = false python-versions = "*" files = [ - {file = "http-tools-3.0.0.tar.gz", hash = "sha256:367e03a83464a16aa710cd77dc5e1b3730b3fdbee1b7db2e20a8adf759d987e2"}, + {file = "http-tools-4.0.0.tar.gz", hash = "sha256:aa6a3477ca4ef2c61b461b79d3ce88200c9d02d37c84c65e8abbc7ed2dfed545"}, ] [package.dependencies] -markupsafe = "2.0.1" -mitmproxy = "9.0.1" +markupsafe = ">=2.1.3" +mitmproxy = "10.1.5" [[package]] name = "hyperframe" @@ -1102,52 +1085,15 @@ files = [ [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] - -[[package]] -name = "importlib-metadata" -version = "6.8.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - -[[package]] -name = "importlib-resources" -version = "6.1.0" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.1.0-py3-none-any.whl", hash = "sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83"}, - {file = "importlib_resources-6.1.0.tar.gz", hash = "sha256:9d48dcccc213325e810fd723e7fbb45ccb39f6cf5c31f00cf2b965f5f10f3cb9"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] - [[package]] name = "ip2location" version = "8.10.0" @@ -1161,28 +1107,26 @@ files = [ [[package]] name = "ipython" -version = "8.17.2" +version = "8.18.1" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.9" files = [ - {file = "ipython-8.17.2-py3-none-any.whl", hash = "sha256:1e4d1d666a023e3c93585ba0d8e962867f7a111af322efff6b9c58062b3e5444"}, - {file = "ipython-8.17.2.tar.gz", hash = "sha256:126bb57e1895594bb0d91ea3090bbd39384f6fe87c3d57fd558d0670f50339bb"}, + {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, + {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, ] [package.dependencies] -appnope = {version = "*", markers = "sys_platform == \"darwin\""} colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +prompt-toolkit = ">=3.0.41,<3.1.0" pygments = ">=2.4.0" stack-data = "*" traitlets = ">=5" -typing-extensions = {version = "*", markers = "python_version < \"3.10\""} [package.extras] all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] @@ -1572,131 +1516,120 @@ altgraph = ">=0.17" [[package]] name = "markupsafe" -version = "2.0.1" +version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, - {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] [[package]] name = "matplotlib" -version = "3.8.0" +version = "3.8.2" description = "Python plotting package" optional = false python-versions = ">=3.9" files = [ - {file = "matplotlib-3.8.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c4940bad88a932ddc69734274f6fb047207e008389489f2b6f77d9ca485f0e7a"}, - {file = "matplotlib-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a33bd3045c7452ca1fa65676d88ba940867880e13e2546abb143035fa9072a9d"}, - {file = "matplotlib-3.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea6886e93401c22e534bbfd39201ce8931b75502895cfb115cbdbbe2d31f287"}, - {file = "matplotlib-3.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d670b9348e712ec176de225d425f150dc8e37b13010d85233c539b547da0be39"}, - {file = "matplotlib-3.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7b37b74f00c4cb6af908cb9a00779d97d294e89fd2145ad43f0cdc23f635760c"}, - {file = "matplotlib-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:0e723f5b96f3cd4aad99103dc93e9e3cdc4f18afdcc76951f4857b46f8e39d2d"}, - {file = "matplotlib-3.8.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5dc945a9cb2deb7d197ba23eb4c210e591d52d77bf0ba27c35fc82dec9fa78d4"}, - {file = "matplotlib-3.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8b5a1bf27d078453aa7b5b27f52580e16360d02df6d3dc9504f3d2ce11f6309"}, - {file = "matplotlib-3.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f25ffb6ad972cdffa7df8e5be4b1e3cadd2f8d43fc72085feb1518006178394"}, - {file = "matplotlib-3.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee482731c8c17d86d9ddb5194d38621f9b0f0d53c99006275a12523ab021732"}, - {file = "matplotlib-3.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36eafe2128772195b373e1242df28d1b7ec6c04c15b090b8d9e335d55a323900"}, - {file = "matplotlib-3.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:061ee58facb3580cd2d046a6d227fb77e9295599c5ec6ad069f06b5821ad1cfc"}, - {file = "matplotlib-3.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3cc3776836d0f4f22654a7f2d2ec2004618d5cf86b7185318381f73b80fd8a2d"}, - {file = "matplotlib-3.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6c49a2bd6981264bddcb8c317b6bd25febcece9e2ebfcbc34e7f4c0c867c09dc"}, - {file = "matplotlib-3.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ed11654fc83cd6cfdf6170b453e437674a050a452133a064d47f2f1371f8d3"}, - {file = "matplotlib-3.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dae97fdd6996b3a25da8ee43e3fc734fff502f396801063c6b76c20b56683196"}, - {file = "matplotlib-3.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:87df75f528020a6299f76a1d986c0ed4406e3b2bd44bc5e306e46bca7d45e53e"}, - {file = "matplotlib-3.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:90d74a95fe055f73a6cd737beecc1b81c26f2893b7a3751d52b53ff06ca53f36"}, - {file = "matplotlib-3.8.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c3499c312f5def8f362a2bf761d04fa2d452b333f3a9a3f58805273719bf20d9"}, - {file = "matplotlib-3.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31e793c8bd4ea268cc5d3a695c27b30650ec35238626961d73085d5e94b6ab68"}, - {file = "matplotlib-3.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d5ee602ef517a89d1f2c508ca189cfc395dd0b4a08284fb1b97a78eec354644"}, - {file = "matplotlib-3.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5de39dc61ca35342cf409e031f70f18219f2c48380d3886c1cf5ad9f17898e06"}, - {file = "matplotlib-3.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dd386c80a98b5f51571b9484bf6c6976de383cd2a8cd972b6a9562d85c6d2087"}, - {file = "matplotlib-3.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f691b4ef47c7384d0936b2e8ebdeb5d526c81d004ad9403dfb9d4c76b9979a93"}, - {file = "matplotlib-3.8.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b11f354aae62a2aa53ec5bb09946f5f06fc41793e351a04ff60223ea9162955"}, - {file = "matplotlib-3.8.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f54b9fb87ca5acbcdd0f286021bedc162e1425fa5555ebf3b3dfc167b955ad9"}, - {file = "matplotlib-3.8.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:60a6e04dfd77c0d3bcfee61c3cd335fff1b917c2f303b32524cd1235e194ef99"}, - {file = "matplotlib-3.8.0.tar.gz", hash = "sha256:df8505e1c19d5c2c26aff3497a7cbd3ccfc2e97043d1e4db3e76afa399164b69"}, + {file = "matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7"}, + {file = "matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367"}, + {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78e4f2cedf303869b782071b55fdde5987fda3038e9d09e58c91cc261b5ad18"}, + {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e208f46cf6576a7624195aa047cb344a7f802e113bb1a06cfd4bee431de5e31"}, + {file = "matplotlib-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a569130ff53798ea5f50afce7406e91fdc471ca1e0e26ba976a8c734c9427a"}, + {file = "matplotlib-3.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:830f00640c965c5b7f6bc32f0d4ce0c36dfe0379f7dd65b07a00c801713ec40a"}, + {file = "matplotlib-3.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d86593ccf546223eb75a39b44c32788e6f6440d13cfc4750c1c15d0fcb850b63"}, + {file = "matplotlib-3.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a5430836811b7652991939012f43d2808a2db9b64ee240387e8c43e2e5578c8"}, + {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9576723858a78751d5aacd2497b8aef29ffea6d1c95981505877f7ac28215c6"}, + {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba9cbd8ac6cf422f3102622b20f8552d601bf8837e49a3afed188d560152788"}, + {file = "matplotlib-3.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:03f9d160a29e0b65c0790bb07f4f45d6a181b1ac33eb1bb0dd225986450148f0"}, + {file = "matplotlib-3.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:3773002da767f0a9323ba1a9b9b5d00d6257dbd2a93107233167cfb581f64717"}, + {file = "matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627"}, + {file = "matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4"}, + {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d"}, + {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331"}, + {file = "matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213"}, + {file = "matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630"}, + {file = "matplotlib-3.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:deaed9ad4da0b1aea77fe0aa0cebb9ef611c70b3177be936a95e5d01fa05094f"}, + {file = "matplotlib-3.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:172f4d0fbac3383d39164c6caafd3255ce6fa58f08fc392513a0b1d3b89c4f89"}, + {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7d36c2209d9136cd8e02fab1c0ddc185ce79bc914c45054a9f514e44c787917"}, + {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5864bdd7da445e4e5e011b199bb67168cdad10b501750367c496420f2ad00843"}, + {file = "matplotlib-3.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8345b48e95cee45ff25192ed1f4857273117917a4dcd48e3905619bcd9c9b8"}, + {file = "matplotlib-3.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:7c48d9e221b637c017232e3760ed30b4e8d5dfd081daf327e829bf2a72c731b4"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa11b3c6928a1e496c1a79917d51d4cd5d04f8a2e75f21df4949eeefdf697f4b"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1095fecf99eeb7384dabad4bf44b965f929a5f6079654b681193edf7169ec20"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bddfb1db89bfaa855912261c805bd0e10218923cc262b9159a49c29a7a1c1afa"}, + {file = "matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1"}, ] [package.dependencies] contourpy = ">=1.0.1" cycler = ">=0.10" fonttools = ">=4.22.0" -importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} -kiwisolver = ">=1.0.1" +kiwisolver = ">=1.3.1" numpy = ">=1.21,<2" packaging = ">=20.0" -pillow = ">=6.2.0" +pillow = ">=8" pyparsing = ">=2.3.1" python-dateutil = ">=2.7" -setuptools_scm = ">=7" [[package]] name = "matplotlib-inline" @@ -1714,58 +1647,82 @@ traitlets = "*" [[package]] name = "mitmproxy" -version = "9.0.1" +version = "10.1.5" description = "An interactive, SSL/TLS-capable intercepting proxy for HTTP/1, HTTP/2, and WebSockets." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "mitmproxy-9.0.1-py3-none-any.whl", hash = "sha256:8df75b769725e2d8e7fc064e8397f46610d103630c27f6175e41151d6e523e4c"}, + {file = "mitmproxy-10.1.5-py3-none-any.whl", hash = "sha256:24d78fe45ee30573ab2c57ab9f65d4be6bc45bbc322e36e9555bb898ee787ae8"}, ] [package.dependencies] -asgiref = ">=3.2.10,<3.6" -Brotli = ">=1.0,<1.1" +aioquic-mitmproxy = ">=0.9.21,<0.10" +asgiref = ">=3.2.10,<3.8" +Brotli = ">=1.0,<1.2" certifi = ">=2019.9.11" -cryptography = ">=38.0,<38.1" -flask = ">=1.1.1,<2.3" +cryptography = ">=38.0,<41.1" +flask = ">=1.1.1,<3.1" h11 = ">=0.11,<0.15" h2 = ">=4.1,<5" hyperframe = ">=6.0,<7" kaitaistruct = ">=0.10,<0.11" ldap3 = ">=2.8,<2.10" -mitmproxy-wireguard = ">=0.1.6,<0.2" +mitmproxy-rs = ">=0.4,<0.5" msgpack = ">=1.0.0,<1.1.0" passlib = ">=1.6.5,<1.8" protobuf = ">=3.14,<5" publicsuffix2 = ">=2.20190812,<3" pydivert = {version = ">=2.0.3,<2.2", markers = "sys_platform == \"win32\""} -pyOpenSSL = ">=22.1,<22.2" -pyparsing = ">=2.4.2,<3.1" +pyOpenSSL = ">=22.1,<23.4" +pyparsing = ">=2.4.2,<3.2" pyperclip = ">=1.6.0,<1.9" -"ruamel.yaml" = ">=0.16,<0.18" +"ruamel.yaml" = ">=0.16,<0.19" sortedcontainers = ">=2.3,<2.5" -tornado = ">=6.1,<7" -typing-extensions = {version = ">=4.3,<4.5", markers = "python_version < \"3.10\""} -urwid = ">=2.1.1,<2.2" +tornado = ">=6.2,<7" +typing-extensions = {version = ">=4.3,<5", markers = "python_version < \"3.11\""} +urwid-mitmproxy = ">=2.1.1,<2.2" wsproto = ">=1.0,<1.3" -zstandard = ">=0.11,<0.20" +zstandard = ">=0.11,<0.23" [package.extras] -dev = ["click (>=7.0,<8.2)", "hypothesis (>=5.8,<7)", "parver (>=0.1,<2.0)", "pdoc (>=4.0.0)", "pyinstaller (==5.6.2)", "pytest (>=6.1.0,<8)", "pytest-asyncio (>=0.17,<0.21)", "pytest-cov (>=2.7.1,<4.1)", "pytest-timeout (>=1.3.3,<2.2)", "pytest-xdist (>=2.1.0,<3.1)", "requests (>=2.9.1,<3)", "tox (>=3.5,<4)", "wheel (>=0.36.2,<0.39)"] +dev = ["build (>=0.10.0)", "click (>=7.0,<8.2)", "hypothesis (>=5.8,<7)", "pdoc (>=4.0.0)", "pyinstaller (==6.1.0)", "pytest (>=6.1.0,<8)", "pytest-asyncio (>=0.17,<0.22)", "pytest-cov (>=2.7.1,<4.2)", "pytest-timeout (>=1.3.3,<2.3)", "pytest-xdist (>=2.1.0,<3.4)", "requests (>=2.9.1,<3)", "tox (>=3.5,<5)", "wheel (>=0.36.2,<0.42)"] [[package]] -name = "mitmproxy-wireguard" -version = "0.1.23" -description = "WireGuard interface for mitmproxy" +name = "mitmproxy-macos" +version = "0.4.1" +description = "" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" +files = [ + {file = "mitmproxy_macos-0.4.1-py3-none-any.whl", hash = "sha256:3720561fc8788f2ac71a063780dfb28e3353f216f978b04f8a0aa865835af97b"}, +] + +[[package]] +name = "mitmproxy-rs" +version = "0.4.1" +description = "" +optional = false +python-versions = ">=3.10" files = [ - {file = "mitmproxy_wireguard-0.1.23-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:661471e8a363b16d5d871c6c6c30205b16b636574b5bc062b3f158d8b76951ad"}, - {file = "mitmproxy_wireguard-0.1.23-cp37-abi3-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:f433fa20358ab2999ba9233819649d2a5a5c2b95eac7e95f4533de90d978d115"}, - {file = "mitmproxy_wireguard-0.1.23-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82ac397bcd8ac3cfd08c91b2039b0ba5dc67edfa65fb52ab0e5df81220bdb913"}, - {file = "mitmproxy_wireguard-0.1.23-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23a55cd010d9d116b23101b5b8b963d10f3d38d6f9be0f58ba53288012f4fef9"}, - {file = "mitmproxy_wireguard-0.1.23-cp37-abi3-win_amd64.whl", hash = "sha256:f2f6121dc4d2d8b692124f3d027f4c839ef764278d473fb89357841b0a4406cf"}, - {file = "mitmproxy_wireguard-0.1.23.tar.gz", hash = "sha256:b0f7b44ef9b0601307c122c5fe1ce57368c2fc9330097ec576984a0d640b4727"}, + {file = "mitmproxy_rs-0.4.1-cp310-abi3-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:8674af6f9be7c1d26508f355322d3ce6a33f344eacefef403e0784bb10941054"}, + {file = "mitmproxy_rs-0.4.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70adbcb0bd4d4c9a5ada99dc720747f9ab2c10357c1350b02e064466d77760c"}, + {file = "mitmproxy_rs-0.4.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5da094d204ccca4b76f5db25c69ee34f8ad1aa9ac5e14ed72b8a91a6b8bcddd"}, + {file = "mitmproxy_rs-0.4.1-cp310-abi3-win_amd64.whl", hash = "sha256:037f627ae08ca62af50e62e8915b6563a3a8b55313e5aa2266406a0a27b19092"}, + {file = "mitmproxy_rs-0.4.1.tar.gz", hash = "sha256:179bc584a771ec39c16b3a87bde7faffa3872137aad477173ad4435b49ef855e"}, +] + +[package.dependencies] +mitmproxy_macos = {version = "0.4.1", markers = "sys_platform == \"darwin\""} +mitmproxy_windows = {version = "0.4.1", markers = "os_name == \"nt\""} + +[[package]] +name = "mitmproxy-windows" +version = "0.4.1" +description = "" +optional = false +python-versions = ">=3.10" +files = [ + {file = "mitmproxy_windows-0.4.1-py3-none-any.whl", hash = "sha256:1a9c8e024d1b2e5544cc81bfb95bf2e713260191e64523e1c5b3bef98ad960ab"}, ] [[package]] @@ -1853,36 +1810,47 @@ test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] name = "numpy" -version = "1.25.2" +version = "1.26.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, - {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, - {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, - {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, - {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, - {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, - {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, - {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, - {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, - {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, - {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, - {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, + {file = "numpy-1.26.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f"}, + {file = "numpy-1.26.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440"}, + {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75"}, + {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00"}, + {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe"}, + {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523"}, + {file = "numpy-1.26.2-cp310-cp310-win32.whl", hash = "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9"}, + {file = "numpy-1.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919"}, + {file = "numpy-1.26.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841"}, + {file = "numpy-1.26.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1"}, + {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a"}, + {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b"}, + {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7"}, + {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8"}, + {file = "numpy-1.26.2-cp311-cp311-win32.whl", hash = "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186"}, + {file = "numpy-1.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d"}, + {file = "numpy-1.26.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0"}, + {file = "numpy-1.26.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75"}, + {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7"}, + {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6"}, + {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6"}, + {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec"}, + {file = "numpy-1.26.2-cp312-cp312-win32.whl", hash = "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167"}, + {file = "numpy-1.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e"}, + {file = "numpy-1.26.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef"}, + {file = "numpy-1.26.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2"}, + {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3"}, + {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818"}, + {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210"}, + {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36"}, + {file = "numpy-1.26.2-cp39-cp39-win32.whl", hash = "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80"}, + {file = "numpy-1.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841"}, + {file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"}, ] [[package]] @@ -1897,32 +1865,39 @@ files = [ ] [[package]] -name = "oscrypto" -version = "1.3.0" -description = "TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD." +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "oscrypto-1.3.0-py2.py3-none-any.whl", hash = "sha256:2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085"}, - {file = "oscrypto-1.3.0.tar.gz", hash = "sha256:6f5fef59cb5b3708321db7cca56aed8ad7e662853351e7991fcf60ec606d47a4"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] [package.dependencies] -asn1crypto = ">=1.5.1" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] -name = "packaging" -version = "21.3" -description = "Core utilities for Python packages" +name = "paramiko" +version = "3.3.1" +description = "SSH2 protocol library" optional = false python-versions = ">=3.6" files = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, + {file = "paramiko-3.3.1-py3-none-any.whl", hash = "sha256:b7bc5340a43de4287bbe22fe6de728aa2c22468b2a849615498dd944c2f275eb"}, + {file = "paramiko-3.3.1.tar.gz", hash = "sha256:6a3777a961ac86dbef375c5f5b8d50014a1a96d0fd7f054a43bc880134b0ff77"}, ] [package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +bcrypt = ">=3.2" +cryptography = ">=3.3" +pynacl = ">=1.5" + +[package.extras] +all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +invoke = ["invoke (>=2.0)"] [[package]] name = "parso" @@ -1980,13 +1955,13 @@ files = [ [[package]] name = "pexpect" -version = "4.8.0" +version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" files = [ - {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, - {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, ] [package.dependencies] @@ -2093,13 +2068,13 @@ tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"] [[package]] name = "prompt-toolkit" -version = "3.0.39" +version = "3.0.41" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, - {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, + {file = "prompt_toolkit-3.0.41-py3-none-any.whl", hash = "sha256:f36fe301fafb7470e86aaf90f036eef600a3210be4decf461a5b1ca8403d3cb2"}, + {file = "prompt_toolkit-3.0.41.tar.gz", hash = "sha256:941367d97fc815548822aa26c2a269fdc4eb21e9ec05fc5d447cf09bad5d75f0"}, ] [package.dependencies] @@ -2107,24 +2082,22 @@ wcwidth = "*" [[package]] name = "protobuf" -version = "4.24.4" +version = "4.25.1" description = "" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "protobuf-4.24.4-cp310-abi3-win32.whl", hash = "sha256:ec9912d5cb6714a5710e28e592ee1093d68c5ebfeda61983b3f40331da0b1ebb"}, - {file = "protobuf-4.24.4-cp310-abi3-win_amd64.whl", hash = "sha256:1badab72aa8a3a2b812eacfede5020472e16c6b2212d737cefd685884c191085"}, - {file = "protobuf-4.24.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e61a27f362369c2f33248a0ff6896c20dcd47b5d48239cb9720134bef6082e4"}, - {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:bffa46ad9612e6779d0e51ae586fde768339b791a50610d85eb162daeb23661e"}, - {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b493cb590960ff863743b9ff1452c413c2ee12b782f48beca77c8da3e2ffe9d9"}, - {file = "protobuf-4.24.4-cp37-cp37m-win32.whl", hash = "sha256:dbbed8a56e56cee8d9d522ce844a1379a72a70f453bde6243e3c86c30c2a3d46"}, - {file = "protobuf-4.24.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6b7d2e1c753715dcfe9d284a25a52d67818dd43c4932574307daf836f0071e37"}, - {file = "protobuf-4.24.4-cp38-cp38-win32.whl", hash = "sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe"}, - {file = "protobuf-4.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:2fa3886dfaae6b4c5ed2730d3bf47c7a38a72b3a1f0acb4d4caf68e6874b947b"}, - {file = "protobuf-4.24.4-cp39-cp39-win32.whl", hash = "sha256:b77272f3e28bb416e2071186cb39efd4abbf696d682cbb5dc731308ad37fa6dd"}, - {file = "protobuf-4.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:9fee5e8aa20ef1b84123bb9232b3f4a5114d9897ed89b4b8142d81924e05d79b"}, - {file = "protobuf-4.24.4-py3-none-any.whl", hash = "sha256:80797ce7424f8c8d2f2547e2d42bfbb6c08230ce5832d6c099a37335c9c90a92"}, - {file = "protobuf-4.24.4.tar.gz", hash = "sha256:5a70731910cd9104762161719c3d883c960151eea077134458503723b60e3667"}, + {file = "protobuf-4.25.1-cp310-abi3-win32.whl", hash = "sha256:193f50a6ab78a970c9b4f148e7c750cfde64f59815e86f686c22e26b4fe01ce7"}, + {file = "protobuf-4.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:3497c1af9f2526962f09329fd61a36566305e6c72da2590ae0d7d1322818843b"}, + {file = "protobuf-4.25.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:0bf384e75b92c42830c0a679b0cd4d6e2b36ae0cf3dbb1e1dfdda48a244f4bcd"}, + {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:0f881b589ff449bf0b931a711926e9ddaad3b35089cc039ce1af50b21a4ae8cb"}, + {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:ca37bf6a6d0046272c152eea90d2e4ef34593aaa32e8873fc14c16440f22d4b7"}, + {file = "protobuf-4.25.1-cp38-cp38-win32.whl", hash = "sha256:abc0525ae2689a8000837729eef7883b9391cd6aa7950249dcf5a4ede230d5dd"}, + {file = "protobuf-4.25.1-cp38-cp38-win_amd64.whl", hash = "sha256:1484f9e692091450e7edf418c939e15bfc8fc68856e36ce399aed6889dae8bb0"}, + {file = "protobuf-4.25.1-cp39-cp39-win32.whl", hash = "sha256:8bdbeaddaac52d15c6dce38c71b03038ef7772b977847eb6d374fc86636fa510"}, + {file = "protobuf-4.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:becc576b7e6b553d22cbdf418686ee4daa443d7217999125c045ad56322dda10"}, + {file = "protobuf-4.25.1-py3-none-any.whl", hash = "sha256:a19731d5e83ae4737bb2a089605e636077ac001d18781b3cf489b9546c7c80d6"}, + {file = "protobuf-4.25.1.tar.gz", hash = "sha256:57d65074b4f5baa4ab5da1605c02be90ac20c8b40fb137d6a8df9f416b0d0ce2"}, ] [[package]] @@ -2193,15 +2166,29 @@ tests = ["pytest"] [[package]] name = "pyasn1" -version = "0.5.0" +version = "0.5.1" 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" files = [ - {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, - {file = "pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"}, + {file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"}, + {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, ] +[[package]] +name = "pyasn1-modules" +version = "0.3.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" +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"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.6.0" + [[package]] name = "pycparser" version = "2.21" @@ -2244,45 +2231,99 @@ pyparsing = ">=2.1.4" [[package]] name = "pygments" -version = "2.16.1" +version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] [package.extras] plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pylsqpack" +version = "0.3.18" +description = "Python wrapper for the ls-qpack QPACK library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pylsqpack-0.3.18-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1f415d2e03c779261ac7ed421a009a4c752eef6f1ef7b5a34c4a463a5e17fbad"}, + {file = "pylsqpack-0.3.18-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c84e6d4dcb708d766a50bfd16579d8a0bff4eb4e5f5dff9f3df4018454d4013b"}, + {file = "pylsqpack-0.3.18-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bac5f2dc255ae70e5a14033e769769b38bd4c980b365dacd88665610f245e36f"}, + {file = "pylsqpack-0.3.18-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75042b442a0a7a283b5adc21045e6583f3c817d40ccec769837bf2f90b79c494"}, + {file = "pylsqpack-0.3.18-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b5fd04bb27180286811f8e1659974e6e5e854a882de3f2aba8caefc1bb9ab81"}, + {file = "pylsqpack-0.3.18-cp38-abi3-win32.whl", hash = "sha256:a2798e1c08bd36875f77a1ebec0f130fdf9e27eebdb0499a764201d55ef78770"}, + {file = "pylsqpack-0.3.18-cp38-abi3-win_amd64.whl", hash = "sha256:40465d025b946bca195bdaed74b3b79fe3f7f419ab1d4bc4109dca34ba9881d7"}, + {file = "pylsqpack-0.3.18-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ae628cd359ecb466dd85f151ea1ad53de3114e5a9ae0f0ac1408fb43a4318032"}, + {file = "pylsqpack-0.3.18-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a248be29d9ca1fa2ebd7ef4b8ac166d17df0d8d4631b4499c8c566e221d4e5b"}, + {file = "pylsqpack-0.3.18-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:005ddce84bdcbf5c3cf99f764504208e1aa0a91a8331bf47108f2708f2a315e6"}, + {file = "pylsqpack-0.3.18-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dd664354422d4cd51c189febb5f5d22bf3d8c453cc25517c04ce01a57478060"}, + {file = "pylsqpack-0.3.18-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c003eb882f41e4dbd093243c67b97c8634209b4d5ba7edd16163b1ff37306254"}, + {file = "pylsqpack-0.3.18-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8ea75152e8cb8b8c7cfef11c3aa5ebe5b226bd850889f56ff70a688e9680acbf"}, + {file = "pylsqpack-0.3.18-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4cccfd91afd589994f844fd1dbae0acdb58a8ab929d8edeadb25339deb6590"}, + {file = "pylsqpack-0.3.18-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06e1bbe47514b83cd03158e5558ef8cc44f578169c1820098be9f3cc4137f16a"}, + {file = "pylsqpack-0.3.18-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1054b0b44f6141a99e84a9aa6a27c9df028e9223747b893e8e37cdc95b602f1"}, + {file = "pylsqpack-0.3.18-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:711f3aa645f72a928e22606c1f026cde905de23efc07028fe1bc7429f73ec8ee"}, + {file = "pylsqpack-0.3.18.tar.gz", hash = "sha256:45ae55e721877505f4d5ccd49591d69353f2a548a8673dfafb251d385b3c097f"}, +] + +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pyopenssl" -version = "22.1.0" +version = "23.3.0" description = "Python wrapper module around the OpenSSL library" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pyOpenSSL-22.1.0-py3-none-any.whl", hash = "sha256:b28437c9773bb6c6958628cf9c3bebe585de661dba6f63df17111966363dd15e"}, - {file = "pyOpenSSL-22.1.0.tar.gz", hash = "sha256:7a83b7b272dd595222d672f5ce29aa030f1fb837630ef229f62e72e395ce8968"}, + {file = "pyOpenSSL-23.3.0-py3-none-any.whl", hash = "sha256:6756834481d9ed5470f4a9393455154bc92fe7a64b7bc6ee2c804e78c52099b2"}, + {file = "pyOpenSSL-23.3.0.tar.gz", hash = "sha256:6b2cba5cc46e822750ec3e5a81ee12819850b11303630d575e98108a079c2b12"}, ] [package.dependencies] -cryptography = ">=38.0.0,<39" +cryptography = ">=41.0.5,<42" [package.extras] -docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] +docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"] test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] name = "pyparsing" -version = "3.0.9" +version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, ] [package.extras] @@ -2528,24 +2569,24 @@ python-versions = ">=3.6" files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, @@ -2553,7 +2594,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, @@ -2561,7 +2602,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, @@ -2569,7 +2610,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, @@ -2626,42 +2667,28 @@ wcmatch = ">=8.3,<9.0" experiments = ["jsonnet (>=0.18,<1.0)"] [[package]] -name = "setuptools" -version = "68.2.2" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, - {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "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-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "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"] - -[[package]] -name = "setuptools-scm" -version = "8.0.4" -description = "the blessed package to manage your versions by scm tags" +name = "service-identity" +version = "23.1.0" +description = "Service identity verification for pyOpenSSL & cryptography." optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-scm-8.0.4.tar.gz", hash = "sha256:b5f43ff6800669595193fd09891564ee9d1d7dcb196cab4b2506d53a2e1c95c7"}, - {file = "setuptools_scm-8.0.4-py3-none-any.whl", hash = "sha256:b47844cd2a84b83b3187a5782c71128c28b4c94cad8bfb871da2784a5cb54c4f"}, + {file = "service_identity-23.1.0-py3-none-any.whl", hash = "sha256:87415a691d52fcad954a500cb81f424d0273f8e7e3ee7d766128f4575080f383"}, + {file = "service_identity-23.1.0.tar.gz", hash = "sha256:ecb33cd96307755041e978ab14f8b14e13b40f1fbd525a4dc78f46d2b986431d"}, ] [package.dependencies] -packaging = ">=20" -setuptools = "*" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} -typing-extensions = "*" +attrs = ">=19.1.0" +cryptography = "*" +pyasn1 = "*" +pyasn1-modules = "*" [package.extras] -docs = ["entangled-cli[rich]", "mkdocs", "mkdocs-entangled-plugin", "mkdocs-material", "mkdocstrings[python]", "pygments"] -rich = ["rich"] -test = ["build", "pytest", "rich", "wheel"] +dev = ["pyopenssl", "service-identity[docs,idna,mypy,tests]"] +docs = ["furo", "myst-parser", "pyopenssl", "sphinx", "sphinx-notfound-page"] +idna = ["idna"] +mypy = ["idna", "mypy", "types-pyopenssl"] +tests = ["coverage[toml] (>=5.0.2)", "pytest"] [[package]] name = "shelljob" @@ -2906,22 +2933,22 @@ files = [ [[package]] name = "tornado" -version = "6.3.3" +version = "6.4" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">= 3.8" files = [ - {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d"}, - {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a"}, - {file = "tornado-6.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f"}, - {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a"}, - {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2"}, - {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0"}, - {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16"}, - {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17"}, - {file = "tornado-6.3.3-cp38-abi3-win32.whl", hash = "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3"}, - {file = "tornado-6.3.3-cp38-abi3-win_amd64.whl", hash = "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5"}, - {file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"}, + {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, + {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, + {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, + {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"}, + {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"}, + {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"}, + {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"}, + {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"}, + {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"}, + {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, + {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, ] [[package]] @@ -2946,28 +2973,28 @@ telegram = ["requests"] [[package]] name = "traitlets" -version = "5.13.0" +version = "5.14.0" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" files = [ - {file = "traitlets-5.13.0-py3-none-any.whl", hash = "sha256:baf991e61542da48fe8aef8b779a9ea0aa38d8a54166ee250d5af5ecf4486619"}, - {file = "traitlets-5.13.0.tar.gz", hash = "sha256:9b232b9430c8f57288c1024b34a8f0251ddcc47268927367a0dd3eeaca40deb5"}, + {file = "traitlets-5.14.0-py3-none-any.whl", hash = "sha256:f14949d23829023013c47df20b4a76ccd1a85effb786dc060f34de7948361b33"}, + {file = "traitlets-5.14.0.tar.gz", hash = "sha256:fcdaa8ac49c04dfa0ed3ee3384ef6dfdb5d6f3741502be247279407679296772"}, ] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.6.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] [[package]] name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] @@ -3068,13 +3095,19 @@ secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "p socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] -name = "urwid" -version = "2.1.2" +name = "urwid-mitmproxy" +version = "2.1.2.1" description = "A full-featured console (xterm et al.) user interface library" optional = false python-versions = "*" files = [ - {file = "urwid-2.1.2.tar.gz", hash = "sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae"}, + {file = "urwid-mitmproxy-2.1.2.1.tar.gz", hash = "sha256:be6238e587acb92bdd43b241af0a10dc23798e8cf3eddef834164eb637686cda"}, + {file = "urwid_mitmproxy-2.1.2.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:29c62a593235d2b69ba4557648588c54420ef030794b9d28e65f50bffdde85c3"}, + {file = "urwid_mitmproxy-2.1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:d93bdc87cbb329cd262f8ada586e954a95ca4cc7249eca5b348b87f47ef1adb5"}, + {file = "urwid_mitmproxy-2.1.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8cb7eb42fcc426ea02c321159631d396ec0cd6ebebabb310f3a4493579ff2e09"}, + {file = "urwid_mitmproxy-2.1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:66c40dcead7fedbb312516e18574d216b0e7c728bf5cd0e240eee53737234b45"}, + {file = "urwid_mitmproxy-2.1.2.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:7a8a95460a519e0388d91a198acb31836dce40d14e599a0b9c24ba70fa4ec64b"}, + {file = "urwid_mitmproxy-2.1.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:d2d536ad412022365b5e1974cde9029b86cfc30f3960ae073f959630f0c27c21"}, ] [[package]] @@ -3108,28 +3141,31 @@ bracex = ">=2.1.1" [[package]] name = "wcwidth" -version = "0.2.9" +version = "0.2.12" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.9-py2.py3-none-any.whl", hash = "sha256:9a929bd8380f6cd9571a968a9c8f4353ca58d7cd812a4822bba831f8d685b223"}, - {file = "wcwidth-0.2.9.tar.gz", hash = "sha256:a675d1a4a2d24ef67096a04b85b02deeecd8e226f57b5e3a72dbb9ed99d27da8"}, + {file = "wcwidth-0.2.12-py2.py3-none-any.whl", hash = "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c"}, + {file = "wcwidth-0.2.12.tar.gz", hash = "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02"}, ] [[package]] name = "werkzeug" -version = "2.1.2" +version = "3.0.1" description = "The comprehensive WSGI web application library." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Werkzeug-2.1.2-py3-none-any.whl", hash = "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255"}, - {file = "Werkzeug-2.1.2.tar.gz", hash = "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6"}, + {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, + {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, ] +[package.dependencies] +MarkupSafe = ">=2.1.1" + [package.extras] -watchdog = ["watchdog"] +watchdog = ["watchdog (>=2.3)"] [[package]] name = "whitenoise" @@ -3243,79 +3279,59 @@ files = [ {file = "yara_python_dex-1.0.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:da45cbc03c347f310cbcb9d262ec2d938fb55304c2eec36b779a0e8b9052fbb3"}, ] -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - [[package]] name = "zstandard" -version = "0.19.0" +version = "0.22.0" description = "Zstandard bindings for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "zstandard-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a65e0119ad39e855427520f7829618f78eb2824aa05e63ff19b466080cd99210"}, - {file = "zstandard-0.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fa496d2d674c6e9cffc561639d17009d29adee84a27cf1e12d3c9be14aa8feb"}, - {file = "zstandard-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f7c68de4f362c1b2f426395fe4e05028c56d0782b2ec3ae18a5416eaf775576"}, - {file = "zstandard-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a7a716bb04b1c3c4a707e38e2dee46ac544fff931e66d7ae944f3019fc55b8"}, - {file = "zstandard-0.19.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:72758c9f785831d9d744af282d54c3e0f9db34f7eae521c33798695464993da2"}, - {file = "zstandard-0.19.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04c298d381a3b6274b0a8001f0da0ec7819d052ad9c3b0863fe8c7f154061f76"}, - {file = "zstandard-0.19.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aef0889417eda2db000d791f9739f5cecb9ccdd45c98f82c6be531bdc67ff0f2"}, - {file = "zstandard-0.19.0-cp310-cp310-win32.whl", hash = "sha256:9d97c713433087ba5cee61a3e8edb54029753d45a4288ad61a176fa4718033ce"}, - {file = "zstandard-0.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:81ab21d03e3b0351847a86a0b298b297fde1e152752614138021d6d16a476ea6"}, - {file = "zstandard-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:593f96718ad906e24d6534187fdade28b611f8ed06e27ba972ba48aecec45fc6"}, - {file = "zstandard-0.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e21032efe673b887464667d09406bab6e16d96b09ad87e80859e3a20b6745b6"}, - {file = "zstandard-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:876567136b0359f6581ecd892bdb4ca03a0eead0265db73206c78cff03bcdb0f"}, - {file = "zstandard-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9087571729c968cd853d54b3f6e9d0ec61e45cd2c31e0eb8a0d4bdbbe6da2f"}, - {file = "zstandard-0.19.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8371217dff635cfc0220db2720fc3ce728cd47e72bb7572cca035332823dbdfc"}, - {file = "zstandard-0.19.0-cp311-cp311-win32.whl", hash = "sha256:126aa8433773efad0871f624339c7984a9c43913952f77d5abeee7f95a0c0860"}, - {file = "zstandard-0.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:0fde1c56ec118940974e726c2a27e5b54e71e16c6f81d0b4722112b91d2d9009"}, - {file = "zstandard-0.19.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:898500957ae5e7f31b7271ace4e6f3625b38c0ac84e8cedde8de3a77a7fdae5e"}, - {file = "zstandard-0.19.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660b91eca10ee1b44c47843894abe3e6cfd80e50c90dee3123befbf7ca486bd3"}, - {file = "zstandard-0.19.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55b3187e0bed004533149882ef8c24e954321f3be81f8a9ceffe35099b82a0d0"}, - {file = "zstandard-0.19.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6d2182e648e79213b3881998b30225b3f4b1f3e681f1c1eaf4cacf19bde1040d"}, - {file = "zstandard-0.19.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ec2c146e10b59c376b6bc0369929647fcd95404a503a7aa0990f21c16462248"}, - {file = "zstandard-0.19.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:67710d220af405f5ce22712fa741d85e8b3ada7a457ea419b038469ba379837c"}, - {file = "zstandard-0.19.0-cp36-cp36m-win32.whl", hash = "sha256:f097dda5d4f9b9b01b3c9fa2069f9c02929365f48f341feddf3d6b32510a2f93"}, - {file = "zstandard-0.19.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f4ebfe03cbae821ef994b2e58e4df6a087470cc522aca502614e82a143365d45"}, - {file = "zstandard-0.19.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b80f6f6478f9d4ca26daee6c61584499493bf97950cfaa1a02b16bb5c2c17e70"}, - {file = "zstandard-0.19.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909bdd4e19ea437eb9b45d6695d722f6f0fd9d8f493e837d70f92062b9f39faf"}, - {file = "zstandard-0.19.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9c90a44470f2999779057aeaf33461cbd8bb59d8f15e983150d10bb260e16e0"}, - {file = "zstandard-0.19.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:401508efe02341ae681752a87e8ac9ef76df85ef1a238a7a21786a489d2c983d"}, - {file = "zstandard-0.19.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47dfa52bed3097c705451bafd56dac26535545a987b6759fa39da1602349d7ba"}, - {file = "zstandard-0.19.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1a4fb8b4ac6772e4d656103ccaf2e43e45bd16b5da324b963d58ef360d09eb73"}, - {file = "zstandard-0.19.0-cp37-cp37m-win32.whl", hash = "sha256:d63b04e16df8ea21dfcedbf5a60e11cbba9d835d44cb3cbff233cfd037a916d5"}, - {file = "zstandard-0.19.0-cp37-cp37m-win_amd64.whl", hash = "sha256:74c2637d12eaacb503b0b06efdf55199a11b1d7c580bd3dd9dfe84cac97ef2f6"}, - {file = "zstandard-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2e4812720582d0803e84aefa2ac48ce1e1e6e200ca3ce1ae2be6d410c1d637ae"}, - {file = "zstandard-0.19.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4514b19abe6dbd36d6c5d75c54faca24b1ceb3999193c5b1f4b685abeabde3d0"}, - {file = "zstandard-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6caed86cd47ae93915d9031dc04be5283c275e1a2af2ceff33932071f3eeff4d"}, - {file = "zstandard-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ccc4727300f223184520a6064c161a90b5d0283accd72d1455bcd85ec44dd0d"}, - {file = "zstandard-0.19.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:879411d04068bd489db57dcf6b82ffad3c5fb2a1fdd30817c566d8b7bedee442"}, - {file = "zstandard-0.19.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c9ca56345b0c5574db47560603de9d05f63cce5dfeb3a456eb60f3fec737ff2"}, - {file = "zstandard-0.19.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d777d239036815e9b3a093fa9208ad314c040c26d7246617e70e23025b60083a"}, - {file = "zstandard-0.19.0-cp38-cp38-win32.whl", hash = "sha256:be6329b5ba18ec5d32dc26181e0148e423347ed936dda48bf49fb243895d1566"}, - {file = "zstandard-0.19.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d5bb598963ac1f1f5b72dd006adb46ca6203e4fb7269a5b6e1f99e85b07ad38"}, - {file = "zstandard-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:619f9bf37cdb4c3dc9d4120d2a1003f5db9446f3618a323219f408f6a9df6725"}, - {file = "zstandard-0.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b253d0c53c8ee12c3e53d181fb9ef6ce2cd9c41cbca1c56a535e4fc8ec41e241"}, - {file = "zstandard-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c927b6aa682c6d96225e1c797f4a5d0b9f777b327dea912b23471aaf5385376"}, - {file = "zstandard-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f01b27d0b453f07cbcff01405cdd007e71f5d6410eb01303a16ba19213e58e4"}, - {file = "zstandard-0.19.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c7560f622e3849cc8f3e999791a915addd08fafe80b47fcf3ffbda5b5151047c"}, - {file = "zstandard-0.19.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e892d3177380ec080550b56a7ffeab680af25575d291766bdd875147ba246a91"}, - {file = "zstandard-0.19.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60a86b7b2b1c300779167cf595e019e61afcc0e20c4838692983a921db9006ac"}, - {file = "zstandard-0.19.0-cp39-cp39-win32.whl", hash = "sha256:755020d5aeb1b10bffd93d119e7709a2a7475b6ad79c8d5226cea3f76d152ce0"}, - {file = "zstandard-0.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:55a513ec67e85abd8b8b83af8813368036f03e2d29a50fc94033504918273980"}, - {file = "zstandard-0.19.0.tar.gz", hash = "sha256:31d12fcd942dd8dbf52ca5f6b1bbe287f44e5d551a081a983ff3ea2082867863"}, + {file = "zstandard-0.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:275df437ab03f8c033b8a2c181e51716c32d831082d93ce48002a5227ec93019"}, + {file = "zstandard-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ac9957bc6d2403c4772c890916bf181b2653640da98f32e04b96e4d6fb3252a"}, + {file = "zstandard-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe3390c538f12437b859d815040763abc728955a52ca6ff9c5d4ac707c4ad98e"}, + {file = "zstandard-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1958100b8a1cc3f27fa21071a55cb2ed32e9e5df4c3c6e661c193437f171cba2"}, + {file = "zstandard-0.22.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93e1856c8313bc688d5df069e106a4bc962eef3d13372020cc6e3ebf5e045202"}, + {file = "zstandard-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1a90ba9a4c9c884bb876a14be2b1d216609385efb180393df40e5172e7ecf356"}, + {file = "zstandard-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3db41c5e49ef73641d5111554e1d1d3af106410a6c1fb52cf68912ba7a343a0d"}, + {file = "zstandard-0.22.0-cp310-cp310-win32.whl", hash = "sha256:d8593f8464fb64d58e8cb0b905b272d40184eac9a18d83cf8c10749c3eafcd7e"}, + {file = "zstandard-0.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:f1a4b358947a65b94e2501ce3e078bbc929b039ede4679ddb0460829b12f7375"}, + {file = "zstandard-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:589402548251056878d2e7c8859286eb91bd841af117dbe4ab000e6450987e08"}, + {file = "zstandard-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a97079b955b00b732c6f280d5023e0eefe359045e8b83b08cf0333af9ec78f26"}, + {file = "zstandard-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:445b47bc32de69d990ad0f34da0e20f535914623d1e506e74d6bc5c9dc40bb09"}, + {file = "zstandard-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33591d59f4956c9812f8063eff2e2c0065bc02050837f152574069f5f9f17775"}, + {file = "zstandard-0.22.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:888196c9c8893a1e8ff5e89b8f894e7f4f0e64a5af4d8f3c410f0319128bb2f8"}, + {file = "zstandard-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:53866a9d8ab363271c9e80c7c2e9441814961d47f88c9bc3b248142c32141d94"}, + {file = "zstandard-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ac59d5d6910b220141c1737b79d4a5aa9e57466e7469a012ed42ce2d3995e88"}, + {file = "zstandard-0.22.0-cp311-cp311-win32.whl", hash = "sha256:2b11ea433db22e720758cba584c9d661077121fcf60ab43351950ded20283440"}, + {file = "zstandard-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:11f0d1aab9516a497137b41e3d3ed4bbf7b2ee2abc79e5c8b010ad286d7464bd"}, + {file = "zstandard-0.22.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6c25b8eb733d4e741246151d895dd0308137532737f337411160ff69ca24f93a"}, + {file = "zstandard-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9b2cde1cd1b2a10246dbc143ba49d942d14fb3d2b4bccf4618d475c65464912"}, + {file = "zstandard-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a88b7df61a292603e7cd662d92565d915796b094ffb3d206579aaebac6b85d5f"}, + {file = "zstandard-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466e6ad8caefb589ed281c076deb6f0cd330e8bc13c5035854ffb9c2014b118c"}, + {file = "zstandard-0.22.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1d67d0d53d2a138f9e29d8acdabe11310c185e36f0a848efa104d4e40b808e4"}, + {file = "zstandard-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:39b2853efc9403927f9065cc48c9980649462acbdf81cd4f0cb773af2fd734bc"}, + {file = "zstandard-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a1b2effa96a5f019e72874969394edd393e2fbd6414a8208fea363a22803b45"}, + {file = "zstandard-0.22.0-cp312-cp312-win32.whl", hash = "sha256:88c5b4b47a8a138338a07fc94e2ba3b1535f69247670abfe422de4e0b344aae2"}, + {file = "zstandard-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:de20a212ef3d00d609d0b22eb7cc798d5a69035e81839f549b538eff4105d01c"}, + {file = "zstandard-0.22.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d75f693bb4e92c335e0645e8845e553cd09dc91616412d1d4650da835b5449df"}, + {file = "zstandard-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:36a47636c3de227cd765e25a21dc5dace00539b82ddd99ee36abae38178eff9e"}, + {file = "zstandard-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68953dc84b244b053c0d5f137a21ae8287ecf51b20872eccf8eaac0302d3e3b0"}, + {file = "zstandard-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2612e9bb4977381184bb2463150336d0f7e014d6bb5d4a370f9a372d21916f69"}, + {file = "zstandard-0.22.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23d2b3c2b8e7e5a6cb7922f7c27d73a9a615f0a5ab5d0e03dd533c477de23004"}, + {file = "zstandard-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d43501f5f31e22baf822720d82b5547f8a08f5386a883b32584a185675c8fbf"}, + {file = "zstandard-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a493d470183ee620a3df1e6e55b3e4de8143c0ba1b16f3ded83208ea8ddfd91d"}, + {file = "zstandard-0.22.0-cp38-cp38-win32.whl", hash = "sha256:7034d381789f45576ec3f1fa0e15d741828146439228dc3f7c59856c5bcd3292"}, + {file = "zstandard-0.22.0-cp38-cp38-win_amd64.whl", hash = "sha256:d8fff0f0c1d8bc5d866762ae95bd99d53282337af1be9dc0d88506b340e74b73"}, + {file = "zstandard-0.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2fdd53b806786bd6112d97c1f1e7841e5e4daa06810ab4b284026a1a0e484c0b"}, + {file = "zstandard-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73a1d6bd01961e9fd447162e137ed949c01bdb830dfca487c4a14e9742dccc93"}, + {file = "zstandard-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9501f36fac6b875c124243a379267d879262480bf85b1dbda61f5ad4d01b75a3"}, + {file = "zstandard-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48f260e4c7294ef275744210a4010f116048e0c95857befb7462e033f09442fe"}, + {file = "zstandard-0.22.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959665072bd60f45c5b6b5d711f15bdefc9849dd5da9fb6c873e35f5d34d8cfb"}, + {file = "zstandard-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d22fdef58976457c65e2796e6730a3ea4a254f3ba83777ecfc8592ff8d77d303"}, + {file = "zstandard-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a7ccf5825fd71d4542c8ab28d4d482aace885f5ebe4b40faaa290eed8e095a4c"}, + {file = "zstandard-0.22.0-cp39-cp39-win32.whl", hash = "sha256:f058a77ef0ece4e210bb0450e68408d4223f728b109764676e1a13537d056bb0"}, + {file = "zstandard-0.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:e9e9d4e2e336c529d4c435baad846a181e39a982f823f7e4495ec0b0ec8538d2"}, + {file = "zstandard-0.22.0.tar.gz", hash = "sha256:8226a33c542bcb54cd6bd0a366067b610b41713b64c9abec1bc4533d69f51e70"}, ] [package.dependencies] @@ -3326,5 +3342,5 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" -python-versions = "^3.9" -content-hash = "c5c77323e789db4c24f54d8c29ca9d7a7127f9091e7adb9b8f01e976a03eebe8" +python-versions = "^3.10" +content-hash = "e8daa22be0ec4957f7268f3d2dec0dca434b99433d0143cc750be770b9179019" diff --git a/pyproject.toml b/pyproject.toml index 36fb266ecd..fc21faeead 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "mobsf" -version = "3.7.8" +version = "3.8.0" description = "Mobile Security Framework (MobSF) is an automated, all-in-one mobile application (Android/iOS/Windows) pen-testing, malware analysis and security assessment framework capable of performing static and dynamic analysis." keywords = ["mobsf", "mobile security framework", "mobile security", "security tool", "static analysis", "dynamic analysis", "malware analysis"] authors = ["Ajin Abraham "] @@ -23,7 +23,7 @@ classifiers = [ mobsf = "mobsf.__main__:main" [tool.poetry.dependencies] -python = "^3.9" +python = "^3.10" django = ">=3.1.5" lxml = ">=4.6.2" rsa = ">=4.7" @@ -38,11 +38,10 @@ gunicorn = {version = ">=20.0.4", markers = "sys_platform != 'win32'"} psutil = ">=5.8.0" shelljob = ">=0.6.2" asn1crypto = ">=1.4.0" -oscrypto = ">=1.2.1" distro = ">=1.5.0" ip2location = "8.10.0" lief = ">=0.12.3" -http-tools = ">=3.0.0" +http-tools = ">=4.0.0" pdfkit = ">=0.6.1" google-play-scraper = ">=0.1.2" androguard = "3.4.0a1" @@ -56,6 +55,7 @@ arpy = "2.3.0" apksigtool = "0.1.0" tzdata = "^2023.3" libsast = "^2.0.0" +paramiko = "^3.3.1" [build-system] diff --git a/setup.bat b/setup.bat index 81071e04fe..67f1b63bdf 100644 --- a/setup.bat +++ b/setup.bat @@ -2,7 +2,7 @@ rem Python Check set /a count=0 where python >nul 2>&1 && ( - echo [INSTALL] Checking for Python version 3.9+ + echo [INSTALL] Checking for Python version 3.10+ :redo if %count% lss 3 ( set /a count+=1 @@ -13,10 +13,10 @@ where python >nul 2>&1 && ( ) else ( exit /b ) - echo %var%|findstr /R "[3].[91011]" >nul + echo %var%|findstr /R "[3].[1011]" >nul if errorlevel 1 ( if "%var%"=="" goto redo - echo [ERROR] MobSF dependencies require Python 3.9-3.11. Your python points to %var% + echo [ERROR] MobSF dependencies require Python 3.10-3.11. Your python points to %var% exit /b ) else ( echo [INSTALL] Found %var% diff --git a/setup.sh b/setup.sh index 2cd936a4b3..e959c30b22 100755 --- a/setup.sh +++ b/setup.sh @@ -11,10 +11,10 @@ fi python_version="$(python3 --version 2>&1 | awk '{print $2}')" py_major=$(echo "$python_version" | cut -d'.' -f1) py_minor=$(echo "$python_version" | cut -d'.' -f2) -if [ "$py_major" -eq "3" ] && [ "$py_minor" -gt "8" ] && [ "$py_minor" -lt "12" ]; then +if [ "$py_major" -eq "3" ] && [ "$py_minor" -gt "9" ] && [ "$py_minor" -lt "12" ]; then echo "[INSTALL] Found Python ${python_version}" else - echo "[ERROR] MobSF dependencies require Python 3.9 - 3.11. You have Python version ${python_version} or python3 points to Python ${python_version}." + echo "[ERROR] MobSF dependencies require Python 3.10 - 3.11. You have Python version ${python_version} or python3 points to Python ${python_version}." exit 1 fi diff --git a/tox.ini b/tox.ini index f07dc92e4a..73480825b1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py39, py310, py311 +envlist = py310, py311 skipsdist = True isolated_build = true