diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..eaa4872 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,31 @@ +name: Catherine + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +env: + CARGO_TERM_COLOR: always + +jobs: + workflow: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configuring + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libsoup2.4-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev + + - name: Checking + run: cargo check --verbose + + - name: Testing + run: cargo test --verbose + + - name: Building + run: cargo build --verbose diff --git a/.gitignore b/.gitignore index 0668415..2e3f602 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,4 @@ Cargo.lock build/ __MACOSX/ __pycache__/ -.DS_Store \ No newline at end of file +.DS_Store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9004a30..d3b2cf9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,4 +10,4 @@ git checkout -b BRANCH_NAME 2. A pull request will need to be created and no merge conflicts should be present. -For anyone looking to contribute, please do not hesitate to fix or improve anything in the repository. \ No newline at end of file +For anyone looking to contribute, please do not hesitate to fix or improve anything in the repository. diff --git a/Cargo.toml b/Cargo.toml index c68e074..ea4ed83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "catherine" -description = "The Catherine Framework is a general-purpose cybersecurity framework built to aggregate, validate, decode, decrypt, and maintain data." -version = "0.5.0" +description = "The Catherine Framework is a general-purpose cybersecurity framework built to provide extended support for defense operations." +version = "0.6.0" authors = ["azazelm3dj3d"] license = "BSD-2-Clause" categories = ["cryptography", "command-line-utilities", "encoding"] @@ -20,7 +20,7 @@ serde = "1.0" serde_json = "1.0" libloading = "0.7" prettytable-rs = "0.10.0" -mercy = "1.2.22" +mercy = "2.0.1" rand = "0.7.2" tauri = { version = "1.2.4", features = [] } @@ -29,4 +29,4 @@ ipconfig = "0.3.0" [features] default = [ "custom-protocol" ] -custom-protocol = [ "tauri/custom-protocol" ] \ No newline at end of file +custom-protocol = [ "tauri/custom-protocol" ] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..31e06ab --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +# Project: Catherine Framework (https://github.com/azazelm3dj3d/catherine) +# Author: azazelm3dj3d (https://github.com/azazelm3dj3d) +# License: BSD 2-Clause + +NAME=catherine +PROJ_VERSION=0.6.0 + +run: + @echo "Building $(NAME) v$(PROJ_VERSION)" + make setup_env + make build + make modules + +setup_env: + sudo mkdir -p /opt/catherine/modules + sudo mkdir -p /opt/catherine/modules/db + sudo mkdir -p /opt/catherine/modules/formats/exe + sudo mkdir -p /opt/catherine/modules/formats/hex + sudo mkdir -p /opt/catherine/modules/mercy + sudo mkdir -p /opt/catherine/modules/net/netscan + sudo mkdir -p /opt/catherine/modules/web/parsers + pip3 install -r requirements.txt + +build: + cargo check && cargo build + +modules: + chmod +x build_modules.sh && sudo ./build_modules.sh + + @echo "[+] Configuring Hex 'C' module..." + sudo cc src/modules/formats/hex/c_hex_dump.c -Wall -shared -o /opt/catherine/modules/formats/hex/hex.so + + # Cleanup spec files from pyinstaller + sudo rm *.spec diff --git a/README.md b/README.md index cbfbc3e..f049edf 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@
- +
-

- Catherine -

+[![Catherine](https://github.com/azazelm3dj3d/catherine/actions/workflows/workflow.yml/badge.svg)](https://github.com/azazelm3dj3d/catherine/actions/workflows/workflow.yml) -The Catherine Framework is a general-purpose cybersecurity framework built to aggregate, validate, decode, decrypt, and maintain data. Catherine currently collects information from dumping hexadecimal content from files, validates malicious domains & IP addresses, attempts to crack unknown hashes, handles real-time database analysis, various types of decoding, and much more. Thanks to Catherine being built in an easily packaged executable, you can quickly download the tool by running `cargo install catherine` via the `Cargo` ecosystem. Catherine can also be quickly compiled by pulling down the source code from `git` and simply running `cargo build`. +The Catherine Framework is a general-purpose cybersecurity framework built to provide extended support for defense operations. Catherine currently collects information from dumping hexadecimal content from files, validates malicious domains & IP addresses, attempts to crack unknown hashes, handles real-time database analysis, various types of decoding, and much more in a quick CLI utility. Thanks to Catherine being built in an easily packaged executable, you can quickly download the tool by running `cargo install catherine` via the `Cargo` ecosystem. Catherine can also be quickly compiled by pulling down the source code from `git` and simply running `cargo build` or `make` to build alongside modules. + +NOTE: Modules do require Go, Python, and C build utilities to be installed. Catherine provides a Command Line Interface (CLI) and Graphical User Interface (GUI) built into the executable. This means whether you install from source or `Cargo`, you can choose your method of use. +This project will most likely be in an experimental state for a long time. This project is a personal endeavor to explore different ways to control or manipulate data utilizing Rust. A more stable and production-oriented crate is available here: [Mercy](https://github.com/azazelm3dj3d/mercy). Mercy is an open-source Rust crate and CLI designed for building cybersecurity utilities and projects. + ## Installation You can easily install via the `Cargo` CLI: @@ -26,7 +28,7 @@ First, clone the repository using `git`: git clone https://github.com/azazelm3dj3d/catherine.git ``` -Once you've cloned the repository and you're in the correct directory, simply run the following command: +Once you've cloned the repository, and you're in the correct directory, simply run the following command: ```bash cargo build @@ -34,16 +36,13 @@ cargo build Now you'll have a local debug build available for testing under `target/debug/catherine`. -Catherine also offers custom modules for Linux operating systems. You can access these modules by installing Catherine via the `catherine_install` script. - -You can review the script [here](https://github.com/azazelm3dj3d/catherine/blob/main/catherine_install). +If you're interested in working with the Catherine modules, you can use the `make` build ecosystem to create executables for Catherine: ```bash -# The script requires sudo privileges to build a directory under `/opt/catherine/` -sudo ./catherine_install +make ``` -NOTE: I am working on converting all external [modules](https://github.com/azazelm3dj3d/catherine-modules) (Python, C, Go) into native modules (Rust) to offer everything in a built-in executable via `Cargo` without any extra steps, but for now, I've made sure to keep them accessible (excluding the GUI) for extended functionality. +I am working on converting all external [modules](https://github.com/azazelm3dj3d/catherine-modules) (Python, C, Go) into native modules (Rust) to offer everything in a built-in executable via `Cargo` without any extra steps, but for now, I've made sure to keep them accessible (excluding the GUI) for extended functionality. ## Usage @@ -59,6 +58,6 @@ If a GUI is more your style, there is a simple version available with the majori 🦀 Catherine [v0.x.x] (None) ☀️ 〉launch ``` -NOTE: I am still working on making the GUI a little nicer looking, but a basic version is currently available for testing. +If a bug or issue is found, please report it [here](https://github.com/azazelm3dj3d/catherine/issues). -If a bug or issue is found, please report it [here](https://github.com/azazelm3dj3d/catherine/issues). \ No newline at end of file +*GUI logo provided by Freepik w/ modification from azazelm3dj3d. diff --git a/assets/catherine_banner.png b/assets/catherine_banner.png new file mode 100644 index 0000000..3762b00 Binary files /dev/null and b/assets/catherine_banner.png differ diff --git a/assets/catherine_icon.jpg b/assets/catherine_icon.jpg new file mode 100644 index 0000000..498b67a Binary files /dev/null and b/assets/catherine_icon.jpg differ diff --git a/assets/catherine_icon.png b/assets/catherine_icon.png index 30071a2..555c15d 100644 Binary files a/assets/catherine_icon.png and b/assets/catherine_icon.png differ diff --git a/build.rs b/build.rs index 3b92901..dbd46db 100644 --- a/build.rs +++ b/build.rs @@ -6,4 +6,4 @@ fn main() { tauri_build::build() -} \ No newline at end of file +} diff --git a/build_modules.sh b/build_modules.sh new file mode 100755 index 0000000..ea72d2c --- /dev/null +++ b/build_modules.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +# Project: Catherine Framework (https://github.com/azazelm3dj3d/catherine) +# Author: azazelm3dj3d (https://github.com/azazelm3dj3d) +# License: BSD 2-Clause + +# NetScan +# TODO: Test this function to verify it works, Go is the odd man out +function netscan() { + cd src/modules/net/netscan/src + echo "[+] Configuring NetScan module..." + + go build src/modules/net/netscan/src/main.go -o /opt/catherine/modules/net/netscan + + if [ -f "/opt/catherine/modules/net/netscan" ] + then + echo "[+] NetScan module successfully built" + else + echo "[-] NetScan module was not built properly" + fi + + echo "" +} + +# Web parsers +function parsers() { + # Link parser + echo "[+] Configuring Link Parser module..." + pyinstaller src/modules/web/parsers/links.py --onefile --clean -n links --distpath /opt/catherine/modules/web/parsers/ 2>/dev/null + + if [ -f "/opt/catherine/modules/web/parsers/links" ] + then + echo "[+] Link Parser module successfully built" + else + echo "[-] Link Parser module was not built properly" + fi + + echo "" +} + +# Exec Dump +function exec_dump() { + echo "[+] Configuring Windows Exe Dump module..." + pyinstaller src/modules/formats/exe/win_exe_dump.py --onefile --clean -n win_exe_dump --distpath /opt/catherine/modules/formats/exe/ 2>/dev/null + + if [ -f "/opt/catherine/modules/formats/exe/win_exe_dump" ] + then + echo "[+] Windows Exe Dump module successfully built" + else + echo "[-] Windows Exe Dump module was not built properly" + fi + + echo "" +} + +# Redis +function db_redis() { + echo "[+] Configuring Redis Database module..." + pyinstaller src/modules/db/redis.py --onefile --clean -n redis --distpath /opt/catherine/modules/db/ 2>/dev/null + + if [ -f "/opt/catherine/modules/db/redis" ] + then + echo "[+] Redis Database module successfully built" + else + echo "[-] Redis Database module was not built properly" + fi + + echo "" +} + +# Mercy Extension +function mercy_ext() { + echo "[+] Configuring Mercy Extension module..." + pyinstaller src/modules/mercy/extension.py --onefile --clean -n extension --distpath /opt/catherine/modules/mercy/ 2>/dev/null + + if [ -f "/opt/catherine/modules/mercy/extension" ] + then + echo "[+] Mercy Extension module successfully built" + else + echo "[-] Mercy Extension module was not built properly" + fi + + echo "" +} + +# netscan +parsers +exec_dump +db_redis +mercy_ext diff --git a/catherine_install b/catherine_install deleted file mode 100755 index 7202e36..0000000 --- a/catherine_install +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash - -# Project: Catherine Framework (https://github.com/azazelm3dj3d/catherine) -# Author: azazelm3dj3d (https://github.com/azazelm3dj3d) -# License: BSD 2-Clause - -# Created directory for Catherine -catherine_dir="/opt/catherine" - -# Installs Catherine -function install_catherine() { - if [[ ! -d "$catherine_dir" ]]; then - mkdir $catherine_dir - echo "[+] Created Catherine cache successfully" - fi - - if ! catherine_installed="$(type -p "catherine")" || [[ -z $catherine_installed ]]; then - cargo install catherine - echo "[+] Catherine installed successfully" - fi -} - -install_catherine - -function install_modules() { - cd $catherine_dir - - if [[ -d "$catherine_dir/modules" ]]; then - rm -r "$catherine_dir/modules" - git clone https://github.com/azazelm3dj3d/catherine-modules.git - mv "$catherine_dir/catherine-modules" "$catherine_dir/modules" - - echo "[+] Catherine modules installed successfully" - else - git clone https://github.com/azazelm3dj3d/catherine-modules.git - mv "$catherine_dir/catherine-modules" "$catherine_dir/modules" - - if [[ -d "$catherine_dir/modules" ]]; then - echo "[+] Catherine modules installed successfully" - fi - fi -} - -install_modules - -function installation_complete() { - echo "" - echo "Catherine should now be installed!" - echo "You can start the framework by running 'catherine' in your terminal" - exit -} - -installation_complete \ No newline at end of file diff --git a/icons/icon.jpg b/icons/icon.jpg new file mode 100644 index 0000000..498b67a Binary files /dev/null and b/icons/icon.jpg differ diff --git a/icons/icon.png b/icons/icon.png new file mode 100644 index 0000000..555c15d Binary files /dev/null and b/icons/icon.png differ diff --git a/modules.json b/modules.json new file mode 100644 index 0000000..e68040a --- /dev/null +++ b/modules.json @@ -0,0 +1,55 @@ +{ + "author": "azazelm3dj3d", + "version": "0.1.26", + "numOfModules": "6", + "ModulesList": [ + { + "id": 1, + "name": "NetScan", + "description": "Collects publicly available network information about a host", + "version": "1.0.9", + "source_path": "net/netscan/src/*", + "dist_path": "net/netscan/dist/netscan" + }, + { + "id": 2, + "name": "links", + "description": "Parses web content, extracting external and internal links", + "version": "0.2.14", + "source_path": "web/parsers/links.py", + "dist_path": "web/parsers/dist/links" + }, + { + "id": 3, + "name": "Mercy Extension", + "description": "Suite of methods for decryption and decoding data, extends the Mercy Rust crate", + "version": "1.4.15", + "source_path": "mercy/extenstion.py", + "dist_path": "mercy/dist/extenstion" + }, + { + "id": 4, + "name": ["c_hex_dump", "rust_hex_dump"], + "description": "Dumps hexadecimal information for most file types (.exe, .toml, .c, etc.)", + "version": "0.1.11", + "source_path": ["data/hex/c/c_hex_dump.c", "data/hex/rust/rust_hex_dump.rs"], + "dist_path": ["data/hex/c/dist/hex.so", "data/hex/rust/rust_hex_dump.rs"] + }, + { + "id": 5, + "name": "redis_analysis", + "description": "Real-time Redis database analysis and monitoring", + "version": "1.3.36", + "source_path": "db/redis/redis_analysis.py", + "dist_path": "db/redis/dist/redis_analysis" + }, + { + "id": 6, + "name": "exec_dump_win", + "description": "Multi-format parser built to extract various data points from Windows executables, object binaries, DLLs and more (32-bit & 64-bit)", + "version": "0.1.10", + "source_path": "data/exe/exec_dump_win.py", + "dist_path": "data/exe/dist/exec_dump_win" + } + ] +} diff --git a/public/catherine_icon.jpg b/public/catherine_icon.jpg new file mode 100644 index 0000000..498b67a Binary files /dev/null and b/public/catherine_icon.jpg differ diff --git a/public/catherine_icon.png b/public/catherine_icon.png new file mode 100644 index 0000000..555c15d Binary files /dev/null and b/public/catherine_icon.png differ diff --git a/public/crack_hash.html b/public/crack_hash.html new file mode 100644 index 0000000..098aade --- /dev/null +++ b/public/crack_hash.html @@ -0,0 +1,129 @@ + + + + + + + + + + Catherine Framework + + + +
+ + +
+
+
+
+ Crack Hash +
+ +
+
+
+ +
+
+
+ +
+ +
+ +
+

Output

+
+

+
+
+
+
+
+
+ + + + + diff --git a/public/decode.html b/public/decode.html new file mode 100644 index 0000000..f76802c --- /dev/null +++ b/public/decode.html @@ -0,0 +1,142 @@ + + + + + + + + + + Catherine Framework + + + +
+ + +
+
+
+
+ Decode +
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+

Output

+
+

+
+
+
+
+
+
+ + + + + diff --git a/public/defang.html b/public/defang.html new file mode 100644 index 0000000..f276369 --- /dev/null +++ b/public/defang.html @@ -0,0 +1,129 @@ + + + + + + + + + + Catherine Framework + + + +
+ + +
+
+
+
+ Defang +
+ +
+
+
+ +
+
+
+ +
+ +
+ +
+

Output

+
+

+
+
+
+
+
+
+ + + + + diff --git a/public/extract_zip.html b/public/extract_zip.html new file mode 100644 index 0000000..d4601a7 --- /dev/null +++ b/public/extract_zip.html @@ -0,0 +1,129 @@ + + + + + + + + + + Catherine Framework + + + +
+ + +
+
+
+
+ Extract Zip File +
+ +
+
+
+ +
+
+
+ +
+ +
+ +
+

Output

+
+

+
+
+
+
+
+
+ + + + + diff --git a/public/gen_domain.html b/public/gen_domain.html new file mode 100644 index 0000000..5908112 --- /dev/null +++ b/public/gen_domain.html @@ -0,0 +1,129 @@ + + + + + + + + + + Catherine Framework + + + +
+ + +
+
+
+
+ Generate a Domain +
+ +
+
+
+ +
+
+
+ +
+ +
+ +
+

Output

+
+

+
+
+
+
+
+
+ + + + + diff --git a/public/identify.html b/public/identify.html new file mode 100644 index 0000000..c203e56 --- /dev/null +++ b/public/identify.html @@ -0,0 +1,129 @@ + + + + + + + + + + Catherine Framework + + + +
+ + +
+
+
+
+ Identify String +
+ +
+
+
+ +
+
+
+ +
+ +
+ +
+

Output

+
+

+
+
+
+
+
+
+ + + + + diff --git a/public/index.html b/public/index.html index 823a879..17c9855 100644 --- a/public/index.html +++ b/public/index.html @@ -7,283 +7,100 @@ - - - - - Catherine Framework - - - - -
-
-
-
-
-
-
- -
-
-

- Catherine Framework -

-
- - - -
-
-
-
-
-
- Decode -
- -
-
-
- -
-
- -
-
- -
-
-
- -
- -
- -

-
-
-
-
- -
-
-
-
-
- Dump System Information (Linux only) -
- -
- -
- -

-
-
-
-
- -
-
-
-
-
- Defang -
- -
-
- -
-
-
- -
- -
- -

-
-
-
- -
-
-
-
-
- WHOIS Lookup -
- -
-
- -
-
-
- -
- -
- -

-
-
-
- -
-
-
-
-
- Identify String -
- -
-
- -
-
-
- -
- -
- -

-
-
-
- -
-
-
-
-
- Crack Hash -
- -
-
- -
-
-
- -
- -
- -

-
-
-
- -
-
-
-
-
- Generate a Domain -
- -
-
- -
-
-
- -
- -
- -

-
-
-
- -
-
-
-
-
- Extract Zip File(s) -
- -
-
- -
-
-
- -
- -
- -

-
-
-
- -
-
-
-
-
- Exit Catherine -
-
- -
-
-
-
- -
-
-
+ + + + + Catherine Framework + + + +
+ - - +
+
+ Skull logo representing the Catherine framework +

+ Built by azazelm3dj3d (https://github.com/azazelm3dj3d) +

+
+
+
+ + + - \ No newline at end of file + diff --git a/public/js/index.js b/public/js/index.js index 4f954eb..5fdd517 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -82,7 +82,7 @@ const domainGen = () => { }) } -// Extract a zip file contents +// Extract zip file contents const zipExtract = () => { invoke('extract_zip', { extractZipFile: document.getElementById("extractZipFile").value @@ -91,7 +91,16 @@ const zipExtract = () => { }) } +// Parse email contents +const emailParse = () => { + invoke('parse_email', { + parseEmailFile: document.getElementById("parseEmailFile").value + }).then((response) => { + document.getElementById("res_9").innerHTML = response; + }) +} + // Exit application const exitCatherine = () => { invoke("exit_catherine") -} \ No newline at end of file +} diff --git a/public/parse_email.html b/public/parse_email.html new file mode 100644 index 0000000..43f9831 --- /dev/null +++ b/public/parse_email.html @@ -0,0 +1,129 @@ + + + + + + + + + + Catherine Framework + + + +
+ + +
+
+
+
+ Parse Email +
+ +
+
+
+ +
+
+
+ +
+ +
+ +
+

Output

+
+

+
+
+
+
+
+
+ + + + + diff --git a/public/sysdump.html b/public/sysdump.html new file mode 100644 index 0000000..d0f29d2 --- /dev/null +++ b/public/sysdump.html @@ -0,0 +1,119 @@ + + + + + + + + + + Catherine Framework + + + +
+ + +
+
+
+
+ Dump System Information (Linux only) +
+
+ +
+ +
+

Output

+
+

+
+
+
+
+
+
+ + + + + diff --git a/public/whois.html b/public/whois.html new file mode 100644 index 0000000..9b78e68 --- /dev/null +++ b/public/whois.html @@ -0,0 +1,129 @@ + + + + + + + + + + Catherine Framework + + + +
+ + +
+
+
+
+ WHOIS Lookup +
+ +
+
+
+ +
+
+
+ +
+ +
+ +
+

Output

+
+

+
+
+
+
+
+
+ + + + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..12961c2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +requests +bs4 +colorama +redis +prettytable +cryptography +pyjwt +pefile +pyinstaller \ No newline at end of file diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..777b68a --- /dev/null +++ b/server/README.md @@ -0,0 +1,3 @@ +# Server Directory + +This server directory contains everything relevant to using the HTTP server functionality. diff --git a/server/public/README.md b/server/public/README.md index 06c20ad..14c9e6c 100644 --- a/server/public/README.md +++ b/server/public/README.md @@ -1,2 +1,3 @@ # Public Directory -This public directory is available for rendering HTML content when using the `start_server` command. \ No newline at end of file + +This public directory is available for rendering HTML content when using the `start_server` command. diff --git a/server/public/index.html b/server/public/index.html index 427493e..a05cbe3 100644 --- a/server/public/index.html +++ b/server/public/index.html @@ -13,6 +13,6 @@ Catherine Framework -

You know what they say cowboy, easy come easy go...

+

Whatever happens, happens

- \ No newline at end of file + diff --git a/src/catherine.rs b/src/catherine.rs index 42a4d8f..7d4d6a0 100644 --- a/src/catherine.rs +++ b/src/catherine.rs @@ -38,13 +38,14 @@ use crate::core::{ git_downloader, existence }, - x::catherine_shell + shell::catherine_shell }; use mercy::{ - mercy_decode, - mercy_extra, - mercy_malicious, mercy_experimental // status, url + decode, + extra, + malicious, + experimental // status, url }; use crate::ui::controller::launch_gui; @@ -53,19 +54,18 @@ use crate::ui::controller::launch_gui; extern crate ipconfig; pub(crate) static NAME: &str = "Catherine"; -pub(crate) static VERSION: &str = "0.5.0"; +pub(crate) static VERSION: &str = "0.6.0"; -pub(crate) static NETSCAN_PATH: &str = "/opt/catherine/modules/net/netscan/dist/netscan"; -pub(crate) static LINK_PARSER_PATH: &str = "/opt/catherine/modules/web/parsers/dist/links"; -pub(crate) static MERCY_EXT_PATH: &str = "/opt/catherine/modules/mercy/dist/mercy_ext"; -pub(crate) static REDIS_ANALYSIS_PATH: &str = "/opt/catherine/modules/db/redis/dist/redis_analysis"; -pub(crate) static WIN_EXE_DUMP_PATH: &str = "/opt/catherine/modules/data/exe/dist/exec_dump"; +pub(crate) static NETSCAN_PATH: &str = "/opt/catherine/catherine-modules/net/netscan/netscan"; +pub(crate) static LINK_PARSER_PATH: &str = "/opt/catherine/catherine-modules/web/parsers/links"; +pub(crate) static MERCY_EXT_PATH: &str = "/opt/catherine/catherine-modules/mercy/extension"; +pub(crate) static REDIS_ANALYSIS_PATH: &str = "/opt/catherine/catherine-modules/db/redis"; +pub(crate) static WIN_EXE_DUMP_PATH: &str = "/opt/catherine/catherine-modules/data/exe/win_exe_dump"; pub fn init(boot_msg: &str) { // Cool little boot message println!("\n{}", boot_msg); - thread::sleep(time::Duration::from_millis(1000)); let booted_time = Local::now(); let (is_pm, hour) = booted_time.hour12(); @@ -137,12 +137,12 @@ pub fn init(boot_msg: &str) { match set_method { "0" | "base64" => { let encoded_msg = catherine_shell(NAME, VERSION, "set_decode/base64_input".blue()); - pretty_output(&encoded_msg, &mercy_decode("base64", &encoded_msg), "Encoded Message", "Decoded Message"); + pretty_output(&encoded_msg, &decode("base64", &encoded_msg), "Encoded Message", "Decoded Message"); }, "1" | "rot13" => { let encoded_msg = catherine_shell(NAME, VERSION, "set_decode/rot13_input".blue()); - pretty_output(&encoded_msg, &mercy_decode("rot13", &encoded_msg), "Encoded Message", "Decoded Message"); + pretty_output(&encoded_msg, &decode("rot13", &encoded_msg), "Encoded Message", "Decoded Message"); }, @@ -165,21 +165,21 @@ pub fn init(boot_msg: &str) { // NOTE: Doesn't work on macOS "sys_info" => { - println!("{}Internal IP Address: {}\n", mercy_extra("system_info", "all"), mercy_extra("internal_ip", "")); + println!("{}Internal IP Address: {}\n", extra("system_info", "all"), extra("internal_ip", "")); }, "defang" => { let defang_url = catherine_shell(NAME, VERSION, "defang/url".blue()); let set_url: &str = &defang_url; - println!("{}", mercy_extra("defang", set_url)); + println!("{}", extra("defang", set_url)); }, "whois" => { let whois_url = catherine_shell(NAME, VERSION, "whois/url".blue()); let set_url: &str = &whois_url; - println!("{}", mercy_extra("whois", set_url)); + println!("{}", extra("whois", set_url)); }, "mal_query" => { @@ -187,35 +187,63 @@ pub fn init(boot_msg: &str) { let set_url: &str = &mal_url; println!("Domain: {}", set_url); - println!("Status: {}", mercy_malicious("status", set_url)); + println!("Status: {}", malicious("status", set_url)); }, "id" => { let id: String = catherine_shell(NAME, VERSION, "identify/string".blue()); let id_str: &str = &id; - println!("{}", mercy_extra("identify", id_str)); + println!("{}", extra("identify", id_str)); }, "crack_hash" => { let hash: String = catherine_shell(NAME, VERSION, "crack_hash/hash".blue()); let hash_str: &str = &hash; - println!("{}", mercy_extra("crack", hash_str)); + println!("{}", extra("crack", hash_str)); }, "domain_gen" => { let domain_name: String = catherine_shell(NAME, VERSION, "domain_gen/domain".blue()); let domain_str: &str = &domain_name; - mercy_experimental("domain_gen", domain_str); + experimental("domain_gen", domain_str); }, - "extract_zip" => { - let zip_name: String = catherine_shell(NAME, VERSION, "extract/zip".blue()); - let zip_str: &str = &zip_name; + "set_extract" => { + println!("\nAvailable options:"); + println!("[0] zip"); + println!("[1] email, eml\n"); + + let extract_method = catherine_shell(NAME, VERSION, "set_extract".blue()); + let set_method: &str = &extract_method; + + match set_method { + "0" | "zip" => { + let zip_name: String = catherine_shell(NAME, VERSION, "set_extract/zip".blue()); + let zip_str: &str = &zip_name; + + experimental("zip", zip_str); + }, + + "1" | "email" | "eml" => { + + let eml_file: String = catherine_shell(NAME, VERSION, "set_extract/eml".blue()); + let eml_str: &str = &eml_file; - mercy_experimental("zip", zip_str); + println!("{}", extra("parse_email", eml_str)); + }, + + _ => { } + } + }, + + "detect_lang" => { + let lang_data: String = catherine_shell(NAME, VERSION, "detect_lang".blue()); + let lang_str: &str = &lang_data; + + println!("{}", extra("detect_lang", lang_str)); }, // Launches the GUI @@ -239,8 +267,7 @@ pub fn init(boot_msg: &str) { } if existence("/opt/catherine") { - let new_dir = "/opt/catherine"; - let set_dir = Path::new(new_dir); + let set_dir = Path::new("/opt/catherine"); if let Err(err) = env::set_current_dir(&set_dir) { println!("{}", err); @@ -249,8 +276,8 @@ pub fn init(boot_msg: &str) { // Downloads Catherine modules from GitHub git_downloader("https://github.com/azazelm3dj3d/catherine-modules.git"); - if existence("/opt/catherine/modules") { - println!("\nInstallation complete! Modules can be found here: /opt/catherine/modules\n"); + if existence("/opt/catherine") { + println!("\nInstallation complete! Modules can be found here: /opt/catherine/catherine-modules\n"); if let Err(err) = env::set_current_dir(&set_dir) { println!("{}", err); @@ -374,4 +401,4 @@ pub fn shutdown(shutdown_msg: &str) { println!("{}\n", shutdown_msg); thread::sleep(time::Duration::from_millis(1000)); -} \ No newline at end of file +} diff --git a/src/core/commands.rs b/src/core/commands.rs index 85f00a6..9145488 100644 --- a/src/core/commands.rs +++ b/src/core/commands.rs @@ -19,7 +19,7 @@ use colored::{ Colorize, ColoredString }; use serde_json::Value; use super::{ - x::catherine_shell, + shell::catherine_shell, utils::{ connection_handler, find_open_ports, @@ -30,7 +30,7 @@ use super::{ }; use crate::{ - modules::rust_hex_dump::collect_hex, + modules::formats::hex::rs_hex_dump::collect_hex, core::utils::pretty_output }; @@ -71,7 +71,7 @@ pub fn start_server(addr: &str) { pub fn view_modules() { // JSON file - let json_file: &str = "/opt/catherine/modules/modules.json"; // Local + let json_file: &str = "/opt/catherine/catherine-modules/modules.json"; // Local let json_parse = { // Load the JSON file and convert to an easier to read format @@ -111,25 +111,17 @@ pub fn set_module() { // Go acts a little funky on WSL for some reason if set_host == "help" { - if env::consts::OS == "linux" { - Command::new(NETSCAN_PATH) - .arg("help") - .status() - .expect("Failed to execute process"); - } else { - println!("Unable to run module on this operating system"); - } + Command::new(NETSCAN_PATH) + .arg("help") + .status() + .expect("Failed to execute process"); } else { - if env::consts::OS == "linux" { - Command::new(NETSCAN_PATH) - .arg("all") - .arg("--host") - .arg(set_host) - .status() - .expect("Failed to execute process"); - } else { - println!("Unable to run module on this operating system"); - } + Command::new(NETSCAN_PATH) + .arg("all") + .arg("--host") + .arg(set_host) + .status() + .expect("Failed to execute process"); } }, @@ -140,14 +132,10 @@ pub fn set_module() { println!("{}", module_activating); thread::sleep(time::Duration::from_secs(1)); - if env::consts::OS == "linux" { - Command::new(LINK_PARSER_PATH) - .arg(set_host) - .status() - .expect("Failed to execute process"); - } else { - println!("Unable to run module on this operating system"); - } + Command::new(LINK_PARSER_PATH) + .arg(set_host) + .status() + .expect("Failed to execute process"); }, "hex" | "Hex" | "set_module hex" => { @@ -187,13 +175,9 @@ pub fn set_module() { let set_db = catherine_shell(NAME, VERSION, "set_module/db_analysis/set_db".blue()); if set_db == "redis" || set_db == "Redis" || set_db == "0" { - if env::consts::OS == "linux" { - Command::new(REDIS_ANALYSIS_PATH) - .status() - .expect("Failed to execute process"); - } else { - println!("Unable to run module on this operating system"); - } + Command::new(REDIS_ANALYSIS_PATH) + .status() + .expect("Failed to execute process"); } else { println!("Database is not supported yet"); } @@ -208,14 +192,10 @@ pub fn set_module() { println!("NOTE: {}", note_for_user); thread::sleep(time::Duration::from_secs(1)); - if env::consts::OS == "linux" { - Command::new(WIN_EXE_DUMP_PATH) - .arg(file_loc) - .status() - .expect("Failed to execute process"); - } else { - println!("Unable to run module on this operating system"); - } + Command::new(WIN_EXE_DUMP_PATH) + .arg(file_loc) + .status() + .expect("Failed to execute process"); }, "list" | "view" => { @@ -226,7 +206,7 @@ pub fn set_module() { "help" => { // JSON file - let json_file = "/opt/catherine/modules/modules.json"; // Local + let json_file = "/opt/catherine/catherine-modules/modules.json"; // Local let json_parse = { // Load the JSON file and convert to an easier to read format @@ -332,7 +312,7 @@ pub fn win_adapter_dump() { pub fn help_menu() { println!("\n=== General ==="); - pretty_output("start_server\nscan_ports\nsearch_exploit\nset_decode\nsys_info\ndefang\nwhois\nmal_query\nid\ncrack_hash\ndomain_gen\nextract_zip\nlaunch\n", "Start a Rust server\nScan for open local ports\nSearch ExploitDB for an available exploit to review\nDecode an encoded message using one of our provided methods\nPrint local system information to stdout\nDefang a URL or IP address (prints to stdout)\nRun a domain registrar search against the WHOIS API\nRun a domain name search to validate if it's malicious (InQuest API)\nAttempt to identify a string's origins\nAttempt to crack an unknown hash in real-time\nGenerate a string for domain squatting or phishing assessments\nExtract zip contents that are not password protected\nLaunch a GUI built with Tauri", "Command", "Description"); + pretty_output("start_server\nscan_ports\nsearch_exploit\nset_decode\nsys_info\ndefang\nwhois\nmal_query\nid\ncrack_hash\ndomain_gen\nset_extract\ndetect_lang\nlaunch\n", "Start a Rust server\nScan for open local ports\nSearch ExploitDB for an available exploit to review\nDecode an encoded message using one of our provided methods\nPrint local system information to stdout\nDefang a URL or IP address (prints to stdout)\nRun a domain registrar search against the WHOIS API\nRun a domain name search to validate if it's malicious\nAttempt to identify a string's origins\nAttempt to crack an unknown hash in real-time\nGenerate a string for domain squatting or phishing assessments\nExtract contents from selected files using one of our provided methods\nAttempt to detect the language being used (beta)\nLaunch a GUI built with Tauri", "Command", "Description"); println!("\n=== Module ==="); pretty_output("set_module\nview_modules", "Set one of Catherine's modules\nCurrently installed modules", "Command", "Description"); @@ -341,5 +321,5 @@ pub fn help_menu() { pretty_output("netscan\nparser\nhex\ndb_analysis\nexec_dump\nwin_adapter_dump", "Collects public network information about a host\nParses web content, extracting external and internal links\nExports a custom hexadecimal dump for most file types (.exe, .toml, .c, etc.)\nTerminal-based database exploration and monitoring\nMulti-format parser built to extract various data points from executables, object binaries, DLLs and more (32-bit & 64-bit)\nDumps high-level adapter information from a Windows device", "Module", "Description"); println!("\n=== Help ==="); - pretty_output("help\nversion\nexit", "Help menu\nVersion info for Catherine framework\nExit Catherine framework", "Command", "Description"); -} \ No newline at end of file + pretty_output("help\ninstall\nversion\nexit", "Help menu\nInstall modules\nVersion info for Catherine framework\nExit Catherine framework", "Command", "Description"); +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 36977c4..ac696c1 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -6,4 +6,4 @@ pub mod commands; pub mod utils; -pub mod x; \ No newline at end of file +pub mod shell; diff --git a/src/core/x.rs b/src/core/shell.rs similarity index 99% rename from src/core/x.rs rename to src/core/shell.rs index edf06a3..d570884 100644 --- a/src/core/x.rs +++ b/src/core/shell.rs @@ -28,4 +28,4 @@ pub fn catherine_shell(framework: &str, version: &str, active: ColoredString) -> std::io::stdin().read_line(&mut line).expect("[ERROR] Unable to process input"); return line.trim().to_string() -} \ No newline at end of file +} diff --git a/src/core/utils.rs b/src/core/utils.rs index 54a3165..0d78a22 100644 --- a/src/core/utils.rs +++ b/src/core/utils.rs @@ -19,7 +19,7 @@ use std::{ use colored::Colorize; -use super::x::catherine_shell; +use super::shell::catherine_shell; use crate::catherine::{ NAME, VERSION }; @@ -328,4 +328,4 @@ pub fn pretty_output(input: &str, output: &str, left_col: &str, right_col: &str) // Ok(value) => set_current_dir(value).expect("Unable to set directory"), // Err(err) => println!("Unable to interpret environment variable. Is your $HOME variable set?\n {}", err), // } -// } \ No newline at end of file +// } diff --git a/src/main.rs b/src/main.rs index ad0a121..eff2d97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,5 +21,5 @@ fn main() { // Returns the banner vector // Randomizes u8 integer (+ converts to usize) for random banner from vector catherine::init(&banners()[num as usize]); - catherine::shutdown("You know what they say cowboy, easy come easy go..."); -} \ No newline at end of file + catherine::shutdown("Whatever happens, happens"); +} diff --git a/src/meta.rs b/src/meta.rs index df28046..e7983bc 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -65,4 +65,4 @@ pub fn banners() -> Vec { ]; return banners; -} \ No newline at end of file +} diff --git a/src/modules/db/README.md b/src/modules/db/README.md new file mode 100644 index 0000000..c86276d --- /dev/null +++ b/src/modules/db/README.md @@ -0,0 +1,11 @@ +# Database Module + +The database module contains solutions for gathering database information, analyzing databases in real-time, and consistent database communication. + +The following databases are supported: + +| Database | Location | +|------------|--------------| +| Redis | db/redis.py | + +NOTE: These are unique modules that spawn individual shells to communicate with the database. In most cases, Catherine will still be active, so once you exit the database shell, it'll drop you back into the Catherine prompt. diff --git a/src/modules/db/redis.py b/src/modules/db/redis.py new file mode 100644 index 0000000..9aeb9d0 --- /dev/null +++ b/src/modules/db/redis.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- + +""" + Project: Catherine Framework (https://github.com/azazelm3dj3d/catherine) + Author: azazelm3dj3d (https://github.com/azazelm3dj3d) + License: BSD 2-Clause +""" + +import redis, time, sys, os, platform, argparse, colorama +from subprocess import getoutput +from prettytable import PrettyTable + +r = redis.Redis() + +# Colorama config +colorama.init() +RED = colorama.Fore.RED +RESET = colorama.Fore.RESET + +class RedisAnalysis: + def real_time_render(): + parser = argparse.ArgumentParser() + parser.add_argument('-s', '--start_server', help="Choose specific keys to investigate in real-time", action='store_true', default=True, required=False) + args = parser.parse_args() + + def clear(): + if platform.system() == "Windows": + os.system("cls") + else: + os.system("clear") + + timer = 0 + + if args.start_server: + print("Example: key1 key2 key3") + user_keys = input("Enter Search Criteria (seperated by spaces): ") + + key_list = user_keys.split() + + try: + while True: + # 0.1 = 100ms + time.sleep(0.1) + clear() + + state_header = PrettyTable(["Keys", "Values"]) + + for key in key_list: + state_value = r.mget(key) + state_header.add_row([key, state_value]) + + print(state_header.get_string(title="Real Time Redis Data")) + + timer += 100 + + print("\nTime Elapsed: {0}ms".format(timer)) + print("\nYou can exit by pressing Ctrl + C") + except redis.exceptions.ConnectionError: + print("Unable to connect to Redis") + + def redis_comms(): + whoami = getoutput("whoami") + print("Connecting to db shell...") + time.sleep(0.5) + + while True: + try: + command = input(f"{whoami}@{RED}redis_analysis{RESET}[🦀 Catherine Framework 🦀]:~$ ") + + if command == "h" or command == "help": + print("q, quit Quit program") + print("h, help Displays help menu") + print("k, key Search for a specific Redis key") + print("v, version Check version of Redis & RediSea") + print("c, clear Clears all data in the terminal") + print("d, dump Dump entire Redis database (keys)") + print("df, dumpf Dump entire Redis database (keys) into a file") + print("i, info Return general information about the Redis instance") + print("r, remote Remotely connect to a Redis instance") + print("rt, realtime View Redis data update in real-time") + elif command == "q" or command == "quit" or command == "exit": + print("Disconnecting...") + time.sleep(0.2) + sys.exit() + elif command == "v" or command == "version": + try: + print(f"Redis Version: {r.execute_command('INFO')['redis_version']}") + except redis.exceptions.ConnectionError: + print("Unable to connect to Redis") + elif command == "k" or command == "key": + key = input("Key: ") + + try: + key_output = r.mget(key) + print(f"Key: {key}\nValue: {key_output}") + except redis.exceptions.ConnectionError: + print("Unable to connect to Redis") + elif command == "c" or command == "clear": + system_info = platform.system() + + if system_info == 'Windows': + os.system("cls") + else: + os.system("clear") + elif command == "dump" or command == "d": + try: + for key in r.scan_iter("*"): + print(key) + except redis.exceptions.ConnectionError: + print("Unable to connect to Redis") + elif command == "df" or command == "dumpf": + try: + with open('redis_dump.log', 'w') as f: + for key in r.scan_iter("*"): + f.write(str(key) + "\n") + + print(f"{RED}[+]{RESET} Data successfully dumped!") + except redis.exceptions.ConnectionError: + print("Unable to connect to Redis") + elif command == "i" or command == "info": + try: + redis_data = r.execute_command('CLIENT LIST') + redis_data_str = str(redis_data) + print(redis_data_str) + except redis.exceptions.ConnectionError: + print("Unable to connect to Redis") + elif command == "r" or command == "remote": + ip_address = input("IP Address: ") + port = input("Port: ") + + confirm_choice = input("Are you sure you would like to continue (y/n)? ") + + if confirm_choice == "y": + # Connect to the Redis database + os.system(f"redis-cli -h {ip_address} -p {port}") + elif confirm_choice == "n": + print("Exiting...") + else: + print("Please choose y/n") + elif command == "rt" or command == "realtime": + try: + RedisAnalysis().real_time_render() + except: + print("Unable to connect to Redis") + else: + print("Unrecognized Command\n") + print("Available commands:") + print("q, quit Quit program") + print("h, help Displays help menu") + print("k, key Search for a specific Redis key") + print("v, version Check version of Redis & RediSea") + print("c, clear Clears all data in the terminal") + print("d, dump Dump entire Redis database (keys)") + print("df, dumpf Dump entire Redis database (keys) into a file") + print("i, info Return general information about the Redis instance") + print("r, remote Remotely connect to a Redis instance") + print("rt, realtime View Redis data update in real-time\n") + + except KeyboardInterrupt: + time.sleep(0.2) + sys.exit() + +if __name__ == '__main__': + RedisAnalysis.redis_comms() diff --git a/src/modules/formats/README.md b/src/modules/formats/README.md new file mode 100644 index 0000000..909ecfb --- /dev/null +++ b/src/modules/formats/README.md @@ -0,0 +1,10 @@ +# Formats Module + +The formats module contains solutions for data collection, deobfuscation, or anything else related to the retention/collection of data for file formats. + +## Formats + +| Type | Location | Format | +|------------------------------|--------------------------|--------| +| Hexadecminal dumping (Rust) | data/hex/rs_hex_dump.rs | Hex | +| Hexadecminal dumping (C) | data/hex/c_hex_dump.rs | Hex | diff --git a/src/modules/formats/exe/win_exe_dump.py b/src/modules/formats/exe/win_exe_dump.py new file mode 100644 index 0000000..458c1fb --- /dev/null +++ b/src/modules/formats/exe/win_exe_dump.py @@ -0,0 +1,56 @@ +""" + Project: Catherine Framework (https://github.com/azazelm3dj3d/catherine) + Author: azazelm3dj3d (https://github.com/azazelm3dj3d) + License: BSD 2-Clause +""" + +import pefile, datetime, sys + +try: + file_loc = sys.argv[1] +except IndexError: + print("Missing file location argument") + sys.exit() + +try: + try: + pe = pefile.PE(str(file_loc)) + except FileNotFoundError: + print("Unable to locate file. Please make sure the path is correct") + sys.exit() +except pefile.PEFormatError: + print("Wrong execution format. Module only accepts Windows execution format") + sys.exit() + +class ExecDumpWin: + """ + This module dumps Windows executable information, mainly targeting binaries, DLLs, and similar files. + Thanks to the `pefile` library, we can dump magic numbers, header information, and much more. + """ + + def bit_identifier() -> bool: + if hex(pe.OPTIONAL_HEADER.Magic) == '0x10b': + return False + elif hex(pe.OPTIONAL_HEADER.Magic) == '0x20b': + return True + + def exe_dump(self): + print("\nFile Information") + print(f"Magic Number (int): {pe.OPTIONAL_HEADER.Magic} (hex: {hex(pe.OPTIONAL_HEADER.Magic)})") + + if ExecDumpWin.bit_identifier(): + print(f"Binary info: {hex(pe.OPTIONAL_HEADER.Magic)} (64-bit)") + elif ExecDumpWin.bit_identifier() != True: + print(f"Binary info: {hex(pe.OPTIONAL_HEADER.Magic)} (32-bit)") + + print(f"TimeDateStamp: {pe.FILE_HEADER.dump_dict()['TimeDateStamp']['Value'].split('[')[1][:-1]}\n") + + # Dumps header info to a log file + with open(f"exec_dump.log", "w") as f: + f.write(pe.dump_info()) + +if __name__ == '__main__': + try: + ExecDumpWin.exe_dump() + except NameError: + pass \ No newline at end of file diff --git a/src/modules/formats/hex/c_hex_dump.c b/src/modules/formats/hex/c_hex_dump.c new file mode 100644 index 0000000..5e0da9e --- /dev/null +++ b/src/modules/formats/hex/c_hex_dump.c @@ -0,0 +1,64 @@ +/* + Project: Catherine Framework (https://github.com/azazelm3dj3d/catherine) + Author: azazelm3dj3d (https://github.com/azazelm3dj3d) + License: BSD 2-Clause +*/ + +/* + The primary focus of the c_hex_dump module is to offer an alternative to the Rust hex_dump function for dumping hexadecimal information about a file. +*/ + +#include +#include + +int ascii_to_hex(char c) { + int n = (int) c; + + if (n < 58 && n > 47) { return n - 48; } + if (n < 103 && n > 96) { return n - 87; } + + return n; +} + +void collect_hex(char *filename) { + + // Opens file for reading, allowing us to obtain hexadecimal information + FILE *fp = fopen(filename, "r"); + + // Seeks the file to locate the beginning and end of the file (file_len) + fseek(fp, 0, SEEK_END); + int file_len = ftell(fp); + fseek(fp, 0, SEEK_SET); + + unsigned char conversion_one, conversion_two; + unsigned char final_result, return_hex[file_len / 2]; + + // Saves hex informatation to a file + FILE *call_c_lib = fopen("c_hex_dump.hex", "wb+"); + + // Hits an error if unable to create the file + if (call_c_lib == NULL) { + printf("Unable to create a file\n"); + exit(EXIT_FAILURE); + } + + int i = 0; + + // Iterates over the file for the length of the file + for (i = 0; i < file_len / 2; i++) { + conversion_one = ascii_to_hex(fgetc(fp)); + conversion_two = ascii_to_hex(fgetc(fp)); + final_result = conversion_one << 4 | conversion_two; + return_hex[i] = final_result; + + // Writes data to a file + fprintf(call_c_lib, "%02x ", final_result); + + // Prints data to stdout + // printf("%02x ", final_result); + } + + // Closes file + fclose(call_c_lib); + printf("\n"); +} diff --git a/src/modules/formats/hex/mod.rs b/src/modules/formats/hex/mod.rs new file mode 100644 index 0000000..5737edf --- /dev/null +++ b/src/modules/formats/hex/mod.rs @@ -0,0 +1,7 @@ +/* + Project: Catherine Framework (https://github.com/azazelm3dj3d/catherine) + Author: azazelm3dj3d (https://github.com/azazelm3dj3d) + License: BSD 2-Clause +*/ + +pub mod rs_hex_dump; diff --git a/src/modules/rust_hex_dump.rs b/src/modules/formats/hex/rs_hex_dump.rs similarity index 88% rename from src/modules/rust_hex_dump.rs rename to src/modules/formats/hex/rs_hex_dump.rs index 189881c..a46ba96 100644 --- a/src/modules/rust_hex_dump.rs +++ b/src/modules/formats/hex/rs_hex_dump.rs @@ -11,11 +11,13 @@ use std::{ str, env }; -use mercy::mercy_hex; +use mercy::hex; use libloading::{ Library, Symbol }; fn access_c_lib(convert_file: &str) { if Path::new(convert_file).exists() { + let filepath = "/opt/catherine/modules/formats/hex/hex.so"; + // Being precautious - don't want to even initialize an unsafe ability if the file doesn't exist unsafe { // Handles the pointer assignment @@ -23,7 +25,7 @@ fn access_c_lib(convert_file: &str) { if env::consts::OS == "linux" { // Sets the shared object - let lib = { Library::new("/opt/catherine/modules/data/hex/c/dist/hex.so").unwrap() }; + let lib = { Library::new(filepath.to_string()).unwrap() }; // Grabs the C function we need to call let call_c_lib: Symbol *const c_char> = lib.get("collect_hex\0".as_bytes()).unwrap(); @@ -41,10 +43,10 @@ fn access_c_lib(convert_file: &str) { pub fn collect_hex(option: &str, convert_file: &str) { if option == "get_data_dump" { - mercy_hex("hex_dump", convert_file); + hex("hex_dump", convert_file); } else if option == "access_c_lib" { access_c_lib(convert_file); } else { println!("Unrecognized function call"); } -} \ No newline at end of file +} diff --git a/src/modules/formats/mod.rs b/src/modules/formats/mod.rs new file mode 100644 index 0000000..a5bf447 --- /dev/null +++ b/src/modules/formats/mod.rs @@ -0,0 +1,7 @@ +/* + Project: Catherine Framework (https://github.com/azazelm3dj3d/catherine) + Author: azazelm3dj3d (https://github.com/azazelm3dj3d) + License: BSD 2-Clause +*/ + +pub mod hex; diff --git a/src/modules/mercy/README.md b/src/modules/mercy/README.md new file mode 100644 index 0000000..14ff803 --- /dev/null +++ b/src/modules/mercy/README.md @@ -0,0 +1,15 @@ +# Mercy Extension Module + +Mercy is a unique module created to be the main source of all things decryption, decoding, and deobfuscation. This module is an extension of the Mercy Rust crate. + +The following methods are currently supported: + +| Method | Ability | +|-------------|-----------| +| Base64 | Decode | +| Rot13 | Decode | +| Base32 | Decode | +| JWT Token | Decode | +| Fernet | Decrypt | + +NOTE: We do not currently support encoding or encrypting since the core functionality of the framework is oriented toward "defensive-type" operations. diff --git a/src/modules/mercy/extension.py b/src/modules/mercy/extension.py new file mode 100644 index 0000000..b5fd9c7 --- /dev/null +++ b/src/modules/mercy/extension.py @@ -0,0 +1,69 @@ +""" + Project: Catherine Framework (https://github.com/azazelm3dj3d/catherine) + Author: azazelm3dj3d (https://github.com/azazelm3dj3d) + License: BSD 2-Clause +""" + +import base64, argparse +from cryptography.fernet import Fernet +import jwt as j + +parser = argparse.ArgumentParser() +parser.add_argument('-b32', '--base32', help="Decode Base32", action='store_true', default=False, required=False) +parser.add_argument('-j', '--jwt', help="Decode JWT token", action='store_true', default=False, required=False) +parser.add_argument('-f', '--fernet', help="Decrypt Fernet encryption", action='store_true', default=False, required=False) +parser.add_argument('-s', '--set_string', help="Encoded/Encrypted string", default=None, required=True) +parser.add_argument('-k', '--key', help="Fernet key // JWT secret", default=None, required=False) +args = parser.parse_args() + +class MercyExtension: + """ + Extension for the Mercy Rust crate. + """ + + def decode_base32(self, msg: str): + """ + Base32 decoding + """ + base32_bytes = msg.encode("UTF-8") + decoder = base64.b32decode(base32_bytes) + print(f"\nBase32 Decoded: {decoder.decode('UTF-8')}") + + def decode_jwt(self, msg: str, key): + """ + JWT token decoding + """ + jwt_token = msg + jwt_secret = key + decoded_msg = j.decode(jwt_token, str(jwt_secret), algorithms=['HS256']) + print(f"\nJWT Token Decoded: {decoded_msg['payload']}") + + def decrypt_fernet(self, msg: str, key: str): + """ + Fernet decryption + """ + private_key: str = key + encrypted_msg: str = msg + + # Requires Fernet key for the encrypted message + fernet = Fernet(private_key) + + decrypt_msg = fernet.decrypt(encrypted_msg) + print(f"Fernet Decrypted: {decrypt_msg.decode('UTF-8')}") + +if __name__ == '__main__': + set_msg = args.set_string + set_key = args.key + + if args.base32: + MercyExtension().decode_base32(set_msg) + elif args.jwt: + try: + MercyExtension().decode_jwt(set_msg, set_key) + except j.exceptions.InvalidSignatureError: + print("\nJWT API was unable to recognize the signature") + elif args.fernet: + MercyExtension().decrypt_fernet(set_msg, set_key) + else: + print("Unrecognized method") + exit() \ No newline at end of file diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 9966f4d..10c059b 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -4,4 +4,4 @@ License: BSD 2-Clause */ -pub mod rust_hex_dump; \ No newline at end of file +pub mod formats; diff --git a/src/modules/net/README.md b/src/modules/net/README.md new file mode 100644 index 0000000..4cc4e6f --- /dev/null +++ b/src/modules/net/README.md @@ -0,0 +1,9 @@ +# Network Module + +The network module consists of anything that communicates with an external or internal network. Collects information such as hostname, public records (MX, TXT, CNAME, etc.), nameservers, and more. + +Available tools: + +| Type | Location | +|--------------|---------------------| +| NetScan | net/netscan/src/* | diff --git a/src/modules/net/netscan/src/cmds/cli/commands.go b/src/modules/net/netscan/src/cmds/cli/commands.go new file mode 100644 index 0000000..5f0bc82 --- /dev/null +++ b/src/modules/net/netscan/src/cmds/cli/commands.go @@ -0,0 +1,309 @@ +/* + Project: Catherine Framework (https://github.com/azazelm3dj3d/catherine) + Author: azazelm3dj3d (https://github.com/azazelm3dj3d) + License: BSD 2-Clause +*/ + +package commands + +import ( + "fmt" + "log" + "net" + "os" + + "github.com/urfave/cli" +) + +func NetCommands() { + controller := cli.NewApp() + + controller.Usage = "Go application created to gather network information about a specific host" + + defaultValues := []cli.Flag { + cli.StringFlag { + // Default values for commands + Name: "host", + Value: "google.com", // Testing only + }, + } + + // Command List + controller.Commands = []cli.Command { + { + Name: "ns", + Usage: "Returns nameserver information about a particular host", + Flags: defaultValues, + + // ns command activated + Action: func(cmd *cli.Context) error { + ns, err := net.LookupNS(cmd.String("host")) + + // if err != nil { + // return err + // } + + // Nameservers + fmt.Println("Nameserver(s):") + fmt.Println("==============================") + + // Logs results to terminal + if err != nil { + fmt.Println("[*] Unable to gather information. Are you connected to the internet?\n") + + fmt.Println("Error Encountered:", err) + } else { + for i := 0; i < len(ns); i++ { + fmt.Println("[*]", ns[i].Host) + } + } + + fmt.Println("==============================") + + return nil + }, + }, + { + Name: "ip", + Usage: "Returns IP address(es) associated with a particular host", + Flags:defaultValues, + + // ip command activated + Action: func(cmd *cli.Context) error { + ip, err := net.LookupIP(cmd.String("host")) + + // if err != nil { + // return err + // } + + // IP Address(es) + fmt.Println("IP Address(es):") + fmt.Println("==============================") + + // Logs results to terminal + if err != nil { + fmt.Println("[*] Unable to gather information. Are you connected to the internet?\n") + + fmt.Println("Error Encountered:", err) + } else { + for i := 0; i < len(ip); i++ { + fmt.Println("[*]", ip[i]) + } + } + + fmt.Println("==============================") + + return nil + }, + }, + { + Name: "cname", + Usage: "Returns CNAME record(s) about a particular host", + Flags:defaultValues, + + // cname command activated + Action: func(cmd *cli.Context) error { + cname, err := net.LookupCNAME(cmd.String("host")) + + // if err != nil { + // return err + // } + + // CNAME + fmt.Println("CNAME Record(s):") + fmt.Println("==============================") + + // Logs results to terminal + if err != nil { + fmt.Println("[*] Unable to gather information. Are you connected to the internet?\n") + + fmt.Println("Error Encountered:", err) + } else { + fmt.Println("[*]", cname) + } + + fmt.Println("==============================") + + return nil + }, + }, + { + Name: "mx", + Usage: "Returns MX record(s) about a particular host", + Flags:defaultValues, + + // mx command activated + Action: func(cmd *cli.Context) error { + mx, err := net.LookupMX(cmd.String("host")) + + // if err != nil { + // return err + // } + + // MX Record + fmt.Println("MX Record(s):") + fmt.Println("==============================") + + // Logs results to terminal + if err != nil { + fmt.Println("[*] Unable to gather information. Are you connected to the internet?\n") + + fmt.Println("Error Encountered:", err) + } else { + for i := 0; i < len(mx); i++ { + fmt.Println("Host:", mx[i].Host) + fmt.Println("Priority:", mx[i].Pref) + } + } + + fmt.Println("==============================") + + return nil + }, + }, + { + Name: "txt", + Usage: "Returns TXT record(s) about a particular host", + Flags:defaultValues, + + // txt command activated + Action: func(cmd *cli.Context) error { + txt, err := net.LookupTXT(cmd.String("host")) + + // if err != nil { + // return err + // } + + // TXT Record + fmt.Println("TXT Record(s):") + fmt.Println("==============================") + + // Logs results to terminal + if err != nil { + fmt.Println("[*] Unable to gather information. Are you connected to the internet?\n") + + fmt.Println("Error Encountered:", err) + } else { + for i := 0; i < len(txt); i++ { + fmt.Println("[*]", txt[i]) + } + } + + fmt.Println("==============================") + + return nil + }, + }, + { + Name: "all", + Usage: "Returns all information about a particular host", + Flags:defaultValues, + + // all command activated + Action: func(cmd *cli.Context) error { + ns, err := net.LookupNS(cmd.String("host")) + ip, err := net.LookupIP(cmd.String("host")) + cname, err := net.LookupCNAME(cmd.String("host")) + mx, err := net.LookupMX(cmd.String("host")) + txt, err := net.LookupTXT(cmd.String("host")) + + // if err != nil { + // return err + // } + + fmt.Println("\n") + + // Nameservers + fmt.Println("Nameserver(s):") + fmt.Println("==============================") + + // Logs results to terminal + if err != nil { + fmt.Println("[*] Unable to gather information. Are you connected to the internet?\n") + + fmt.Println("Error Encountered:", err) + } else { + for i := 0; i < len(ns); i++ { + fmt.Println("[*]", ns[i].Host) + } + } + + fmt.Println("\n") + + // IP Address(es) + fmt.Println("IP Address(es):") + fmt.Println("==============================") + + // Logs results to terminal + if err != nil { + fmt.Println("[*] Unable to gather information. Are you connected to the internet?\n") + + fmt.Println("Error Encountered:", err) + } else { + for i := 0; i < len(ip); i++ { + fmt.Println("[*]", ip[i]) + } + } + + fmt.Println("\n") + + // CNAME + fmt.Println("CNAME Record(s):") + fmt.Println("==============================") + + // Logs results to terminal + if err != nil { + fmt.Println("[*] Unable to gather information. Are you connected to the internet?\n") + + fmt.Println("Error Encountered:", err) + } else { + fmt.Println("[*]", cname) + } + + fmt.Println("\n") + + // MX Record + fmt.Println("MX Record(s):") + fmt.Println("==============================") + + // Logs results to terminal + if err != nil { + fmt.Println("[*] Unable to gather information. Are you connected to the internet?\n") + + fmt.Println("Error Encountered:", err) + } else { + for i := 0; i < len(mx); i++ { + fmt.Println("Host:", mx[i].Host) + fmt.Println("Priority:", mx[i].Pref) + } + } + + fmt.Println("\n") + + // TXT Record + fmt.Println("TXT Record(s):") + fmt.Println("==============================") + + // Logs results to terminal + if err != nil { + fmt.Println("[*] Unable to gather information. Are you connected to the internet?\n") + + fmt.Println("Error Encountered:", err) + } else { + for i := 0; i < len(txt); i++ { + fmt.Println("[*]", txt[i]) + } + } + + return nil + }, + }, + } + + // Initializing Engine... + err := controller.Run(os.Args) + + if err != nil { + log.Fatal(err) + } +} \ No newline at end of file diff --git a/src/modules/net/netscan/src/go.mod b/src/modules/net/netscan/src/go.mod new file mode 100644 index 0000000..4a2cb27 --- /dev/null +++ b/src/modules/net/netscan/src/go.mod @@ -0,0 +1,10 @@ +module netscan + +go 1.18 + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect + github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/urfave/cli v1.22.9 // indirect +) diff --git a/src/modules/net/netscan/src/go.sum b/src/modules/net/netscan/src/go.sum new file mode 100644 index 0000000..2b7e56c --- /dev/null +++ b/src/modules/net/netscan/src/go.sum @@ -0,0 +1,12 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw= +github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/src/modules/net/netscan/src/main.go b/src/modules/net/netscan/src/main.go new file mode 100644 index 0000000..4a82ee0 --- /dev/null +++ b/src/modules/net/netscan/src/main.go @@ -0,0 +1,15 @@ +/* + Project: Catherine Framework (https://github.com/azazelm3dj3d/catherine) + Author: azazelm3dj3d (https://github.com/azazelm3dj3d) + License: BSD 2-Clause +*/ + +package main + +import ( + cmds "netscan/cmds/cli" +) + +func main() { + cmds.NetCommands() +} \ No newline at end of file diff --git a/src/modules/web/README.md b/src/modules/web/README.md new file mode 100644 index 0000000..608a6ae --- /dev/null +++ b/src/modules/web/README.md @@ -0,0 +1,9 @@ +# Web Module + +The web module is any tool or script that collects any type of information or communicates with the web. + +Available tools: + +| Tool | Location | +|---------------|--------------------------------| +| Link Parser | modules/web/parsers/links.py | diff --git a/src/modules/web/parsers/links.py b/src/modules/web/parsers/links.py new file mode 100644 index 0000000..6c30b74 --- /dev/null +++ b/src/modules/web/parsers/links.py @@ -0,0 +1,123 @@ +""" + Project: Catherine Framework (https://github.com/azazelm3dj3d/catherine) + Author: azazelm3dj3d (https://github.com/azazelm3dj3d) + License: BSD 2-Clause +""" + +import requests, colorama, time, sys +from urllib.parse import urlparse, urljoin +from bs4 import BeautifulSoup + +# Colorama config +colorama.init() +GREEN = colorama.Fore.GREEN +RED = colorama.Fore.RED +RESET = colorama.Fore.RESET + +# Creates global links for URLs +internal_links = set() +external_links = set() + +set_host = sys.argv[1] # str +set_batch = 10 + +class Parser: + """ + Parses a URL, locating all associated links, then seperating into internal and external. + """ + + def host_validation(self, host): + """ + Validates URL + """ + + parse_links = urlparse(host) + host_scheme = bool(parse_links.scheme) + host_netloc = bool(parse_links.netloc) + + return host_scheme and host_netloc + + def link_collector(self, host): + """ + Collects URL links + """ + + # Sets new URL object for collecting a pool of links + hosts = set() + website = urlparse(host).netloc + + try: + soup = BeautifulSoup(requests.get(host).content, "html.parser") + except requests.exceptions.ConnectionError: + print(f"Unable to secure a connection for {set_host}") + + for anchor in soup.findAll("a"): + href = anchor.attrs.get("href") + + if href == "" or href is None or href == "#": + continue + + href = urljoin(host, href) + parse_host = urlparse(href) + + # Sets HTTP(s) fragments and scheme + href = f"{parse_host.scheme}://{parse_host.netloc}{parse_host.path}" + + if not P.host_validation(href): + continue + + if href in internal_links: + continue + + if website not in href: + if href not in external_links: + print(f"{GREEN}[+]{RESET}" + f" External Link: {href}") + external_links.add(href) + + continue + + print(f"{GREEN}[+]{RESET}" + f" Internal Link: {href}") + + # Add all links to the hosts set() + hosts.add(href) + internal_links.add(href) + + return hosts + + check_max = 0 + + def parse_links(self, host, batch_num=int(set_batch)): + global check_max + + # Increments variable for every link found + Parser.check_max += 1 + + links = P.link_collector(host) + + # Parses links up to the max integer + for link in links: + if Parser.check_max > batch_num: + break + + P.parse_links(link, batch_num=batch_num) + +if __name__ == '__main__': + P = Parser() + + try: + print("Validating host...") + time.sleep(1) + + if P.host_validation(set_host): + print("Valid host submitted\n") + P.parse_links(set_host) + else: + print("Host is not valid") + sys.exit() + + except KeyboardInterrupt: + sys.exit() + + print(f"\n{GREEN}[+] {RESET}" + f"Number of Internal Links: {len(internal_links)}") + print(f"{GREEN}[+] {RESET}" + f"Number of External Links: {len(external_links)}") + print(f"{GREEN}[+] {RESET}" + f"Total: {len(external_links) + len(internal_links)}") diff --git a/src/ui/controller.rs b/src/ui/controller.rs index acb6c5c..b56416b 100644 --- a/src/ui/controller.rs +++ b/src/ui/controller.rs @@ -20,20 +20,20 @@ use chrono::{ }; use mercy::{ - mercy_decode, - mercy_extra, - mercy_experimental + decode, + extra, + experimental }; #[tauri::command] fn decode_string(method_name: &str, encoded_data: &str) -> String { - format!("Decoded String: {}", mercy_decode(method_name, encoded_data)) + format!("Decoded String: {}", decode(method_name, encoded_data)) } #[tauri::command] fn sys_info() -> String { if env::consts::OS == "linux" { - format!("{}Internal IP Address: {}\n", mercy_extra("system_info", "all"), mercy_extra("internal_ip", "")) + format!("{}Internal IP Address: {}\n", extra("system_info", "all"), extra("internal_ip", "")) } else { format!("Command not available on your operating system yet!") } @@ -41,38 +41,43 @@ fn sys_info() -> String { #[tauri::command] fn defang_string(defang_value: &str) -> String { - format!("Defanged: {}", mercy_extra("defang", defang_value)) + format!("Defanged: {}", extra("defang", defang_value)) } #[tauri::command] fn whois_search(whois_url: &str) -> String { - format!("WHOIS Data: \n{}", mercy_extra("whois", whois_url)) + format!("WHOIS Data: \n{}", extra("whois", whois_url)) } // Causes extreme lag. Needs to be reviewed. // #[tauri::command] // fn malicious_search(mal_url: &str) -> String { -// format!("URL Status: {}!", mercy_malicious("status", mal_url)) +// format!("URL Status: {}!", malicious("status", mal_url)) // } #[tauri::command] fn id_string(id_str: &str) -> String { - format!("Guess: {}", mercy_extra("identify", id_str)) + format!("Guess: {}", extra("identify", id_str)) } #[tauri::command] fn crack_hash(hash_cracker: &str) -> String { - format!("{}", mercy_extra("crack", hash_cracker)) + format!("{}", extra("crack", hash_cracker)) } #[tauri::command] fn domain_gen(domain_str: &str) -> String { - format!("{:?}", mercy_experimental("domain_gen", domain_str)) + format!("{:?}", experimental("domain_gen", domain_str)) } #[tauri::command] fn extract_zip(extract_zip_file: &str) { - mercy_experimental("zip", extract_zip_file) + experimental("zip", extract_zip_file) +} + +#[tauri::command] +fn parse_email(parse_email_file: &str) -> String { + format!("{}", extra("parse_email", parse_email_file)) } #[tauri::command] @@ -104,8 +109,9 @@ pub fn launch_gui() { crack_hash, domain_gen, extract_zip, + parse_email, exit_catherine ]) .run(tauri::generate_context!()) .expect("Unable to launch Catherine GUI"); -} \ No newline at end of file +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 22f67fa..b55dba1 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -4,4 +4,4 @@ License: BSD 2-Clause */ -pub mod controller; \ No newline at end of file +pub mod controller; diff --git a/tauri.conf.json b/tauri.conf.json index c4bf66d..30513ba 100644 --- a/tauri.conf.json +++ b/tauri.conf.json @@ -1,67 +1,62 @@ { - "build": { - "beforeBuildCommand": "", - "beforeDevCommand": "", - "devPath": "public", - "distDir": "public", - "withGlobalTauri": true + "build": { + "beforeBuildCommand": "", + "beforeDevCommand": "", + "devPath": "public", + "distDir": "public", + "withGlobalTauri": true + }, + "package": { + "productName": "catherine", + "version": "0.6.0" + }, + "tauri": { + "allowlist": { + "all": false }, - "package": { - "productName": "catherine", - "version": "0.5.0" - }, - "tauri": { - "allowlist": { - "all": false - }, - "bundle": { - "active": true, - "category": "DeveloperTool", - "copyright": "", - "deb": { - "depends": [] - }, - "externalBin": [], - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], - "identifier": "com.tauri.dev", - "longDescription": "", - "macOS": { - "entitlements": null, - "exceptionDomain": "", - "frameworks": [], - "providerShortName": null, - "signingIdentity": null - }, - "resources": [], - "shortDescription": "", - "targets": "all", - "windows": { - "certificateThumbprint": null, - "digestAlgorithm": "sha256", - "timestampUrl": "" - } - }, - "security": { - "csp": null + "bundle": { + "active": true, + "category": "DeveloperTool", + "copyright": "", + "deb": { + "depends": [] }, - "updater": { - "active": false + "externalBin": [], + "icon": [ + "icons/icon.png" + ], + "identifier": "com.azazelm3dj3d.catherine", + "longDescription": "", + "macOS": { + "entitlements": null, + "exceptionDomain": "", + "frameworks": [], + "providerShortName": null, + "signingIdentity": null }, - "windows": [ - { - "fullscreen": false, - "height": 720, - "resizable": true, - "title": "Catherine Framework | v0.5.0", - "width": 1280 - } - ] - } + "resources": [], + "shortDescription": "", + "targets": "all", + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "" + } + }, + "security": { + "csp": null + }, + "updater": { + "active": false + }, + "windows": [ + { + "fullscreen": false, + "height": 720, + "resizable": true, + "title": "Catherine Framework | v0.6.0", + "width": 1280 + } + ] } - \ No newline at end of file +}